[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = tab\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{ts,mts,mjs,cjs,js}]\nindent_style = tab\n\n[*.json]\nindent_style = tab\n\n[*.md]\nindent_style = tab\n\n[*.toml]\nindent_style = space\nindent_size = 4\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: mediasoup\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with a single custom sponsorship URL\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_Report.md",
    "content": "---\nname: 🐍 Bug Report\nabout: Report a bug in mediasoup\nlabels: bug\n---\n\n# Bug Report\n\n**IMPORTANT:** We primarily use GitHub as an issue tracker. Just open an issue here if you have encountered a bug in mediasoup.\n\nIf you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead:\n\nhttps://mediasoup.discourse.group\n\nIf you got a crash in mediasoup, please try to provide a core dump into the issue report:\n\nhttps://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump\n\n## Your environment\n\n- Operating system:\n- gcc/clang version:\n- mediasoup (Node) version:\n- mediasoup (Rust) version:\n- mediasoup-client version:\n\n## Issue description\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature_Request.md",
    "content": "---\nname: 🚀 Feature Request\nabout: Suggest an idea or improvement for mediasoup\nlabels: feature\n---\n\n# Feature Request\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: 🙈 Support Question\n    url: https://mediasoup.discourse.group\n    about: |\n      We primarily use GitHub as an issue tracker. Please, use the mediasoup Discourse Group if you have questions or doubts or if you need support about mediasoup and its ecosystem.\n      Before asking any questions, please check the mediasoup official documentation at https://mediasoup.org/documentation\n"
  },
  {
    "path": ".github/workflows/mediasoup-codeql.yaml",
    "content": "name: mediasoup-codeql\n\non:\n  push:\n    branches: [v3]\n    paths:\n      - 'worker/**'\n      - 'node/src/**'\n      - 'rust/src/**'\n  pull_request:\n    paths:\n      - 'worker/**'\n      - 'node/src/**'\n      - 'rust/src/**'\n  workflow_dispatch:\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-24.04\n    timeout-minutes: 120\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: ['c-cpp', 'javascript-typescript', 'python']\n\n    env:\n      MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true'\n      MEDIASOUP_LOCAL_DEV: 'true'\n      MEDIASOUP_BUILDTYPE: 'Release'\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n\n      - name: Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v3\n        with:\n          languages: ${{ matrix.language }}\n          config: |\n            paths-ignore:\n              - 'art/**'\n              - 'doc/**'\n              - 'node_modules/**'\n              - 'node/lib/**'\n              - 'node/src/fbs/**'\n              - 'rust/benches/**'\n              - 'rust/examples/**'\n              - 'rust/examples-frontend/**'\n              - 'worker/deps/**'\n              - 'worker/subprojects/**'\n              - 'worker/fuzzer/new-corpus/**'\n              - 'worker/fuzzer/reports/**'\n              - 'worker/out/**'\n          # If you wish to specify custom queries, you can do so here or in a\n          # config file. By default, queries listed here will override any\n          # specified in a config file. Prefix the list here with \"+\" to use\n          # these queries and those in the config file.\n          #\n          # Details on CodeQL's query packs refer to:\n          # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n          #\n          # queries: security-extended,security-and-quality\n\n      # Use `npm ci` to build mediasoup Node and worker instead of relying on\n      # built-in Autobuild.\n      - name: npm ci\n        run: npm ci --foreground-scripts\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v3\n        with:\n          category: '/language:${{matrix.language}}'\n"
  },
  {
    "path": ".github/workflows/mediasoup-node.yaml",
    "content": "name: mediasoup-node\n\non:\n  push:\n    branches: [v3]\n    paths:\n      - 'node/**'\n      - 'worker/**'\n      - 'package.json'\n      - 'package-lock.json'\n      - 'tsconfig.json'\n      - 'npm-scripts.mjs'\n      - '.github/workflows/mediasoup-node.yaml'\n  pull_request:\n    paths:\n      - 'node/**'\n      - 'worker/**'\n      - 'package.json'\n      - 'package-lock.json'\n      - 'tsconfig.json'\n      - 'npm-scripts.mjs'\n      - '.github/workflows/mediasoup-node.yaml'\n  workflow_dispatch:\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  ci:\n    strategy:\n      # Here we want to see all errors, not just the first one.\n      fail-fast: false\n      matrix:\n        build:\n          - os: ubuntu-24.04\n            node: 22\n            cc: gcc\n            cxx: g++\n          - os: ubuntu-24.04\n            node: 24\n            cc: gcc\n            cxx: g++\n            meson_args: '-Db_sanitize=address'\n            npm-audit: true\n            run-lint: true\n          - os: ubuntu-24.04\n            node: 24\n            cc: gcc\n            cxx: g++\n          - os: ubuntu-24.04\n            node: 24\n            cc: clang\n            cxx: clang++\n            meson_args: '-Db_sanitize=undefined'\n          - os: ubuntu-24.04-arm\n            node: 22\n            cc: gcc\n            cxx: g++\n          - os: ubuntu-24.04-arm\n            node: 24\n            cc: gcc\n            cxx: g++\n            meson_args: '-Db_sanitize=address'\n            # In Ubuntu 24.04 ARM in Debug build type with ASAN, createWorker()\n            # takes too long.\n            # See https://github.com/versatica/mediasoup/pull/1503.\n            skip-test-in-debug-build-type: true\n          - os: macos-15\n            node: 24\n            cc: clang\n            cxx: clang++\n          - os: windows-2022\n            node: 22\n            cc: cl\n            cxx: cl\n          - os: windows-2025\n            node: 24\n            cc: cl\n            cxx: cl\n        build-type:\n          - Release\n          - Debug\n\n    runs-on: ${{ matrix.build.os }}\n\n    timeout-minutes: 60\n\n    env:\n      CC: ${{ matrix.build.cc }}\n      CXX: ${{ matrix.build.cxx }}\n      MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true'\n      MEDIASOUP_LOCAL_DEV: 'true'\n      MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }}\n      MESON_ARGS: ${{ matrix.build.meson_args }}\n      # Disable leak detection because it's detected by the tool flatc uses to build.\n      # NOTE: This env only affects when 'b_sanitize' args are given in `meson_args`.\n      ASAN_OPTIONS: 'detect_leaks=0 symbolize=1 detect_stack_use_after_return=1 strict_init_order=1 check_initialization_order=1 detect_container_overflow=1'\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.build.node }}\n\n      - name: Configure cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.npm\n          key: ${{ matrix.build.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ matrix.build.os }}-node-\n\n      - name: npm ci --foreground-scripts\n        run: npm ci --foreground-scripts\n\n      - if: ${{ matrix.build.npm-audit }}\n        name: npm audit --omit dev\n        run: npm audit --omit dev\n\n      - if: ${{ matrix.build.npm-audit }}\n        name: npm audit --prefix worker/scripts\n        run: npm audit --prefix worker/scripts\n\n      - if: ${{ matrix.build-type == 'Release' }}\n        name: npm run worker:prebuild\n        run: npm run worker:prebuild\n\n      - name: npm run worker:prebuild-name\n        run: npm run worker:prebuild-name\n\n      - if: ${{ matrix.build.run-lint }}\n        name: npm run lint:node\n        run: npm run lint:node\n\n      - if: ${{ !matrix.build.skip-test-in-debug-build-type || matrix.build-type != 'Debug' }}\n        name: npm run test:node\n        run: npm run test:node\n"
  },
  {
    "path": ".github/workflows/mediasoup-rust.yaml",
    "content": "name: mediasoup-rust\n\non:\n  push:\n    branches: [v3]\n    paths:\n      - 'rust/**'\n      - 'worker/**'\n      - 'Cargo.toml'\n      - 'Cargo.lock'\n      - 'rust-toolchain.toml'\n      - '.github/workflows/mediasoup-rust.yaml'\n  pull_request:\n    paths:\n      - 'rust/**'\n      - 'worker/**'\n      - 'Cargo.toml'\n      - 'Cargo.lock'\n      - 'rust-toolchain.toml'\n      - '.github/workflows/mediasoup-rust.yaml'\n  workflow_dispatch:\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  ci:\n    strategy:\n      # Here we want to see all errors, not just the first one.\n      fail-fast: false\n      matrix:\n        build:\n          - os: ubuntu-24.04\n            run-cargo-fmt: true\n          - os: ubuntu-24.04-arm\n          - os: macos-15\n          - os: windows-2022\n          - os: windows-2025\n\n    runs-on: ${{ matrix.build.os }}\n\n    timeout-minutes: 60\n\n    env:\n      KEEP_BUILD_ARTIFACTS: '1'\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Configure cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n          key: ${{ matrix.build.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n          restore-keys: |\n            ${{ matrix.build.os }}-cargo-\n\n      - if: ${{ matrix.build.run-cargo-fmt }}\n        name: cargo fmt --all -- --check\n        run: cargo fmt --all -- --check\n\n      - name: cargo clippy --all-targets -- -D warnings\n        run: cargo clippy --all-targets -- -D warnings\n\n      # NOTE: In Windows this will build and test libmediasoupworker in release\n      # mode twice since build.rs doesn't allow debug mode on Windows.\n      - name: cargo test\n        run: |\n          cargo test --verbose\n          cargo test --release --verbose\n\n      - name: cargo doc --locked --all --no-deps --lib\n        run: cargo doc --locked --all --no-deps --lib\n        env:\n          DOCS_RS: '1'\n          RUSTDOCFLAGS: '-D rustdoc::broken-intra-doc-links -D rustdoc::private_intra_doc_links'\n"
  },
  {
    "path": ".github/workflows/mediasoup-worker-clang-tidy.yaml",
    "content": "name: mediasoup-worker-clang-tidy\n\non:\n  pull_request:\n    paths:\n      - 'worker/src/**/*.cpp'\n      - 'worker/include/**/*.hpp'\n      - 'worker/test/src/**/*.cpp'\n      - 'worker/test/include/**/*.hpp'\n      - 'worker/fuzzer/src/**/*.cpp'\n      - 'worker/fuzzer/include/**/*.hpp'\n      - 'worker/meson.build'\n      - 'worker/tasks.py'\n      - 'worker/.clang-format'\n      - 'worker/.clang-tidy'\n      - '.github/workflows/mediasoup-worker-clang-tidy.yaml'\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  clang-tidy:\n    runs-on: ubuntu-24.04\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Install dependencies\n        run: pip3 install --break-system-packages invoke\n\n      - name: Setup Meson build (generates compile_commands.json)\n        working-directory: worker\n        env:\n          MESON_ARGS: '-Dms_build_tests=true'\n        run: invoke setup\n\n      - name: Compile flatbuffers FBS files\n        working-directory: worker\n        run: invoke flatc\n\n      - name: Install NPM worker development tools\n        run: npm ci --prefix worker/scripts --foreground-scripts\n\n      - name: Normalize compile_commands.json paths\n        run: npm run normalize-compile-commands --prefix worker/scripts --foreground-scripts\n\n      - uses: ZedThree/clang-tidy-review@v0.23.1\n        id: review\n        with:\n          apt_packages: 'libclang-rt-21-dev'\n          build_dir: 'worker/out/Release/build'\n          config_file: 'worker/.clang-tidy'\n          clang_tidy_version: '21'\n          lgtm_comment_body: ''\n          extra_arguments: --quiet\n          include: 'worker/src/**/*.cpp,worker/test/src/**/*.cpp,worker/fuzzer/src/**/*.cpp'\n\n      # Uploads an artifact containing clang_fixes.json.\n      - uses: ZedThree/clang-tidy-review/upload@v0.23.1\n        id: upload-review\n\n      # If there are any comments, fail the check.\n      - if: steps.review.outputs.total_comments > 0\n        run: exit 1\n"
  },
  {
    "path": ".github/workflows/mediasoup-worker-fuzzer.yaml",
    "content": "name: mediasoup-worker-fuzzer\n\non:\n  push:\n    branches: [v3]\n    paths:\n      - 'worker/**'\n      - '.github/workflows/mediasoup-worker-fuzzer.yaml'\n  pull_request:\n    paths:\n      - 'worker/**'\n      - '.github/workflows/mediasoup-worker-fuzzer.yaml'\n  workflow_dispatch:\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  ci:\n    strategy:\n      matrix:\n        build:\n          - os: ubuntu-24.04\n            cc: clang\n            cxx: clang++\n            pip-break-system-packages: true\n          - os: ubuntu-24.04-arm\n            cc: clang\n            cxx: clang++\n            pip-break-system-packages: true\n        build-type:\n          - Release\n          - Debug\n\n    runs-on: ${{ matrix.build.os }}\n\n    timeout-minutes: 60\n\n    env:\n      CC: ${{ matrix.build.cc }}\n      CXX: ${{ matrix.build.cxx }}\n      MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true'\n      MEDIASOUP_LOCAL_DEV: 'true'\n      MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      # We need to install pip invoke manually.\n      - if: ${{ !matrix.build.pip-break-system-packages }}\n        name: pip3 install invoke\n        run: pip3 install invoke\n\n      # In modern OSs we need to run pip with this option.\n      - if: ${{ matrix.build.pip-break-system-packages }}\n        name: pip3 install --break-system-packages invoke\n        run: pip3 install --break-system-packages invoke\n\n      # Build the mediasoup-worker-fuzzer binary (which uses libFuzzer).\n      - name: invoke -r worker fuzzer\n        run: invoke -r worker fuzzer\n\n      # Run mediasoup-worker-fuzzer for 5 minutes.\n      - name: run-fuzzer.sh 300\n        run: cd worker && ./scripts/run-fuzzer.sh 300\n"
  },
  {
    "path": ".github/workflows/mediasoup-worker-prebuild.yaml",
    "content": "name: mediasoup-worker-prebuild\n\n# Only trigger on GitHub releases.\non:\n  release:\n    types: [published]\n\njobs:\n  ci:\n    strategy:\n      # Here we want to see all errors, not just the first one.\n      fail-fast: false\n      matrix:\n        build:\n          # Worker prebuild for Linux with kernel version 6 Ubuntu (22.04).\n          # Let's use Ubuntu 22.04 instead of 24.04 so that it builds the\n          # mediasoup-worker binary using an old version of GLib that will work\n          # on Linux hosts running more modern GLib versions.\n          # See https://github.com/versatica/mediasoup/issues/1089.\n          - os: ubuntu-22.04\n            cc: gcc\n            cxx: g++\n          - os: ubuntu-22.04-arm\n            cc: gcc\n            cxx: g++\n          - os: macos-15\n            cc: clang\n            cxx: clang++\n          - os: windows-2025\n            cc: cl\n            cxx: cl\n\n    runs-on: ${{ matrix.build.os }}\n\n    timeout-minutes: 90\n\n    env:\n      CC: ${{ matrix.build.cc }}\n      CXX: ${{ matrix.build.cxx }}\n      MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true'\n      MEDIASOUP_LOCAL_DEV: 'true'\n      MEDIASOUP_BUILDTYPE: 'Release'\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n\n      # We need to install some NPM production deps for npm-scripts.mjs to\n      # work.\n      - name: npm ci --ignore-scripts --omit=dev --foreground-scripts\n        run: npm ci --ignore-scripts --omit=dev --foreground-scripts\n\n      - name: npm run worker:prebuild-name\n        run: npm run worker:prebuild-name\n\n      - name: npm run worker:build\n        run: npm run worker:build\n\n      # Publish prebuild binaries on tag.\n      - name: npm run worker:prebuild\n        run: npm run worker:prebuild\n\n      - name: Upload mediasoup-worker prebuilt binary\n        uses: softprops/action-gh-release@v2\n        with:\n          files: worker/prebuild/mediasoup-worker-*.tgz\n"
  },
  {
    "path": ".github/workflows/mediasoup-worker.yaml",
    "content": "name: mediasoup-worker\n\non:\n  push:\n    branches: [v3]\n    paths:\n      - 'worker/**'\n      - 'worker/scripts/package.json'\n      - 'worker/scripts/package-lock.json'\n      - '.github/workflows/mediasoup-worker.yaml'\n  pull_request:\n    paths:\n      - 'worker/**'\n      - 'worker/scripts/package.json'\n      - 'worker/scripts/package-lock.json'\n      - '.github/workflows/mediasoup-worker.yaml'\n  workflow_dispatch:\n\nconcurrency:\n  # Cancel a currently running workflow from the same PR, branch or tag when a\n  # new workflow is triggered.\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  ci:\n    strategy:\n      # Here we want to see all errors, not just the first one.\n      fail-fast: false\n      matrix:\n        build:\n          - os: ubuntu-22.04\n            cc: gcc\n            cxx: g++\n            build-type: Release\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: ubuntu-22.04\n            cc: clang\n            cxx: clang++\n            build-type: Release\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: ubuntu-22.04-arm\n            cc: gcc\n            cxx: g++\n            build-type: Release\n            # No clang-format for ARM.\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: ubuntu-24.04\n            cc: gcc\n            cxx: g++\n            build-type: Release\n            pip-break-system-packages: true\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n            # Compile with all Meson option flags enabled once with gcc in\n            # Release mode.\n            meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true'\n          - os: ubuntu-24.04\n            cc: gcc\n            cxx: g++\n            build-type: Debug\n            pip-break-system-packages: true\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n            # Compile with all Meson option flags enabled once with gcc in\n            # Debug mode.\n            meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true -Dms_disable_liburing=true'\n          - os: ubuntu-24.04\n            cc: clang\n            cxx: clang++\n            build-type: Release\n            pip-break-system-packages: true\n            run-lint: false\n            run-test: true\n            run-test-asan-address: true\n            run-test-asan-undefined: true\n            # Let's just compile with all Meson option flags enabled once with\n            # clang.\n            meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true'\n          - os: ubuntu-24.04-arm\n            cc: gcc\n            cxx: g++\n            build-type: Release\n            pip-break-system-packages: true\n            # No clang-format for ARM.\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: ubuntu-24.04-arm\n            cc: clang\n            cxx: clang++\n            build-type: Release\n            pip-break-system-packages: true\n            # No clang-format for ARM.\n            run-lint: false\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: macos-15\n            cc: clang\n            cxx: clang++\n            build-type: Release\n            pip-break-system-packages: true\n            # Run lint for the latest macos.\n            run-lint: true\n            run-test: true\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: windows-2022\n            cc: cl\n            cxx: cl\n            build-type: Release\n            # No clang-format for Windows.\n            run-lint: false\n            # Maybe some day we fix this.\n            run-test: false\n            # Address Sanitizer does not work on Windows.\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n          - os: windows-2025\n            cc: cl\n            cxx: cl\n            build-type: Release\n            # No clang-format for Windows.\n            run-lint: false\n            # Maybe some day we fix this.\n            run-test: false\n            # Address Sanitizer does not work on Windows.\n            run-test-asan-address: false\n            run-test-asan-undefined: false\n\n    runs-on: ${{ matrix.build.os }}\n\n    timeout-minutes: 90\n\n    env:\n      CC: ${{ matrix.build.cc }}\n      CXX: ${{ matrix.build.cxx }}\n      MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true'\n      MEDIASOUP_LOCAL_DEV: 'false'\n      MEDIASOUP_BUILDTYPE: ${{ matrix.build.build-type }}\n      MESON_ARGS: ${{ matrix.build.meson_args }}\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 24\n\n      - name: Configure cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.npm\n          key: ${{ matrix.build.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ matrix.build.os }}-node-\n\n      # We need to install pip invoke manually.\n      - if: ${{ !matrix.build.pip-break-system-packages }}\n        name: pip3 install invoke\n        run: pip3 install invoke\n\n      # In modern OSs we need to run pip with this option.\n      - if: ${{ matrix.build.pip-break-system-packages }}\n        name: pip3 install --break-system-packages invoke\n        run: pip3 install --break-system-packages invoke\n\n      # Fail if run-lint is set for non macos.\n      - if: ${{ matrix.build.run-lint && !startsWith(matrix.build.os, 'macos') }}\n        name: fail if run-lint is set for non macos\n        run: |\n          echo \"run-lint set for non macos\"\n          exit 1\n\n      # Install clang-format on macos.\n      - if: ${{ matrix.build.run-lint && startsWith(matrix.build.os, 'macos') }}\n        name: brew install clang-format@22\n        run: |\n          brew install clang-format@22\n\n      # We need to install npm deps of worker/scripts/package.json.\n      - if: ${{ matrix.build.run-lint }}\n        name: npm ci --prefix worker/scripts\n        run: npm ci --prefix worker/scripts --foreground-scripts\n\n      - if: ${{ matrix.build.run-lint }}\n        name: invoke -r worker lint\n        run: invoke -r worker lint\n\n      - name: invoke -r worker mediasoup-worker\n        run: invoke -r worker mediasoup-worker\n\n      - if: ${{ matrix.build.run-test }}\n        name: invoke -r worker test\n        run: invoke -r worker test\n\n      # Let's clean everything before rebuilding worker tests with ASAN.\n      - if: ${{ matrix.build.run-test-asan-address }}\n        name: invoke -r worker test-asan-address\n        run: invoke -r worker clean-all && invoke -r worker test-asan-address\n\n      # Let's clean everything before rebuilding worker tests with ASAN.\n      - if: ${{ matrix.build.run-test-asan-undefined }}\n        name: invoke -r worker test-asan-undefined\n        run: invoke -r worker clean-all && invoke -r worker test-asan-undefined\n"
  },
  {
    "path": ".gitignore",
    "content": "## Meson.\n/worker/out\n/worker/subprojects/*\n!/worker/subprojects/*.wrap\n!/worker/subprojects/.clang-tidy\n\n## Node.\n/node_modules\n/node/lib\n# flatc generated files.\n/node/src/fbs\n\n## Rust.\n/rust/examples-frontend/*/node_modules\n/rust/examples-frontend/*/package-lock.json\n/target\n\n## Worker.\n/worker/scripts/node_modules\n# Flatc generated files.\n/worker/include/FBS\n/worker/prebuild\n# Python invoke.\n/worker/pip_invoke\n# Build artifacts.\n/worker/**/Debug\n/worker/**/Release\n# clang-fuzzer stuff is too big.\n/worker/deps/clang-fuzzer\n# Ignore all fuzzer generated test inputs.\n/worker/fuzzer/new-corpus/*\n!/worker/fuzzer/new-corpus/.placeholder\n\n## Others.\n/coverage\n/.cache\n# Packaged module.\n*.tgz\n\n## Text editors' config files.\n/.zed\n"
  },
  {
    "path": ".npmrc",
    "content": "# Generate package-lock.json.\npackage-lock=true\n# For bad node/npm version to throw actual error.\nengine-strict=true\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n\t\"useTabs\": true,\n\t\"tabWidth\": 2,\n\t\"arrowParens\": \"avoid\",\n\t\"bracketSpacing\": true,\n\t\"semi\": true,\n\t\"singleQuote\": true,\n\t\"trailingComma\": \"es5\",\n\t\"endOfLine\": \"auto\"\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n### NEXT\n\n- Node: Update TypeScript to v6 ([PR #1790](https://github.com/versatica/mediasoup/pull/1790)).\n\n### 3.19.22\n\n- Node: Avoid \"worker died\" event when the Node application is closed via signal without calling `worker.close()` ([PR #1788](https://github.com/versatica/mediasoup/pull/1788)).\n\n### 3.19.21\n\n- Worker: Fix regression in `DirectTransport` when closing a `DataProducer` or `DataConsumer` ([PR #1780](https://github.com/versatica/mediasoup/pull/1780)).\n\n### 3.19.20\n\n- Worker: Add `useBuiltInSctpStack` setting (defaults to `false`) to enable mediasoup built-in SCTP stack ([PR #1777](https://github.com/versatica/mediasoup/pull/1777)).\n\n### 3.19.19\n\n- Worker: Ensure 4-byte alignment for network packet receive buffers and test buffers to avoid undefined behavior ([PR #1756](https://github.com/versatica/mediasoup/pull/1756)).\n- Worker: Update liburing from 2.12-1 to 2.14-1 ([PR #1761](https://github.com/versatica/mediasoup/pull/1761)).\n\n### 3.19.18\n\n- Worker: Improve `Utils::Crypto::GetRandomUInt()` ([PR #1725](https://github.com/versatica/mediasoup/pull/1725)).\n- Convert `WORKER_CLOSE` into a notification ([PR #1729](https://github.com/versatica/mediasoup/pull/1729)).\n- Node tests: Replace `sctp` unmaintained library with `werift-sctp` ([PR #1732](https://github.com/versatica/mediasoup/pull/1732), thanks to @shinyoshiaki for his help with `werift-sctp`).\n- Worker: Require C++20 ([PR #1741](https://github.com/versatica/mediasoup/pull/1741)).\n- Fix \"SCTP failed\" if no DataChannel is created on a Transport with `enableSctp: true` ([PR #1749](https://github.com/versatica/mediasoup/pull/1749)).\n\n### 3.19.17\n\n- `ICE::StunPacket`: Fix wrong memory access in `GetXorMappedAddress()` method ([08c1ec9](https://github.com/versatica/mediasoup/commit/ea464d40ef77247c3ff7acd10e4a0118665fdd14)).\n\n### 3.19.16\n\n- `RTP::ProbationGenerator`: Remove wrong warning log ([PR #1703](https://github.com/versatica/mediasoup/pull/1703)).\n\n### 3.19.15\n\n- `RtpStreamSend`: duplicated packets are discarded ([PR #1683](https://github.com/versatica/mediasoup/pull/1683)).\n- Worker: Update liburing from 2.5-2 to 2.12-1 ([PR #1686](https://github.com/versatica/mediasoup/pull/1686)).\n- Worker: Use the new `RTP::Packet` class ([PR #1689](https://github.com/versatica/mediasoup/pull/1689)).\n- Worker: Use the new `ICE::StunPacket` class ([PR #1697](https://github.com/versatica/mediasoup/pull/1697)).\n- Node: Expose `ortc` functions in `exports` in `package.json` and main module ([PR #1698](https://github.com/versatica/mediasoup/pull/1698)).\n\n### 3.19.14\n\n- Worker: Fix missing system header include, which fails in GCC 15 ([PR #1679](https://github.com/versatica/mediasoup/pull/1679), credits to @upisfree).\n\n### 3.19.13\n\n- `RtxStream`: Don't check if RTP timestamp moved backwards ([PR #1668](https://github.com/versatica/mediasoup/pull/1668), credits to @Lynnworld).\n- Fix RTX packets containing non yet seen RTP packets being discarded ([PR #1653](https://github.com/versatica/mediasoup/pull/1653), credits to @penguinol, @mengbieting and @Lynnworld).\n\n### 3.19.12\n\n- Only look up the RTP packet’s RID extension if the packet doesn’t have MID extension ([PR #1666](https://github.com/versatica/mediasoup/pull/1666)).\n\n### 3.19.11\n\n- Node: Add `workerBin` optional field in `createWorker()` ([PR #1660](https://github.com/versatica/mediasoup/pull/1660)).\n\n### 3.19.10\n\n- Add `jitter` in `Consumer` 'outbound-rtp' stats ([PR #1654](https://github.com/versatica/mediasoup/pull/1654)).\n\n### 3.19.9\n\n- Fix RTCP packets lost in stats ([PR #1651](https://github.com/versatica/mediasoup/pull/1651)).\n\n### 3.19.8\n\n- Fix RTCP cumulative total lost computation ([PR #1650](https://github.com/versatica/mediasoup/pull/1650)).\n\n### 3.19.7\n\n- Bump up Meson from 1.5.0 to 1.9.1 ([PR #1634](https://github.com/versatica/mediasoup/pull/1634)).\n- `SeqManager`: Fix, properly account out of order drops until an input is forwarded ([#1635](https://github.com/versatica/mediasoup/pull/1635)), thanks to @pnts-se-whereby for reporting.\n- `RtpParameters`: Add `msid` optional field ([PR #1634](https://github.com/versatica/mediasoup/pull/1634)).\n\n### 3.19.6\n\n- AV1: Set DependencyDescriptor Header Extension to 'recvonly' but forward it between pipe transports ([#1632](https://github.com/versatica/mediasoup/pull/1632)).\n\n### 3.19.5\n\n- Add custom 'urn:mediasoup:params:rtp-hdrext:packet-id' (mediasoup-packet-id) header extension ([#1631](https://github.com/versatica/mediasoup/pull/1631)).\n\n### 3.19.4\n\n- AV1: Add support for DD extension header forwarding ([#1610](https://github.com/versatica/mediasoup/pull/1610)).\n- DependencyDescriptor: Update listener on RtpPacket clone ([#1618](https://github.com/versatica/mediasoup/pull/1618)).\n\n### 3.19.3\n\n- CI: Remove `macos-13` hosts.\n- VP8: Fix keyframe detection if \"extended\" bit is not set ([PR #1612](https://github.com/versatica/mediasoup/pull/1612), credits to @nifigase).\n- CI: Remove `node-20` GitHub actions.\n- Require Node.js >= 22 ([PR #1614](https://github.com/versatica/mediasoup/pull/1614)).\n\n### 3.19.2\n\n- `IceServer`: Fix active tuple selection when in \"completed\" state ([PR #1608](https://github.com/versatica/mediasoup/pull/1608), credits to @pangsimon).\n\n### 3.19.1\n\n- Worker: Fix retransmissions, set proper marker bit ([PR #1606](https://github.com/versatica/mediasoup/pull/1606)).\n\n### 3.19.0\n\n- Node: Improve worker binary location detection ([PR #1603](https://github.com/versatica/mediasoup/pull/1603)).\n- `router.pipeToRouter()` can now connect two `Routers` in the same `Worker` if `keepId` is set to `false` ([PR #1604](https://github.com/versatica/mediasoup/pull/1604)).\n\n### 3.18.1\n\n- `TransportTuple`: Generate hash based not only on remote IP:port but also on local IP:port ([PR #1586](https://github.com/versatica/mediasoup/pull/1586)).\n- `IceServer`: Only update selected tuple if the new Binding request has ICE renomination ([PR #1587](https://github.com/versatica/mediasoup/pull/1587), credits to @pangsimon).\n- Fix installation in paths with spaces ([PR #1596](https://github.com/versatica/mediasoup/pull/1596), thanks to @ShuzhaoFeng for reporting and helping with this issue).\n\n### 3.18.0\n\n- Node: Make `RtpCodecCapability.preferredPayloadType` mandatory and add `RouterRtpCodecCapability` type (in which `preferredPayloadType` is optional) and `RouterRtpCapabilities` type ([PR #1584](https://github.com/versatica/mediasoup/pull/1584)).\n\n### 3.17.1\n\n- `WebRtcServer`: Remove the limit of 8 `listenInfos`.\n\n### 3.17.0\n\n- Worker: Update Meson subprojects ([PR #1582](https://github.com/versatica/mediasoup/pull/1582)).\n- `TransportListenInfo`: Add `exposeInternalIp` which, if set to `true` and `announcedAddress` is set, exposes an additional ICE candidate in `WebRtcTransport` whose IP is `listenInfo.ip` rather than `listenInfo.announcedAddress` ([PR #1583](https://github.com/versatica/mediasoup/pull/1583)).\n\n### 3.16.8\n\n- Node: Fix `PipeConsumerOptions` TypeScript type (make `ConsumerAppData` TS argument optional) ([PR #1581](https://github.com/versatica/mediasoup/pull/1581)).\n\n### 3.16.7\n\n- `Router`: Add `updateMediaCodecs()` method to dynamically change Router's RTP capabilities ([PR #1571](https://github.com/versatica/mediasoup/pull/1571)).\n- `RtpStream`: Update `maxPacketTs` if RTP timestamp moved backwards despite in-order RTP sequence number ([PR #1574](https://github.com/versatica/mediasoup/pull/1574), credits to @oppolixiang).\n- `RtpStream`: Ignore padding only RTP packets in RTP data counters ([PR #1580](https://github.com/versatica/mediasoup/pull/1580), thanks to @quanli168 for reporting the issue).\n\n### 3.16.6\n\n- Remove H265 codec and deprecated frame-marking RTP extension ([PR #1564](https://github.com/versatica/mediasoup/pull/1564)).\n- `SimulcastConsumer`: Fix selecting spatial layer higher than preferred one ([PR #1565](https://github.com/versatica/mediasoup/pull/1565)).\n- Remove H264-SVC codec ([PR #1568](https://github.com/versatica/mediasoup/pull/1568)).\n- `RateCalculator`: Fix crash due to buffer overflow and avoid time overflow ([PR #1570](https://github.com/versatica/mediasoup/pull/1570)).\n\n### 3.16.5\n\n- `Consumer` classes: Really fix target layer retransmission buffer ([PR #1558](https://github.com/versatica/mediasoup/pull/1558)).\n\n### 3.16.4\n\n- `Consumer` classes: Disable target layer retransmission buffer until [issue #1554] (https://github.com/versatica/mediasoup/issues/1554) is really fixed.\n\n### 3.16.3\n\n- `Consumer` classes: Fix target layer retransmission buffer ([PR #1555](https://github.com/versatica/mediasoup/pull/1555)).\n\n### 3.16.2\n\n- `Consumer` classes: Disable target layer retransmission buffer until [issue #1554] (https://github.com/versatica/mediasoup/issues/1554) is fixed.\n\n### 3.16.1\n\n- libuv: Update to v1.51.0 ([PR #1543](https://github.com/versatica/mediasoup/pull/1543)).\n- libsrtp: Update to v3.0.0-beta version in our fork ([PR #1544](https://github.com/versatica/mediasoup/pull/1544)).\n- `Consumer` classes: Only drop packets in RTP sequence manager when they belong to current spatial layer ([PR #1549](https://github.com/versatica/mediasoup/pull/1549)).\n- `Consumer` classes: Add target layer retransmission buffer to avoid PLIs/FIRs when RTP packets containing a key frame arrive out of order ([PR #1550](https://github.com/versatica/mediasoup/pull/1550)).\n\n### 3.16.0\n\n- Node: Make `worker.close()` close the worker process by sending a `WORKER_CLOSE` request through the channel instead of by sending a SIGINT signal ([PR #1534](https://github.com/versatica/mediasoup/pull/1534)).\n- Worker: Add initial AV1 codec support ([PR #1508](https://github.com/versatica/mediasoup/pull/1508)).\n- `SvcConsumer`: Fix K-SVC bitrate in `IncreaseLayer()` method ([PR #1535](https://github.com/versatica/mediasoup/pull/1535) by @vpalmisano).\n- Node: Require Node >= 20 (drop support for Node 18) ([PR #1536](https://github.com/versatica/mediasoup/pull/1536)).\n\n### 3.15.8\n\n- Worker: Fix encode retransmitted packets with the corresponding data ([PR #1527](https://github.com/versatica/mediasoup/pull/1527)).\n\n### 3.15.7\n\n- CI: Remove redundant hosts `macos-14` and `windows-2022` from `mediasoup-worker-prebuild` job ([PR #1506](https://github.com/versatica/mediasoup/pull/1506)).\n- Node: Modernize code ([PR #1513](https://github.com/versatica/mediasoup/pull/1513)).\n- Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` ([PR #1516](https://github.com/versatica/mediasoup/pull/1516)).\n\n### 3.15.6\n\n- CI: Remove deprecated `ubuntu-20.04` host and add `windows-2025`, `ubuntu-22.04-arm` and `ubuntu-24.04-arm` hosts ([PR #1500](https://github.com/versatica/mediasoup/pull/1500)).\n\n### 3.15.5\n\n- `Consumer`: Fix sequence number gap ([PR #1494](https://github.com/versatica/mediasoup/pull/1494)).\n- Fix VP9 out of order packets forwarding ([PR #1486](https://github.com/versatica/mediasoup/pull/1486) by @vpalmisano).\n\n### 3.15.4\n\n- Worker: Drop VP8 packets with a higher temporal layer than the current one ([PR #1009](https://github.com/versatica/mediasoup/pull/1009)).\n- Fix the problem of the TCC package being omitted from being sent ([PR #1492](https://github.com/versatica/mediasoup/pull/1492) by @penguinol).\n\n### 3.15.3\n\n- Node: Expose `Index` interface in `types.indexTypes` or via `import { Index as MediasoupIndex } from 'mediasoup/lib/indexTypes'` ([PR #1485](https://github.com/versatica/mediasoup/pull/1485)).\n\n### 3.15.2\n\n- Worker: Fix crash when using colliding `portRange` values in different transports ([PR #1469](https://github.com/versatica/mediasoup/pull/1469)).\n\n### 3.15.1\n\n- Expose `extras` namespace which exports `EnhancedEventEmitter` and `enhancedOnce()` for now ([PR #1464](https://github.com/versatica/mediasoup/pull/1464)).\n\n### 3.15.0\n\n- Node: Add TypeScript interfaces for all exported classes ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)).\n- Node: Add new `transport.type` getter than returns `'webrtc' | 'plain' | 'pipe' | 'direct'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)).\n- Node: Add new `rtpObserver.type` getter than returns `'activespeaker' | 'audiolevel'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)).\n\n### 3.14.16\n\n- `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears ([PR #1459](https://github.com/versatica/mediasoup/pull/1459) by @Lynnworld).\n\n### 3.14.15\n\n- Update worker abseil-cpp dependency to 20240722.0 LTS (fixes compilation for FreeBSD systems) ([PR #1457](https://github.com/versatica/mediasoup/pull/1457), credits to @garrettboone).\n\n### 3.14.14\n\n- Sign self generated DTLS certificate with SHA256 ([PR #1450](https://github.com/versatica/mediasoup/pull/1450)).\n- Node: Fix `mediasoup.types` exported types are empty ([PR #1453](https://github.com/versatica/mediasoup/pull/1453)).\n\n### 3.14.13\n\n- Node: Fix regression in exported `mediasoup.types` (classes are now exported as classes instead of types).\n\n### 3.14.12\n\n- Worker: Fix `io_uring` support detection ([PR #1445](https://github.com/versatica/mediasoup/pull/1445)).\n- Mitigate libsrtp wraparound with loss decryption failure ([PR #1438](https://github.com/versatica/mediasoup/pull/1438)).\n- Node: New `setLogEventListeners()` utility to get log events ([PR #1448](https://github.com/versatica/mediasoup/pull/1448)).\n\n### 3.14.11\n\n- Worker: Fix `disableLiburing` option in `WorkerSettings` ([PR #1444](https://github.com/versatica/mediasoup/pull/1444)).\n\n### 3.14.10\n\n- CI: Support Node 22 ([PR #1434](https://github.com/versatica/mediasoup/pull/1434)).\n- Update ESLint to version 9 ([PR #1435](https://github.com/versatica/mediasoup/pull/1435)).\n- Worker: Add `disableLiburing` boolean option (`false` by default) to disable `io_uring` even if it's supported by the prebuilt `mediasoup-worker` and by current host ([PR #1442](https://github.com/versatica/mediasoup/pull/1442)).\n\n### 3.14.9\n\n- Worker: Test, fix buffer overflow ([PR #1419](https://github.com/versatica/mediasoup/pull/1419)).\n- Bump up Meson from 1.3.0 to 1.5.0 ([PR #1424](https://github.com/versatica/mediasoup/pull/1424)).\n- Node: Export new `WorkerObserver`, `ProducerObserver`, etc. TypeScript types ([PR #1430](https://github.com/versatica/mediasoup/pull/1430)).\n- Fix frozen video in simulcast due to wrong dropping of padding only packets ([PR #1431](https://github.com/versatica/mediasoup/pull/1431), thanks to @quanli168).\n\n### 3.14.8\n\n- Add support for 'playout-delay' RTP extension ([PR #1412](https://github.com/versatica/mediasoup/pull/1412) by @DavidNegro).\n\n### 3.14.7\n\n- `SimulcastConsumer`: Fix increase layer when current layer has not receive SR ([PR #1098](https://github.com/versatica/mediasoup/pull/1098) by @penguinol).\n- Ignore RTP packets with empty payload ([PR #1403](https://github.com/versatica/mediasoup/pull/1403), credits to @ggarber).\n\n### 3.14.6\n\n- Worker: Fix potential double free when ICE consent check fails ([PR #1393](https://github.com/versatica/mediasoup/pull/1393)).\n\n### 3.14.5\n\n- Worker: Fix memory leak when using `WebRtcServer` with TCP enabled ([PR #1389](https://github.com/versatica/mediasoup/pull/1389)).\n- Worker: Fix crash when closing `WebRtcServer` with active `WebRtcTransports` ([PR #1390](https://github.com/versatica/mediasoup/pull/1390)).\n\n### 3.14.4\n\n- Worker: Fix crash. `RtcpFeedback` parameter is optional ([PR #1387](https://github.com/versatica/mediasoup/pull/1387), credits to @Lynnworld).\n\n### 3.14.3\n\n- Worker: Fix possible value overflow in `FeedbackRtpTransport.cpp` ([PR #1386](https://github.com/versatica/mediasoup/pull/1386), credits to @Lynnworld).\n\n### 3.14.2\n\n- Update worker subprojects ([PR #1376](https://github.com/versatica/mediasoup/pull/1376)).\n- OPUS: Fix DTX detection ([PR #1357](https://github.com/versatica/mediasoup/pull/1357)).\n- Worker: Fix sending callback leaks ([PR #1383](https://github.com/versatica/mediasoup/pull/1383), credits to @Lynnworld).\n\n### 3.14.1\n\n- Node: Bring transport `rtpPacketLossReceived` and `rtpPacketLossSent` stats back ([PR #1371](https://github.com/versatica/mediasoup/pull/1371)).\n\n### 3.14.0\n\n- `TransportListenInfo`: Add `portRange` (deprecate worker port range) ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)).\n- Require Node.js >= 18 ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)).\n\n### 3.13.24\n\n- Node: Fix missing `bitrateByLayer` field in stats of `RtpRecvStream` in Node ([PR #1349](https://github.com/versatica/mediasoup/pull/1349)).\n- Update worker dependency libuv to 1.48.0.\n- Update worker FlatBuffers to 24.3.6-1 (fix cannot set temporal layer 0) ([PR #1348](https://github.com/versatica/mediasoup/pull/1348)).\n\n### 3.13.23\n\n- Fix DTLS packets do not honor configured DTLS MTU (attempt 3) ([PR #1345](https://github.com/versatica/mediasoup/pull/1345)).\n\n### 3.13.22\n\n- Fix wrong publication of mediasoup NPM 3.13.21.\n\n### 3.13.21\n\n- Revert ([PR #1156](https://github.com/versatica/mediasoup/pull/1156)) \"Make DTLS fragment stay within MTU size range\" because it causes a memory leak ([PR #1342](https://github.com/versatica/mediasoup/pull/1342)).\n\n### 3.13.20\n\n- Add server side ICE consent checks to detect silent WebRTC disconnections ([PR #1332](https://github.com/versatica/mediasoup/pull/1332)).\n- Fix regression (crash) in transport-cc feedback generation ([PR #1339](https://github.com/versatica/mediasoup/pull/1339)).\n\n### 3.13.19\n\n- Node: Fix `router.createWebRtcTransport()` with `listenIps` ([PR #1330](https://github.com/versatica/mediasoup/pull/1330)).\n\n### 3.13.18\n\n- Make transport-cc feedback work similarly to libwebrtc ([PR #1088](https://github.com/versatica/mediasoup/pull/1088) by @penguinol).\n- `TransportListenInfo`: \"announced ip\" can also be a hostname ([PR #1322](https://github.com/versatica/mediasoup/pull/1322)).\n- `TransportListenInfo`: Rename \"announced ip\" to \"announced address\" ([PR #1324](https://github.com/versatica/mediasoup/pull/1324)).\n- CI: Add `macos-14`.\n\n### 3.13.17\n\n- Fix prebuilt worker download ([PR #1319](https://github.com/versatica/mediasoup/pull/1319) by @brynrichards).\n- libsrtp: Update to v3.0-alpha version in our fork.\n\n### 3.13.16\n\n- Node: Add new `worker.on('subprocessclose')` event ([PR #1307](https://github.com/versatica/mediasoup/pull/1307)).\n\n### 3.13.15\n\n- Add worker prebuild binary for Linux kernel 6 ([PR #1300](https://github.com/versatica/mediasoup/pull/1300)).\n\n### 3.13.14\n\n- Avoid modification of user input data ([PR #1285](https://github.com/versatica/mediasoup/pull/1285)).\n- `TransportListenInfo`: Add transport socket flags ([PR #1291](https://github.com/versatica/mediasoup/pull/1291)).\n  - Note that `flags.ipv6Only` is `false` by default.\n- `TransportListenInfo`: Ignore given socket flags if not suitable for given IP family or transport ([PR #1294](https://github.com/versatica/mediasoup/pull/1294)).\n- Meson: Remove `-Db_pie=true -Db_staticpic=true` args ([PR #1293](https://github.com/versatica/mediasoup/pull/1293)).\n- Add RTCP Sender Report trace event ([PR #1267](https://github.com/versatica/mediasoup/pull/1267) by @GithubUser8080).\n\n### 3.13.13\n\n- Worker: Do not use references for async callbacks ([PR #1274](https://github.com/versatica/mediasoup/pull/1274)).\n- liburing: Enable zero copy ([PR #1273](https://github.com/versatica/mediasoup/pull/1273)).\n- Fix build on musl based systems (such as Alpine Linux) ([PR #1279](https://github.com/versatica/mediasoup/pull/1279)).\n\n### 3.13.12\n\n- Worker: Disable `RtcLogger` usage if not enabled ([PR #1264](https://github.com/versatica/mediasoup/pull/1264)).\n- npm installation: Don't require Python if valid worker prebuilt binary is fetched ([PR #1265](https://github.com/versatica/mediasoup/pull/1265)).\n- Update h264-profile-level-id NPM dependency to 1.1.0.\n\n### 3.13.11\n\n- liburing: Avoid extra memcpy on RTP ([PR #1258](https://github.com/versatica/mediasoup/pull/1258)).\n- libsrtp: Use our own fork with performance gain ([PR #1260](https://github.com/versatica/mediasoup/pull/1260)).\n- `DataConsumer`: Add `addSubchannel()` and `removeSubchannel()` methods ([PR #1263](https://github.com/versatica/mediasoup/pull/1263)).\n- Fix Rust `DataConsumer` ([PR #1262](https://github.com/versatica/mediasoup/pull/1262)).\n\n### 3.13.10\n\n- `tasks.py`: Always include `--no-user` in `pip install` commands to avoid the \"can not combine --user and --target\" error in Windows ([PR #1257](https://github.com/versatica/mediasoup/pull/1257)).\n\n### 3.13.9\n\n- Update worker liburing dependency to 2.4-2 ([PR #1254](https://github.com/versatica/mediasoup/pull/1254)).\n- liburing: Enable by default ([PR 1255](https://github.com/versatica/mediasoup/pull/1255)).\n\n### 3.13.8\n\n- liburing: Enable liburing usage for SCTP data delivery ([PR 1249](https://github.com/versatica/mediasoup/pull/1249)).\n- liburing: Disable by default ([PR 1253](https://github.com/versatica/mediasoup/pull/1253)).\n\n### 3.13.7\n\n- Update worker dependencies ([PR #1201](https://github.com/versatica/mediasoup/pull/1201)):\n  - abseil-cpp 20230802.0-2\n  - libuv 1.47.0-1\n  - OpenSSL 3.0.8-2\n  - usrsctp snapshot ebb18adac6501bad4501b1f6dccb67a1c85cc299\n- Enable `liburing` usage for Linux (kernel versions >= 6) ([PR #1218](https://github.com/versatica/mediasoup/pull/1218)).\n\n### 3.13.6\n\n- Replace make + Makefile with Python Invoke library + tasks.py (also fix installation under path with whitespaces) ([PR #1239](https://github.com/versatica/mediasoup/pull/1239)).\n\n### 3.13.5\n\n- Fix RTCP SDES packet size calculation ([PR #1236](https://github.com/versatica/mediasoup/pull/1236) based on PR [PR #1234](https://github.com/versatica/mediasoup/pull/1234) by @ybybwdwd).\n- RTCP Compound Packet: Use a single DLRR report to hold all ssrc info sub-blocks ([PR #1237](https://github.com/versatica/mediasoup/pull/1237)).\n\n### 3.13.4\n\n- Fix RTCP DLRR (Delay Since Last Receiver Report) block parsing ([PR #1234](https://github.com/versatica/mediasoup/pull/1234)).\n\n### 3.13.3\n\n- Node: Fix issue when 'pause'/'resume' events are not emitted ([PR #1231](https://github.com/versatica/mediasoup/pull/1231) by @douglaseel).\n\n### 3.13.2\n\n- FBS: `LayersChangeNotification` body must be optional (fixes a crash) ([PR #1227](https://github.com/versatica/mediasoup/pull/1227)).\n\n### 3.13.1\n\n- Node: Extract version from `package.json` using `require()` ([PR #1217](https://github.com/versatica/mediasoup/pull/1217) by @arcinston).\n\n### 3.13.0\n\n- Switch from JSON based messages to FlatBuffers ([PR #1064](https://github.com/versatica/mediasoup/pull/1064)).\n- Add `TransportListenInfo` in all transports and send/recv buffer size options ([PR #1084](https://github.com/versatica/mediasoup/pull/1084)).\n- Add optional `rtcpListenInfo` in `PlainTransportOptions` ([PR #1099](https://github.com/versatica/mediasoup/pull/1099)).\n- Add pause/resume API in `DataProducer` and `DataConsumer` ([PR #1104](https://github.com/versatica/mediasoup/pull/1104)).\n- DataChannel subchannels feature ([PR #1152](https://github.com/versatica/mediasoup/pull/1152)).\n- Worker: Make DTLS fragment stay within MTU size range ([PR #1156](https://github.com/versatica/mediasoup/pull/1156), based on [PR #1143](https://github.com/versatica/mediasoup/pull/1143) by @vpnts-se).\n\n### 3.12.16\n\n- Fix `IceServer` crash when client uses ICE renomination ([PR #1182](https://github.com/versatica/mediasoup/pull/1182)).\n\n### 3.12.15\n\n- Fix NPM \"postinstall\" task in Windows ([PR #1187](https://github.com/versatica/mediasoup/pull/1187)).\n\n### 3.12.14\n\n- CI: Use Node.js version 20 ([PR #1177](https://github.com/versatica/mediasoup/pull/1177)).\n- Use given `PYTHON` environment variable (if given) when running `worker/scripts/getmake.py` ([PR #1186](https://github.com/versatica/mediasoup/pull/1186)).\n\n### 3.12.13\n\n- Bump up Meson from 1.1.0 to 1.2.1 (fixes Xcode 15 build issues) ([PR #1163](https://github.com/versatica/mediasoup/pull/1163) by @arcinston).\n\n### 3.12.12\n\n- Support C++20 ([PR #1150](https://github.com/versatica/mediasoup/pull/1150) by @o-u-p).\n\n### 3.12.11\n\n- Google Transport Feedback: Read Reference Time field as 24bits signed as per spec ([PR #1145](https://github.com/versatica/mediasoup/pull/1145)).\n\n### 3.12.10\n\n- Node: Rename `WebRtcTransport.webRtcServerClosed()` to `listenServerClosed()` ([PR #1141](https://github.com/versatica/mediasoup/pull/1141) by @piranna).\n\n### 3.12.9\n\n- Fix RTCP SDES ([PR #1139](https://github.com/versatica/mediasoup/pull/1139)).\n\n### 3.12.8\n\n- Export `workerBin` absolute path ([PR #1123](https://github.com/versatica/mediasoup/pull/1123)).\n\n### 3.12.7\n\n- `SimulcastConsumer`: Fix lack of \"layerschange\" event when all streams in the producer die ([PR #1122](https://github.com/versatica/mediasoup/pull/1122)).\n\n### 3.12.6\n\n- Worker: Add `Transport::Destroying()` protected method ([PR #1114](https://github.com/versatica/mediasoup/pull/1114)).\n- `RtpStreamRecv`: Fix jitter calculation ([PR #1117](https://github.com/versatica/mediasoup/pull/1117), thanks to @penguinol).\n- Revert \"Node: make types.ts only export types rather than the entire class/code\" ([PR #1109](https://github.com/versatica/mediasoup/pull/1109)) because it requires `typescript` >= 5 in the apps that import mediasoup and we don't want to be that strict yet.\n\n### 3.12.5\n\n- `DataConsumer`: Fix removed 'bufferedamountlow' notification ([PR #1113](https://github.com/versatica/mediasoup/pull/1113)).\n\n### 3.12.4\n\n- Fix downloaded prebuilt binary check on Windows ([PR #1105](https://github.com/versatica/mediasoup/pull/1105) by @woodfe).\n\n### 3.12.3\n\nMigrate `npm-scripts.js` to `npm-scripts.mjs` (ES Module) ([PR #1093](https://github.com/versatica/mediasoup/pull/1093)).\n\n### 3.12.2\n\n- CI: Use `ubuntu-20.04` to build `mediasoup-worker` prebuilt on Linux ([PR #1092](https://github.com/versatica/mediasoup/pull/1092)).\n\n### 3.12.1\n\n- `mediasoup-worker` prebuild: Fallback to local building if fetched binary doesn't run on current host ([PR #1090](https://github.com/versatica/mediasoup/pull/1090)).\n\n### 3.12.0\n\n- Automate and publish prebuilt `mediasoup-worker` binaries ([PR #1087](https://github.com/versatica/mediasoup/pull/1087), thanks to @barlock for his work in ([PR #1083](https://github.com/versatica/mediasoup/pull/1083)).\n\n### 3.11.26\n\n- Worker: Fix NACK timer and avoid negative RTT ([PR #1082](https://github.com/versatica/mediasoup/pull/1082), thanks to @o-u-p for his work in ([PR #1076](https://github.com/versatica/mediasoup/pull/1076)).\n\n### 3.11.25\n\n- Worker: Require C++17, Meson >= 1.1.0 and update subprojects ([PR #1081](https://github.com/versatica/mediasoup/pull/1081)).\n\n### 3.11.24\n\n- `SeqManager`: Fix performance regression ([PR #1068](https://github.com/versatica/mediasoup/pull/1068), thanks to @vpalmisano for properly reporting).\n\n### 3.11.23\n\n- Node: Fix `appData` for `Transport` and `RtpObserver` parent classes ([PR #1066](https://github.com/versatica/mediasoup/pull/1066)).\n\n### 3.11.22\n\n- `RtpStreamRecv`: Only perform RTP inactivity check on simulcast streams ([PR #1061](https://github.com/versatica/mediasoup/pull/1061)).\n- `SeqManager`: Properly remove old dropped entries ([PR #1054](https://github.com/versatica/mediasoup/pull/1054)).\n- libwebrtc: Upgrade trendline estimator to improve low bandwidth conditions ([PR #1055](https://github.com/versatica/mediasoup/pull/1055) by @ggarber).\n- libwebrtc: Fix bandwidth probation dead state ([PR #1031](https://github.com/versatica/mediasoup/pull/1031) by @vpalmisano).\n\n### 3.11.21\n\n- Fix check division by zero in transport congestion control ([PR #1049](https://github.com/versatica/mediasoup/pull/1049) by @ggarber).\n- Fix lost pending statuses in transport CC feedback ([PR #1050](https://github.com/versatica/mediasoup/pull/1050) by @ggarber).\n\n### 3.11.20\n\n- `RtpStreamSend`: Reset RTP retransmission buffer upon RTP sequence number reset ([PR #1041](https://github.com/versatica/mediasoup/pull/1041)).\n- `RtpRetransmissionBuffer`: Handle corner case in which received packet has lower seq than newest packet in the buffer but higher timestamp ([PR #1044](https://github.com/versatica/mediasoup/pull/1044)).\n- `SeqManager`: Fix crash and add fuzzer ([PR #1045](https://github.com/versatica/mediasoup/pull/1045)).\n- Node: Make `appData` TS typed and writable ([PR #1046](https://github.com/versatica/mediasoup/pull/1046), credits to @mango-martin).\n\n### 3.11.19\n\n- `SvcConsumer`: Properly handle VP9 K-SVC bandwidth allocation ([PR #1036](https://github.com/versatica/mediasoup/pull/1036) by @vpalmisano).\n\n### 3.11.18\n\n- `RtpRetransmissionBuffer`: Consider the case of packet with newest timestamp but \"old\" seq number ([PR #1039](https://github.com/versatica/mediasoup/pull/1039)).\n\n### 3.11.17\n\n- Add `transport.setMinOutgoingBitrate()` method ([PR #1038](https://github.com/versatica/mediasoup/pull/1038), credits to @ jcague).\n- `RTC::RetransmissionBuffer`: Increase `RetransmissionBufferMaxItems` from 2500 to 5000.\n\n### 3.11.16\n\n- Fix `SeqManager`: Properly consider previous cycle dropped inputs ([PR #1032](https://github.com/versatica/mediasoup/pull/1032)).\n- `RtpRetransmissionBuffer`: Get rid of not necessary `startSeq` private member ([PR #1029](https://github.com/versatica/mediasoup/pull/1029)).\n- Node: Upgrade TypeScript to 5.0.2.\n\n### 3.11.15\n\n- `RtpRetransmissionBuffer`: Fix crash and add fuzzer ([PR #1028](https://github.com/versatica/mediasoup/pull/1028)).\n\n### 3.11.14\n\n- Refactor RTP retransmission buffer in a separate and testable `RTC::RetransmissionBuffer` class ([PR #1023](https://github.com/versatica/mediasoup/pull/1023)).\n\n### 3.11.13\n\n- `AudioLevelObserver`: Use multimap rather than map to avoid conflict if various Producers generate same audio level ([PR #1021](https://github.com/versatica/mediasoup/pull/1021), issue reported by @buptlsp).\n\n### 3.11.12\n\n- Fix jitter calculation ([PR #1019](https://github.com/versatica/mediasoup/pull/1019), credits to @alexciarlillo and @snnz).\n\n### 3.11.11\n\n- Add support for RTCP NACK in OPUS ([PR #1015](https://github.com/versatica/mediasoup/pull/1015)).\n\n### 3.11.10\n\n- Download and use MSYS/make locally for Windows postinstall ([PR #792](https://github.com/versatica/mediasoup/pull/792) by @snnz).\n\n### 3.11.9\n\n- Allow simulcast with a single encoding (and N temporal layers) ([PR #1013](https://github.com/versatica/mediasoup/pull/1013)).\n- Update libsrtp to 2.5.0.\n\n### 3.11.8\n\n- `SimulcastConsumer::GetDesiredBitrate()`: Choose the highest bitrate among all Producer streams ([PR #992](https://github.com/versatica/mediasoup/pull/992)).\n- `SimulcastConsumer`: Fix frozen video when syncing keyframe is discarded due to too high RTP timestamp extra offset needed ([PR #999](https://github.com/versatica/mediasoup/pull/999), thanks to @satoren for properly reporting the issue and helping with the solution).\n\n### 3.11.7\n\n- libwebrtc: Fix crash due to invalid `arrival_time` value ([PR #985](https://github.com/versatica/mediasoup/pull/985) by @ggarber).\n- libwebrtc: Replace `MS_ASSERT()` with `MS_ERROR()` ([PR #988](https://github.com/versatica/mediasoup/pull/988)).\n\n### 3.11.6\n\n- Fix wrong `PictureID` rolling over in VP9 and VP8 ([PR #984](https://github.com/versatica/mediasoup/pull/984) by @jcague).\n\n### 3.11.5\n\n- Require Node.js >= 16 ([PR #973](https://github.com/versatica/mediasoup/pull/973)).\n- Fix wrong `Consumer` bandwidth estimation under `Producer` packet loss ([PR #962](https://github.com/versatica/mediasoup/pull/962) by @ggarber).\n\n### 3.11.4\n\n- Node: Migrate tests to TypeScript ([PR #958](https://github.com/versatica/mediasoup/pull/958)).\n- Node: Remove compiled JavaScript from repository and compile TypeScript code on NPM `prepare` script on demand when installed via git ([PR #954](https://github.com/versatica/mediasoup/pull/954)).\n- Worker: Add `RTC::Shared` singleton for RTC entities ([PR #953](https://github.com/versatica/mediasoup/pull/953)).\n- Update OpenSSL to 3.0.7.\n\n### 3.11.3\n\n- `ChannelMessageHandlers`: Make `RegisterHandler()` not remove the existing handler if another one with same `id` is given ([PR #952](https://github.com/versatica/mediasoup/pull/952)).\n\n### 3.11.2\n\n- Fix installation issue in Linux due to a bug in ninja latest version 1.11.1 ([PR #948](https://github.com/versatica/mediasoup/pull/948)).\n\n### 3.11.1\n\n- `ActiveSpeakerObserver`: Revert 'dominantspeaker' event changes in [PR #941](https://github.com/versatica/mediasoup/pull/941) to avoid breaking changes ([PR #947](https://github.com/versatica/mediasoup/pull/947)).\n\n### 3.11.0\n\n- `Transport`: Remove duplicate call to method ([PR #931](https://github.com/versatica/mediasoup/pull/931)).\n- RTCP: Adjust maximum compound packet size ([PR #934](https://github.com/versatica/mediasoup/pull/934)).\n- `DataConsumer`: Fix `bufferedAmount` type to be a number again ([PR #936](https://github.com/versatica/mediasoup/pull/936)).\n- `ActiveSpeakerObserver`: Fix 'dominantspeaker' event by having a single `Producer` as argument rather than an array with a single `Producer` into it ([PR #941](https://github.com/versatica/mediasoup/pull/941)).\n- `ActiveSpeakerObserver`: Fix memory leak ([PR #942](https://github.com/versatica/mediasoup/pull/942)).\n- Fix some libwebrtc issues ([PR #944](https://github.com/versatica/mediasoup/pull/944)).\n- Tests: Normalize hexadecimal data representation ([PR #945](https://github.com/versatica/mediasoup/pull/945)).\n- `SctpAssociation`: Fix memory violation ([PR #943](https://github.com/versatica/mediasoup/pull/943)).\n\n### 3.10.12\n\n- Fix worker crash due to `std::out_of_range` exception ([PR #933](https://github.com/versatica/mediasoup/pull/933)).\n\n### 3.10.11\n\n- RTCP: Fix trailing space needed by `srtp_protect_rtcp()` ([PR #929](https://github.com/versatica/mediasoup/pull/929)).\n\n### 3.10.10\n\n- Fix the JSON serialization for the payload channel `rtp` event ([PR #926](https://github.com/versatica/mediasoup/pull/926) by @mhammo).\n\n### 3.10.9\n\n- RTCP enhancements ([PR #914](https://github.com/versatica/mediasoup/pull/914)).\n\n### 3.10.8\n\n- `Consumer`: use a bitset instead of a set for supported payload types ([PR #919](https://github.com/versatica/mediasoup/pull/919)).\n- RtpPacket: optimize UpdateMid() ([PR #920](https://github.com/versatica/mediasoup/pull/920)).\n- Little optimizations and modernization ([PR #916](https://github.com/versatica/mediasoup/pull/916)).\n- Fix SIGSEGV at `RTC::WebRtcTransport::OnIceServerTupleRemoved()` ([PR #915](https://github.com/versatica/mediasoup/pull/915), credits to @ybybwdwd).\n- `WebRtcServer`: Make `port` optional (if not given, a random available port from the `Worker` port range is used) ([PR #908](https://github.com/versatica/mediasoup/pull/908) by @satoren).\n\n### 3.10.7\n\n- Forward `abs-capture-time` RTP extension also for audio packets ([PR #911](https://github.com/versatica/mediasoup/pull/911)).\n\n### 3.10.6\n\n- Node: Define TypeScript types for `internal` and `data` objects ([PR #891](https://github.com/versatica/mediasoup/pull/891)).\n- `Channel` and `PayloadChannel`: Refactor `internal` with a single `handlerId` ([PR #889](https://github.com/versatica/mediasoup/pull/889)).\n- `Channel` and `PayloadChannel`: Optimize message format and JSON generation ([PR #893](https://github.com/versatica/mediasoup/pull/893)).\n- New C++ `ChannelMessageHandlers` class ([PR #894](https://github.com/versatica/mediasoup/pull/894)).\n- Fix Rust support after recent changes ([PR #898](https://github.com/versatica/mediasoup/pull/898)).\n- Modify `FeedbackRtpTransport` and tests to be compliant with latest libwebrtc code, make reference time to be unsigned ([PR #899](https://github.com/versatica/mediasoup/pull/899) by @penguinol and @sarumjanuch).\n\n### 3.10.5\n\n- `RtpStreamSend`: Do not store too old RTP packets ([PR #885](https://github.com/versatica/mediasoup/pull/885)).\n- Log error details in channel socket. ([PR #875](https://github.com/versatica/mediasoup/pull/875) by @mstyura).\n\n### 3.10.4\n\n- Do not clone RTP packets if not needed ([PR #850](https://github.com/versatica/mediasoup/pull/850)).\n- Fix DTLS related crash ([PR #867](https://github.com/versatica/mediasoup/pull/867)).\n\n### 3.10.3\n\n- `SimpleConsumer`: Fix. Only process Opus codec ([PR #865](https://github.com/versatica/mediasoup/pull/865)).\n- TypeScript: Improve `WebRtcTransportOptions` type to not allow `webRtcServer` and `listenIps`options at the same time ([PR #852](https://github.com/versatica/mediasoup/pull/852)).\n\n### 3.10.2\n\n- Fix release contents by including `meson_options.txt` ([PR #863](https://github.com/versatica/mediasoup/pull/863)).\n\n### 3.10.1\n\n- `RtpStreamSend`: Memory optimizations ([PR #840](https://github.com/versatica/mediasoup/pull/840)). Extracted from #675, by @nazar-pc.\n- `SimpleConsumer`: Opus DTX ignore capabilities ([PR #846](https://github.com/versatica/mediasoup/pull/846)).\n- Update `libuv` to 1.44.1: Fixes `libuv` build ([PR #857](https://github.com/versatica/mediasoup/pull/857)).\n\n### 3.10.0\n\n- `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port ([PR #834](https://github.com/versatica/mediasoup/pull/834)).\n- More SRTP crypto suites ([PR #837](https://github.com/versatica/mediasoup/pull/837)).\n- Improve `EnhancedEventEmitter` ([PR #836](https://github.com/versatica/mediasoup/pull/836)).\n- `TransportCongestionControlClient`: Allow setting max outgoing bitrate before `tccClient` is created ([PR #833](https://github.com/versatica/mediasoup/pull/833)).\n- Update TypeScript version.\n\n### 3.9.17\n\n- `RateCalculator`: Fix old buffer items cleanup ([PR #830](https://github.com/versatica/mediasoup/pull/830) by @dsdolzhenko).\n- Update TypeScript version.\n\n### 3.9.16\n\n- `SimulcastConsumer`: Fix spatial layer switch with unordered packets ([PR #823](https://github.com/versatica/mediasoup/pull/823) by @jcague).\n- Update TypeScript version.\n\n### 3.9.15\n\n- `RateCalculator`: Revert Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko).\n\n### 3.9.14\n\n- `NackGenerator`: Add a configurable delay before sending NACK ([PR #827](https://github.com/versatica/mediasoup/pull/827), credits to @penguinol).\n- `SimulcastConsumer`: Fix a race condition in SimulcastConsumer ([PR #825](https://github.com/versatica/mediasoup/pull/825) by @dsdolzhenko).\n- Add support for H264 SVC (#798 by @prtmD).\n- `RtpStreamSend`: Support receive RTCP-XR RRT and send RTCP-XR DLRR ([PR #781](https://github.com/versatica/mediasoup/pull/781) by @aggresss).\n- `RateCalculator`: Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko).\n- `DirectTransport`: Create a buffer to process RTP packets ([PR #730](https://github.com/versatica/mediasoup/pull/730) by @rtctt).\n- Node: Improve `appData` TypeScript syntax and initialization.\n- Allow setting max outgoing bitrate below the initial value ([PR #826](https://github.com/versatica/mediasoup/pull/826) by @ggarber).\n- Update TypeScript version.\n\n### 3.9.13\n\n- `VP8`: Do not discard `TL0PICIDX` from Temporal Layers higher than 0 (PR @817 by @jcague).\n- Update TypeScript version.\n\n### 3.9.12\n\n- `DtlsTransport`: Make DTLS negotiation run immediately ([PR #815](https://github.com/versatica/mediasoup/pull/815)).\n- Update TypeScript version.\n\n### 3.9.11\n\n- Modify `SimulcastConsumer` to keep using layers without good scores ([PR #804](https://github.com/versatica/mediasoup/pull/804) by @ggarber).\n\n### 3.9.10\n\n- Update worker dependencies:\n  - OpenSSL 3.0.2.\n  - abseil-cpp 20211102.0.\n  - nlohmann_json 3.10.5.\n  - usrsctp snapshot 4e06feb01cadcd127d119486b98a4bd3d64aa1e7.\n  - wingetopt 1.00.\n- Update TypeScript version.\n- Fix RTP marker bit not being reseted after mangling in each `Consumer` ([PR #811](https://github.com/versatica/mediasoup/pull/811) by @ggarber).\n\n### 3.9.9\n\n- Optimize RTP header extension handling ([PR #786](https://github.com/versatica/mediasoup/pull/786)).\n- `RateCalculator`: Reset optimization ([PR #785](https://github.com/versatica/mediasoup/pull/785)).\n- Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` ([PR #788](https://github.com/versatica/mediasoup/pull/788), thanks to @ggarber for exposing this issue in [PR #787](https://github.com/versatica/mediasoup/pull/787)).\n\n### 3.9.8\n\n- Fix VP9 kSVC forwarding logic to not forward lower unneded layers ([PR #778](https://github.com/versatica/mediasoup/pull/778) by @ggarber).\n- Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate ([PR #779](https://github.com/versatica/mediasoup/pull/779) by @ggarber).\n- Replace outdated `random-numbers` package by native `crypto.randomInt()` ([PR #776](https://github.com/versatica/mediasoup/pull/776) by @piranna).\n- Update TypeScript version.\n\n### 3.9.7\n\n- Typing event emitters in mediasoup Node ([PR #764](https://github.com/versatica/mediasoup/pull/764) by @unao).\n\n### 3.9.6\n\n- TCC client optimizations for faster and more stable BWE ([PR #712](https://github.com/versatica/mediasoup/pull/712) by @ggarber).\n- Added support for RTP abs-capture-time header ([PR #761](https://github.com/versatica/mediasoup/pull/761) by @oto313).\n\n### 3.9.5\n\n- ICE renomination support ([PR #756](https://github.com/versatica/mediasoup/pull/756)).\n- Update `libuv` to 1.43.0.\n\n### 3.9.4\n\n- Worker: Fix bad printing of error messages from Worker ([PR #750](https://github.com/versatica/mediasoup/pull/750) by @j1elo).\n\n### 3.9.3\n\n- Single H264/H265 codec configuration in `supportedRtpCapabilities` ([PR #718](https://github.com/versatica/mediasoup/pull/718)).\n- Improve Windows support by not requiring MSVC configuration ([PR #741](https://github.com/versatica/mediasoup/pull/741)).\n\n### 3.9.2\n\n- `pipeToRouter()`: Reuse same `PipeTransport` when possible ([PR #697](https://github.com/versatica/mediasoup/pull/697)).\n- Add `worker.died` boolean getter.\n- Update TypeScript version to 4.X.X and use `target: \"esnext\"` so transpilation of ECMAScript private fields (`#xxxxx`) don't use `WeakMaps` tricks but use standard syntax instead.\n- Use more than one core for compilation on Windows ([PR #709](https://github.com/versatica/mediasoup/pull/709)).\n- `Consumer`: Modification of bitrate allocation algorithm ([PR #708](https://github.com/versatica/mediasoup/pull/708)).\n\n### 3.9.1\n\n- NixOS friendly build process ([PR #683](https://github.com/versatica/mediasoup/pull/683)).\n- Worker: Emit \"died\" event before observer \"close\" ([PR #684](https://github.com/versatica/mediasoup/pull/684)).\n- Transport: Hide debug message for RTX RTCP-RR packets ([PR #688](https://github.com/versatica/mediasoup/pull/688)).\n- Update `libuv` to 1.42.0.\n- Improve Windows support ([PR #692](https://github.com/versatica/mediasoup/pull/692)).\n- Avoid build commands when MEDIASOUP_WORKER_BIN is set ([PR #695](https://github.com/versatica/mediasoup/pull/695)).\n\n### 3.9.0\n\n- Replaces GYP build system with fully-functional Meson build system ([PR #622](https://github.com/versatica/mediasoup/pull/622)).\n- Worker communication optimization (aka removing netstring dependency) ([PR #644](https://github.com/versatica/mediasoup/pull/644)).\n- Move TypeScript and compiled JavaScript code to a new `node` folder.\n- Use ES6 private fields.\n- Require Node.js version >= 12.\n\n### 3.8.4\n\n- OPUS multi-channel (Surround sound) support ([PR #647](https://github.com/versatica/mediasoup/pull/647)).\n- Add `packetLoss` stats to transport ([PR #648](https://github.com/versatica/mediasoup/pull/648) by @ggarber).\n- Fixes for active speaker observer ([PR #655](https://github.com/versatica/mediasoup/pull/655) by @ggarber).\n- Fix big endian issues ([PR #639](https://github.com/versatica/mediasoup/pull/639)).\n\n### 3.8.3\n\n- Fix wrong `size_t*` to `int*` conversion in 64bit Big-Endian hosts ([PR #637](https://github.com/versatica/mediasoup/pull/637)).\n\n### 3.8.2\n\n- `ActiveSpeakerObserver`: Fix crash due to a `nullptr` ([PR #634](https://github.com/versatica/mediasoup/pull/634)).\n\n### 3.8.1\n\n- `SimulcastConsumer`: Fix RTP timestamp when switching layers ([PR #626](https://github.com/versatica/mediasoup/pull/626) by @penguinol).\n\n### 3.8.0\n\n- Update `libuv` to 1.42.0.\n- Use non-ASM OpenSSL on Windows ([PR #614](https://github.com/versatica/mediasoup/pull/614)).\n- Fix minor memory leak caused by non-virtual destructor ([PR #625](https://github.com/versatica/mediasoup/pull/625)).\n- Dominant Speaker Event ([PR #603](https://github.com/versatica/mediasoup/pull/603) by @SteveMcFarlin).\n\n### 3.7.19\n\n- Update `libuv` to 1.41.0.\n- C++:\n  - Move header includes ([PR #608](https://github.com/versatica/mediasoup/pull/608)).\n  - Enhance debugging on channel request/notification error ([PR #607](https://github.com/versatica/mediasoup/pull/607)).\n\n### 3.7.18\n\n- Support for optional fixed port on transports ([PR #593](https://github.com/versatica/mediasoup/pull/593) by @nazar-pc).\n- Upgrade and optimize OpenSSL dependency ([PR #598](https://github.com/versatica/mediasoup/pull/598) by @vpalmisano):\n  - OpenSSL upgraded to version 1.1.1k.\n  - Enable the compilation of assembly extensions for OpenSSL.\n  - Optimize the worker build (`-O3`) and disable the debug flag (`-g`).\n\n### 3.7.17\n\n- Introduce `PipeConsumerOptions` to avoid incorrect type information on `PipeTransport.consume()` arguments.\n- Make `ConsumerOptions.rtpCapabilities` field required as it should have always been.\n\n### 3.7.16\n\n- Add `mid` option in `ConsumerOptions` to provide way to override MID ([PR #586](https://github.com/versatica/mediasoup/pull/586) by @mstyura).\n\n### 3.7.15\n\n- `kind` field of `RtpHeaderExtension` is no longer optional. It must be 'audio' or 'video'.\n- Refactor API inconsistency in internal RTP Observer communication with worker.\n\n### 3.7.14\n\n- Update `usrsctp` to include a \"possible use after free bug\" fix (commit [here](https://github.com/sctplab/usrsctp/commit/0f8d58300b1fdcd943b4a9dd3fbd830825390d4d)).\n\n### 3.7.13\n\n- Fix build on FreeBSD ([PR #585](https://github.com/versatica/mediasoup/pull/585) by @smortex).\n\n### 3.7.12\n\n- `mediasoup-worker`: Fix memory leaks on error exit ([PR #581](https://github.com/versatica/mediasoup/pull/581)).\n\n### 3.7.11\n\n- Fix `DepUsrSCTP::Checker::timer` not being freed on `Worker` close ([PR #576](https://github.com/versatica/mediasoup/pull/576)). Thanks @nazar-pc for discovering this.\n\n### 3.7.10\n\n- Remove clang tools binaries from regular installation.\n\n### 3.7.9\n\n- Code clean up.\n\n### 3.7.8\n\n- `PayloadChannel`: Copy received messages into a separate buffer to avoid memory corruption if the message is later modified ([PR #570](https://github.com/versatica/mediasoup/pull/570) by @aggresss).\n\n### 3.7.7\n\n- Thread and memory safety fixes needed for mediasoup-rust ([PR #562](https://github.com/versatica/mediasoup/pull/562) by @nazar-pc).\n- mediasoup-rust support on macOS ([PR #567](https://github.com/versatica/mediasoup/pull/567) by @nazar-pc).\n- mediasoup-rust release 0.7.2.\n\n### 3.7.6\n\n- `Transport`: Implement new `setMaxOutgoingBitrate()` method ([PR #555](https://github.com/versatica/mediasoup/pull/555) by @t-mullen).\n- `SctpAssociation`: Don't warn if SCTP send buffer is full.\n- Rust: Update modules structure and other minor improvements for Rust version ([PR #558](https://github.com/versatica/mediasoup/pull/558)).\n- `mediasoup-worker`: Avoid duplicated basenames so that `libmediasoup-worker` is compilable on macOS ([PR #557](https://github.com/versatica/mediasoup/pull/557)).\n\n### 3.7.5\n\n- SctpAssociation: provide 'sctpsendbufferfull' reason on send error (#552).\n\n### 3.7.4\n\n- Improve `RateCalculator` ([PR #547](https://github.com/versatica/mediasoup/pull/547) by @vpalmisano).\n\n### 3.7.3\n\n- Make worker M1 compilable.\n\n### 3.7.2\n\n- `RateCalculator` optimization ([PR #538](https://github.com/versatica/mediasoup/pull/538) by @vpalmisano).\n\n### 3.7.1\n\n- `SimulcastConsumer`: Fix miscalculation when increasing layer ([PR #541](https://github.com/versatica/mediasoup/pull/541) by @penguinol).\n- Rust version with thread-based worker ([PR #540](https://github.com/versatica/mediasoup/pull/540)).\n\n### 3.7.0\n\n- Welcome to `mediasoup-rust`! Authored by @nazar-pc (PRs #518 and #533).\n- Update `usrsctp`.\n\n### 3.6.37\n\n- Fix crash if empty `fingerprints` array is given in `webrtcTransport.connect()` (issue #537).\n\n### 3.6.36\n\n- `Producer`: Add new stats field 'rtxPacketsDiscarded' ([PR #536](https://github.com/versatica/mediasoup/pull/536)).\n\n### 3.6.35\n\n- `Consumer` classes: make `IsActive()` return `true` (even if `Producer`'s score is 0) when DTX is enabled ([PR #534](https://github.com/versatica/mediasoup/pull/534) due to issue #532).\n\n### 3.6.34\n\n- Fix crash (regression, issue #529).\n\n### 3.6.33\n\n- Add missing `delete cb` that otherwise would leak ([PR #527](https://github.com/versatica/mediasoup/pull/527) based on [PR #526](https://github.com/versatica/mediasoup/pull/526) by @vpalmisano).\n- `router.pipeToRouter()`: Fix possible inconsistency in `pipeProducer.paused` status (as discussed in this [thread](https://mediasoup.discourse.group/t/concurrency-architecture/2515/) in the mediasoup forum).\n- Update `nlohmann/json` to 3.9.1.\n- Update `usrsctp`.\n- Enhance Jitter calculation.\n\n### 3.6.32\n\n- Fix notifications from `mediasoup-worker` being processed before responses received before them (issue #501).\n\n### 3.6.31\n\n- Move `bufferedAmount` from `dataConsumer.dump()` to `dataConsumer.getStats()`.\n\n### 3.6.30\n\n- Add `pipe` option to `transport.consume()`([PR #494](https://github.com/versatica/mediasoup/pull/494)).\n  - So the receiver will get all streams from the `Producer`.\n  - It works for any kind of transport (but `PipeTransport` which is always like this).\n- Add `LICENSE` and `PATENTS` files in `libwebrtc` dependency (issue #495).\n- Added `worker/src/Utils/README_BASE64_UTILS` (issue #497).\n- Update `usrsctp`.\n\n### 3.6.29\n\n- Fix wrong message about `rtcMinPort` and `rtcMaxPort`.\n- Update deps.\n- Improve `EnhancedEventEmitter.safeAsPromise()` (although not used).\n\n### 3.6.28\n\n- Fix replacement of `__MEDIASOUP_VERSION__` in `lib/index.d.ts` (issue #483).\n- `worker/scripts/configure.py`: Handle 'mips64' ([PR #485](https://github.com/versatica/mediasoup/pull/485)).\n\n### 3.6.27\n\n- Allow the `mediasoup-worker` process to inherit all environment variables (issue #480).\n\n### 3.6.26\n\n- BWE tweaks and debug logs.\n\n### 3.6.25\n\n- SCTP fixes ([PR #479](https://github.com/versatica/mediasoup/pull/479)).\n\n### 3.6.24\n\n- Update `awaitqueue` dependency.\n\n### 3.6.23\n\n- Fix yet another memory leak in Node.js layer due to `PayloadChannel` event listener not being removed.\n\n### 3.6.22\n\n- `Transport.cpp`: Provide transport congestion client with RTCP Receiver Reports (#464).\n- Update `libuv` to 1.40.0.\n- Update Node deps.\n- `SctpAssociation.cpp`: increase `sctpBufferedAmount` before sending any data (#472).\n\n### 3.6.21\n\n- Fix memory leak in Node.js layer due to `PayloadChannel` event listener not being removed (related to #463).\n\n### 3.6.20\n\n- Remove `-fwrapv` when building `mediasoup-worker` in `Debug` mode (issue #460).\n- Add `MEDIASOUP_MAX_CORES` to limit `NUM_CORES` during `mediasoup-worker` build ([PR #462](https://github.com/versatica/mediasoup/pull/462)).\n\n### 3.6.19\n\n- Update `usrsctp` dependency.\n- Update `typescript-eslint` deps.\n- Update Node deps.\n\n### 3.6.18\n\n- Fix `ortc.getConsumerRtpParameters()` RTX codec comparison issue ([PR #453](https://github.com/versatica/mediasoup/pull/453)).\n- RtpObserver: expose `RtpObserverAddRemoveProducerOptions` for `addProducer()` and `removeProducer()` methods.\n\n### 3.6.17\n\n- Update `libuv` to 1.39.0.\n- Update Node deps.\n- SimulcastConsumer: Prefer the highest spatial layer initially ([PR #450](https://github.com/versatica/mediasoup/pull/450)).\n- RtpStreamRecv: Set RtpDataCounter window size to 6 secs if DTX (#451)\n\n### 3.6.16\n\n- `SctpAssociation.cpp`: Fix `OnSctpAssociationBufferedAmount()` call.\n- Update deps.\n- New API to send data from Node throught SCTP DataConsumer.\n\n### 3.6.15\n\n- Avoid SRTP leak by deleting invalid SSRCs after STRP decryption (issue #437, thanks to @penguinol for reporting).\n- Update `usrsctp` dep.\n- DataConsumer 'bufferedAmount' implementation ([PR #442](https://github.com/versatica/mediasoup/pull/442)).\n\n### 3.6.14\n\n- Fix `usrsctp` vulnerability ([PR #439](https://github.com/versatica/mediasoup/pull/439)).\n- Fix issue #435 (thanks to @penguinol for reporting).\n- `TransportCongestionControlClient.cpp`: Enable periodic ALR probing to recover faster from network issues.\n\n- Update `nlohmann::json` C++ dep to 3.9.0.\n\n### 3.6.13\n\n- RTP on `DirectTransport` (issue #433, [PR #434](https://github.com/versatica/mediasoup/pull/434)):\n  - New API `producer.send(rtpPacket: Buffer)`.\n  - New API `consumer.on('rtp', (rtpPacket: Buffer)`.\n  - New API `directTransport.sendRtcp(rtcpPacket: Buffer)`.\n  - New API `directTransport.on('rtcp', (rtpPacket: Buffer)`.\n\n### 3.6.12\n\n- Release script.\n\n### 3.6.11\n\n- `Transport`: rename `maxSctpSendBufferSize` to `sctpSendBufferSize`.\n\n### 3.6.10\n\n- `Transport`: Implement `maxSctpSendBufferSize`.\n- Update `libuv` to 1.38.1.\n\n### 3.6.9\n\n- `Transport::ReceiveRtpPacket()`: Call `RecvStreamClosed(packet->GetSsrc())` if received RTP packet does not match any `Producer`.\n- `Transport::HandleRtcpPacket()`: Ensure `Consumer` is found for received NACK Feedback packets.\n- Fix issue #408.\n\n### 3.6.8\n\n- Fix SRTP leak due to streams not being removed when a `Producer` or `Consumer` is closed.\n  - [PR #428](https://github.com/versatica/mediasoup/pull/428) (fixes issues #426).\n  - Credits to credits to @penguinol for reporting and initial work at [PR #427](https://github.com/versatica/mediasoup/pull/427).\n- Update `nlohmann::json` C++ dep to 3.8.0.\n- C++: Enhance `const` correctness.\n\n### 3.6.7\n\n- `ConsumerScore`: Add `producerScores`, scores of all RTP streams in the producer ordered by encoding (just useful when the producer uses simulcast).\n  - [PR #421](https://github.com/versatica/mediasoup/pull/421) (fixes issues #420).\n- Hide worker executable console in Windows.\n  - [PR #419](https://github.com/versatica/mediasoup/pull/419) (credits to @BlueMagnificent).\n- `RtpStream.cpp`: Fix wrong `std::round()` usage.\n  - Issue #423.\n\n### 3.6.6\n\n- Update `usrsctp` library.\n- Update ESLint and TypeScript related dependencies.\n\n### 3.6.5\n\n- Set `score:0` when `dtx:true` is set in an `encoding` and there is no RTP for some seconds for that RTP stream.\n  - Fixes #415.\n\n### 3.6.4\n\n- `gyp`: Fix CLT version detection in OSX Catalina when XCode app is not installed.\n  - [PR #413](https://github.com/versatica/mediasoup/pull/413) (credits to @enimo).\n\n### 3.6.3\n\n- Modernize TypeScript.\n\n### 3.6.2\n\n- Fix crash in `Transport.ts` when closing a `DataConsumer` created on a `DirectTransport`.\n\n### 3.6.1\n\n- Export new `DirectTransport` in `types`.\n- Make `DataProducerOptions` optional (not needed when in a `DirectTransport`).\n\n### 3.6.0\n\n- SCTP/DataChannel termination:\n  - [PR #409](https://github.com/versatica/mediasoup/pull/409)\n  - Allow the Node application to directly send text/binary messages to `mediasoup-worker` C++ process so others can consume them using `DataConsumers`.\n  - And vice-versa: allow the Node application to directly consume in Node messages send by `DataProducers`.\n- Add `WorkerLogTag` TypeScript enum and also add a new 'message' tag into it.\n\n### 3.5.15\n\n- Simulcast and SVC: Better computation of desired bitrate based on `maxBitrate` field in the `producer.rtpParameters.encodings`.\n\n### 3.5.14\n\n- Update deps, specially `uuid` and `@types/uuid` that had a TypeScript related bug.\n- `TransportCongestionClient.cpp`: Improve sender side bandwidth estimation by do not reporting `this->initialAvailableBitrate` as available bitrate due to strange behavior in the algorithm.\n\n### 3.5.13\n\n- Simplify `GetDesiredBitrate()` in `SimulcastConsumer` and `SvcConsumer`.\n- Update `libuv` to 1.38.0.\n\n### 3.5.12\n\n- `SeqManager.cpp`: Improve performance.\n  - [PR #398](https://github.com/versatica/mediasoup/pull/398) (credits to @penguinol).\n\n### 3.5.11\n\n- `SeqManager.cpp`: Fix a bug and improve performance.\n  - Fixes issue #395 via [PR #396](https://github.com/versatica/mediasoup/pull/396) (credits to @penguinol).\n- Drop Node.js 8 support. Minimum supported Node.js version is now 10.\n- Upgrade `eslint` and `jest` major versions.\n\n### 3.5.10\n\n- `SimulcastConsumer.cpp`: Fix `IncreaseLayer()` method (fixes #394).\n- Udpate Node deps.\n\n### 3.5.9\n\n- `libwebrtc`: Apply patch by @sspanak and @Ivaka to avoid crash. Related issue: #357.\n- `PortManager.cpp`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0.\n- Update Node deps.\n\n### 3.5.8\n\n- Enable `UV_UDP_RECVMMSG`:\n  - Upgrade `libuv` to 1.37.0.\n  - Use `uv_udp_init_ex()` with `UV_UDP_RECVMMSG` flag.\n  - Add our own `uv.gyp` now that `libuv` has removed support for GYP (fixes #384).\n\n### 3.5.7\n\n- Fix crash in `mediasoup-worker` due to conversion from `uint64_t` to `int64_t` (used within `libwebrtc` code. Fixes #357.\n- Update `usrsctp` library.\n- Update Node deps.\n\n### 3.5.6\n\n- `SeqManager.cpp`: Fix video lag after a long time.\n  - Fixes #372 (thanks @penguinol for reporting it and giving the solution).\n\n### 3.5.5\n\n- `UdpSocket.cpp`: Revert `uv__udp_recvmmsg()` usage since it notifies about received UDP packets in reverse order. Feature on hold until fixed.\n\n### 3.5.4\n\n- `Transport.cpp`: Enable transport congestion client for the first video Consumer, no matter it's uses simulcast, SVC or a single stream.\n- Update `libuv` to 1.35.0.\n- `UdpSocket.cpp`: Ensure the new libuv's `uv__udp_recvmmsg()` is used, which is more efficient.\n\n### 3.5.3\n\n- `PlainTransport`: Remove `multiSource` option. It was a hack nobody should use.\n\n### 3.5.2\n\n- Enable MID RTP extension in mediasoup to receivers direction (for consumers).\n  - This **requires** mediasoup-client 3.5.2 to work.\n\n### 3.5.1\n\n- `PlainTransport`: Fix event name: 'rtcpTuple' => 'rtcptuple'.\n\n### 3.5.0\n\n- `PipeTransport`: Add support for SRTP and RTP retransmission (RTX + NACK). Useful when connecting two mediasoup servers running in different hosts via pipe transports.\n- `PlainTransport`: Add support for SRTP.\n- Rename `PlainRtpTransport` to `PlainTransport` everywhere (classes, methods, TypeScript types, etc). Keep previous names and mark them as DEPRECATED.\n- Fix vulnarability in IPv6 parser.\n\n### 3.4.13\n\n- Update `uuid` dep to 7.0.X (new API).\n- Fix crash due wrong array index in `PipeConsumer::FillJson()`.\n  - Fixes #364\n\n### 3.4.12\n\n- TypeScript: generate `es2020` instead of `es6`.\n- Update `usrsctp` library.\n  - Fixes #362 (thanks @chvarlam for reporting it).\n\n### 3.4.11\n\n- `IceServer.cpp`: Reject received STUN Binding request with 487 if remote peer indicates ICE-CONTROLLED into it.\n\n### 3.4.10\n\n- `ProducerOptions`: Rename `keyFrameWaitTime` option to `keyFrameRequestDelay` and make it work as expected.\n\n### 3.4.9\n\n- Add `Utils::Json::IsPositiveInteger()` to not rely on `is_number_unsigned()` of json lib, which is unreliable due to its design.\n- Avoid ES6 `export default` and always use named `export`.\n- `router.pipeToRouter()`: Ensure a single `PipeTransport` pair is created between `router1` and `router2`.\n  - Since the operation is async, it may happen that two simultaneous calls to `router1.pipeToRouter({ producerId: xxx, router: router2 })` would end up generating two pairs of `PipeTranports`. To prevent that, let's use an async queue.\n- Add `keyFrameWaitTime` option to `ProducerOptions`.\n- Update Node and C++ deps.\n\n### 3.4.8\n\n- `libsrtp.gyp`: Fix regression in mediasoup for Windows.\n  - `libsrtp.gyp`: Modernize it based on the new `BUILD.gn` in Chromium.\n  - `libsrtp.gyp`: Don't include \"test\" and other targets.\n  - Assume `HAVE_INTTYPES_H`, `HAVE_INT8_T`, etc. in Windows.\n  - Issue details: https://github.com/sctplab/usrsctp/issues/353\n- `gyp` dependency: Add support for Microsoft Visual Studio 2019.\n  - Modify our own `gyp` sources to fix the issue.\n  - CL uploaded to GYP project with the fix.\n  - Issue details: https://github.com/sctplab/usrsctp/issues/347\n\n### 3.4.7\n\n- `PortManager.cpp`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker.\n- Do not copy `catch.hpp` into `test/include/` but make the GYP `mediasoup-worker-test` target include the corresponding folder in `deps/catch`.\n\n### 3.4.6\n\n- Update libsrtp to 2.3.0.\n- Update ESLint and TypeScript deps.\n\n### 3.4.5\n\n- Update deps.\n- Fix text in `./github/Bug_Report.md` so it no longer references the deprecated mailing list.\n\n### 3.4.4\n\n- `Transport.cpp`: Ignore RTCP SDES packets (we don't do anything with them anyway).\n- `Producer` and `Consumer` stats: Always show `roundTripTime` (even if calculated value is 0) after a `roundTripTime` > 0 has been seen.\n\n### 3.4.3\n\n- `Transport.cpp`: Fix RTCP FIR processing:\n  - Instead of looking at the media ssrc in the common header, iterate FIR items and look for associated `Consumers` based on ssrcs in each FIR item.\n  - Fixes #350 (thanks @j1elo for reporting and documenting the issue).\n\n### 3.4.2\n\n- `SctpAssociation.cpp`: Improve/fix logs.\n- Improve Node `EventEmitter` events inline documentation.\n- `test-node-sctp.js`: Wait for SCTP association to be open before sending data.\n\n### 3.4.1\n\n- Improve `mediasoup-worker` build system by using `sh` instead of `bash` and default to 4 cores (thanks @smoke, [PR #349](https://github.com/versatica/mediasoup/pull/349)).\n\n### 3.4.0\n\n- Add `worker.getResourceUsage()` API.\n- Update OpenSSL to 1.1.1d.\n- Update `libuv` to 1.34.0.\n- Update TypeScript version.\n\n### 3.3.8\n\n- Update usrsctp dependency (it fixes a potential wrong memory access).\n  - More details in the reported issue: https://github.com/sctplab/usrsctp/issues/408\n\n### 3.3.7\n\n- Fix `version` getter.\n\n### 3.3.6\n\n- `SctpAssociation.cpp`: Initialize the `usrsctp` socket in the class constructor. Fixes #348.\n\n### 3.3.5\n\n- Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. Fixes #333.\n  - More details in the commit: https://github.com/versatica/mediasoup/commit/49824baf102ab6d2b01e5bca565c29b8ac0fec22\n\n### 3.3.4\n\n- IPv6 fix: Use `INET6_ADDRSTRLEN` instead of `INET_ADDRSTRLEN`.\n\n### 3.3.3\n\n- Add `consumer.setPriority()` and `consumer.priority` API to prioritize how the estimated outgoing bitrate in a transport is distributed among all video consumers (in case there is not enough bitrate to satisfy them).\n- Make video `SimpleConsumers` play the BWE game by helping in probation generation and bitrate distribution.\n- Add `consumer.preferredLayers` getter.\n- Rename `enablePacketEvent()` and \"packet\" event to `enableTraceEvent()` and \"trace\" event (sorry SEMVER).\n- Transport: Add a new \"trace\" event of type \"bwe\" with detailed information about bitrates.\n\n### 3.3.2\n\n- Improve \"packet\" event by not firing both \"keyframe\" and \"rtp\" types for the same RTP packet.\n\n### 3.3.1\n\n- Add type \"keyframe\" as a valid type for \"packet\" event in `Producers` and `Consumers`.\n\n### 3.3.0\n\n- Add transport-cc bandwidth estimation and congestion control in sender and receiver side.\n- Run in Windows.\n- Rewrite to TypeScript.\n- Tons of improvements.\n\n### 3.2.5\n\n- Fix TCP leak (#325).\n\n### 3.2.4\n\n- `PlainRtpTransport`: Fix comedia mode.\n\n### 3.2.3\n\n- `RateCalculator`: improve efficiency in `GetRate()` method (#324).\n\n### 3.2.2\n\n- `RtpDataCounter`: use window size of 2500 ms instead of 1000 ms.\n  - Fixes false \"lack of RTP\" detection in some screen sharing usages with simulcast.\n  - Fixes #312.\n\n### 3.2.1\n\n- Add RTCP Extended Reports for RTT calculation on receiver RTP stream (thanks @yangjinechofor for initial pull request #314).\n- Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321).\n\n### 3.2.0\n\n- Add DataChannel support via DataProducers and DataConsumers (#10).\n- SRTP: Add support for AEAD GCM (#320).\n\n### 3.1.7\n\n- `PipeConsumer.cpp`: Fix RTCP generation (thanks @vpalmisano).\n\n### 3.1.6\n\n- VP8 and H264: Fix regression in 3.1.5 that produces lot of changes in current temporal layer detection.\n\n### 3.1.5\n\n- VP8 and H264: Allow packets without temporal layer information even if N temporal layers were announced.\n\n### 3.1.4\n\n- Add `-fPIC` in `cflags` to compile in x86-64. Fixes #315.\n\n### 3.1.3\n\n- Set the sender SSRC on PLI and FIR requests [related thread](https://mediasoup.discourse.group/t/broadcasting-a-vp8-rtp-stream-from-gstreamer/93).\n\n### 3.1.2\n\n- Workaround to detect H264 key frames when Chrome uses external encoder (related [issue](https://bugs.chromium.org/p/webrtc/issues/detail?id=10746)). Fixes #313.\n\n### 3.1.1\n\n- Improve `GetBitratePriority()` method in `SimulcastConsumer` and `SvcConsumer` by checking the total bitrate of all temporal layers in a given producer stream or spatial layer.\n\n### 3.1.0\n\n- Add SVC support. It includes VP9 full SVC and VP9 K-SVC as implemented by libwebrtc.\n- Prefer Python 2 (if available) over Python 3. This is because there are yet pending issues with gyp + Python 3.\n\n### 3.0.12\n\n- Do not require Python 2 to compile mediasoup worker (#207). Both Python 2 and 3 can now be used.\n\n### 3.0.11\n\n- Codecs: Improve temporal layer switching in VP8 and H264.\n- Skip worker compilation if `MEDIASOUP_WORKER_BIN` environment variable is given (#309). This makes it possible to install mediasoup in platforms in which, somehow, gcc > 4.8 is not available during `npm install mediasoup` but it's available later.\n- Fix `RtpStreamRecv::TransmissionCounter::GetBitrate()`.\n\n### 3.0.10\n\n- `parseScalabilityMode()`: allow \"S\" as spatial layer (and not just \"L\"). \"L\" means \"dependent spatial layer\" while \"S\" means \"independent spatial layer\", which is used in K-SVC (VP9, AV1, etc).\n\n### 3.0.9\n\n- `RtpStreamSend::ReceiveRtcpReceiverReport()`: improve `rtt` calculation if no Sender Report info is reported in received Received Report.\n- Update `libuv` to version 1.29.1.\n\n### 3.0.8\n\n- VP8 & H264: Improve temporal layer switching.\n\n### 3.0.7\n\n- RTP frame-marking: Add some missing checks.\n\n### 3.0.6\n\n- Fix regression in proxied RTP header extensions.\n\n### 3.0.5\n\n- Add support for frame-marking RTP extension and use it to enable temporal layers switching in H264 codec (#305).\n\n### 3.0.4\n\n- Improve RTP probation for simulcast/svc consumers by using proper RTP retransmission with increasing sequence number.\n\n### 3.0.3\n\n- Simulcast: Improve timestamps extra offset handling by having a map of extra offsets indexed by received timestamps. This helps in case of packet retransmission.\n\n### 3.0.2\n\n- Simulcast: proper RTP stream switching by rewriting packet timestamp with a new timestamp calculated from the SenderReports' NTP relationship.\n\n### 3.0.1\n\n- Fix crash in `SimulcastConsumer::IncreaseLayer()` with Safari and H264 (#300).\n\n### 3.0.0\n\n- v3 is here!\n\n### 2.6.19\n\n- `RtpStreamSend.cpp`: Fix a crash in `StorePacket()` when it receives an old packet and there is no space left in the storage buffer (thanks to zkfun for reporting it and providing us with the solution).\n- Update deps.\n\n### 2.6.18\n\n- Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration.\n\n### 2.6.17\n\n- Improve build system by using all available CPU cores in parallel.\n\n### 2.6.16\n\n- Don't mandate server port range to be >= 99.\n\n### 2.6.15\n\n- Fix NACK retransmissions.\n\n### 2.6.14\n\n- Fix TCP leak (#325).\n\n### 2.6.13\n\n- Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321).\n- Update deps.\n\n### 2.6.12\n\n- Fix RTCP Receiver Report handling.\n\n### 2.6.11\n\n- Update deps.\n- Simulcast: Increase profiles one by one unless explicitly forced (fixes #188).\n\n### 2.6.10\n\n- `PlainRtpTransport.js`: Add missing methods and events.\n\n### 2.6.9\n\n- Remove a potential crash if a single `encoding` is given in the Producer `rtpParameters` and it has a `profile` value.\n\n### 2.6.8\n\n- C++: Verify in libuv static callbacks that the associated C++ instance has not been deallocated (thanks @artushin and @mariat-atg for reporting and providing valuable help in #258).\n\n### 2.6.7\n\n- Fix wrong destruction of Transports in Router.cpp that generates 100% CPU usage in `mediasoup-worker` processes.\n\n### 2.6.6\n\n- Fix a port leak when a WebRtcTransport is remotely closed due to a DTLS close alert (thanks @artushin for reporting it in #259).\n\n### 2.6.5\n\n- RtpPacket: Fix Two-Byte header extensions parsing.\n\n### 2.6.4\n\n- Upgrade again to OpenSSL 1.1.0j (20 Nov 2018) after adding a workaround for issue [#257](https://github.com/versatica/mediasoup/issues/257).\n\n### 2.6.3\n\n- Downgrade OpenSSL to version 1.1.0h (27 Mar 2018) until issue [#257](https://github.com/versatica/mediasoup/issues/257) is fixed.\n\n### 2.6.2\n\n- C++: Remove all `Destroy()` class methods and no longer do `delete this`.\n- Update libuv to 1.24.1.\n- Update OpenSSL to 1.1.0g.\n\n### 2.6.1\n\n- worker: Internal refactor and code cleanup.\n- Remove announced support for certain RTCP feedback types that mediasoup does nothing with (and avoid forwarding them to the remote RTP sender).\n- fuzzer: fix some wrong memory access in `RtpPacket::Dump()` and `StunMessage::Dump()` (just used during development).\n\n### 2.6.0\n\n- Integrate [libFuzzer](http://llvm.org/docs/LibFuzzer.html) into mediasoup (documentation in the `doc` folder). Extensive testing done. Several heap-buffer-overflow and memory leaks fixed.\n\n### 2.5.6\n\n- `Producer.cpp`: Remove `UpdateRtpParameters()`. It was broken since Consumers\n  were not notified about profile removed and so on, so they may crash.\n- `Producer.cpp: Remove some maps and simplify streams handling by having a\nsingle `mapSsrcRtpStreamInfo`. Just keep `mapActiveProfiles`because`GetActiveProfiles()` method needs it.\n- `Producer::MayNeedNewStream()`: Ignore new media streams with new SSRC if\n  its RID is already in use by other media stream (fixes #235).\n- Fix a bad memory access when using two byte RTP header extensions.\n\n### 2.5.5\n\n- `Server.js`: If a worker crashes make sure `_latestWorkerIdx` becomes 0.\n\n### 2.5.4\n\n- `server.Room()`: Assign workers incrementally or explicitly via new `workerIdx` argument.\n- Add `server.numWorkers` getter.\n\n### 2.5.3\n\n- Don't announce `muxId` nor RTP MID extension support in `Consumer` RTP parameters.\n\n### 2.5.2\n\n- Enable RTP MID extension again.\n\n### 2.5.1\n\n- Disable RTP MID extension until [#230](https://github.com/versatica/mediasoup/issues/230) is fixed.\n\n### 2.5.0\n\n- Add RTP MID extension support.\n\n### 2.4.6\n\n- Do not close `Transport` on ICE disconnected (as it would prevent ICE restart on \"recv\" TCP transports).\n\n### 2.4.5\n\n- Improve codec matching.\n\n### 2.4.4\n\n- Fix audio codec matching when `channels` parameter is not given.\n\n### 2.4.3\n\n- Make `PlainRtpTransport` not leak if port allocation fails (related issue [#224](https://github.com/versatica/mediasoup/issues/224)).\n\n### 2.4.2\n\n- Fix a crash in when no more RTP ports were available (see related issue [#222](https://github.com/versatica/mediasoup/issues/222)).\n\n### 2.4.1\n\n- Update dependencies.\n\n### 2.4.0\n\n- Allow non WebRTC peers to create plain RTP transports (no ICE/DTLS/SRTP but just plain RTP and RTCP) for sending and receiving media.\n\n### 2.3.3\n\n- Fix C++ syntax to avoid an error when building the worker with clang 8.0.0 (OSX 10.11.6).\n\n### 2.3.2\n\n- `Channel.js`: Upgrade `REQUEST_TIMEOUT` to 20 seconds to avoid timeout errors when the Node or worker thread usage is too high (related to this [issue](https://github.com/versatica/mediasoup-client/issues/48)).\n\n### 2.3.1\n\n- H264: Check if there is room for the indicated NAL unit size (thanks @ggarber).\n- H264: Code cleanup.\n\n### 2.3.0\n\n- Add new \"spy\" feature. A \"spy\" peer cannot produce media and is invisible for other peers in the room.\n\n### 2.2.7\n\n- Fix H264 simulcast by properly detecting when the profile switching should be done.\n- Fix a crash in `Consumer::GetStats()` (see related issue [#196](https://github.com/versatica/mediasoup/issues/196)).\n\n### 2.2.6\n\n- Add H264 simulcast capability.\n\n### 2.2.5\n\n- Avoid calling deprecated (NOOP) `SSL_CTX_set_ecdh_auto()` function in OpenSSL >= 1.1.0.\n\n### 2.2.4\n\n- [Fix #4](https://github.com/versatica/mediasoup/issues/4): Avoid DTLS handshake fragmentation.\n\n### 2.2.3\n\n- [Fix #196](https://github.com/versatica/mediasoup/issues/196): Crash in `Consumer::getStats()` due to wrong `targetProfile`.\n\n### 2.2.2\n\n- Improve [issue #209](https://github.com/versatica/mediasoup/issues/209).\n\n### 2.2.1\n\n- [Fix #209](https://github.com/versatica/mediasoup/issues/209): `DtlsTransport`: don't crash when signaled fingerprint and DTLS fingerprint do not match (thanks @yangjinecho for reporting it).\n\n### 2.2.0\n\n- Update Node and C/C++ dependencies.\n\n### 2.1.0\n\n- Add `localIP` option for `room.createRtpStreamer()` and `transport.startMirroring()` [[PR #199](https://github.com/versatica/mediasoup/pull/199)](https://github.com/versatica/mediasoup/pull/199).\n\n### 2.0.16\n\n- Improve C++ usage (remove \"warning: missing initializer for member\" [-Wmissing-field-initializers]).\n- Update Travis-CI settings.\n\n### 2.0.15\n\n- Make `PlainRtpTransport` also send RTCP SR/RR reports (thanks @artushin for reporting).\n\n### 2.0.14\n\n- [Fix #193](https://github.com/versatica/mediasoup/issues/193): `preferTcp` not honored (thanks @artushin).\n\n### 2.0.13\n\n- Avoid crash when no remote IP/port is given.\n\n### 2.0.12\n\n- Add `handled` and `unhandled` events to `Consumer`.\n\n### 2.0.11\n\n- [Fix #185](https://github.com/versatica/mediasoup/issues/185): Consumer: initialize effective profile to 'NONE' (thanks @artushin).\n- [Fix #186](https://github.com/versatica/mediasoup/issues/186): NackGenerator code being executed after instance deletion (thanks @baiyufei).\n\n### 2.0.10\n\n- [Fix #183](https://github.com/versatica/mediasoup/issues/183): Always reset the effective `Consumer` profile when removed (thanks @thehappycoder).\n\n### 2.0.9\n\n- Make ICE+DTLS more flexible by allowing sending DTLS handshake when ICE is just connected.\n\n### 2.0.8\n\n- Disable stats periodic retrieval also on remote closure of `Producer` and `WebRtcTransport`.\n\n### 2.0.7\n\n- [Fix #180](https://github.com/versatica/mediasoup/issues/180): Added missing include `cmath` so that `std::round` can be used (thanks @jacobEAdamson).\n\n### 2.0.6\n\n- [Fix #173](https://github.com/versatica/mediasoup/issues/173): Avoid buffer overflow in `()` (thanks @lightmare).\n- Improve stream layers management in `Consumer` by using the new `RtpMonitor` class.\n\n### 2.0.5\n\n- [Fix #164](https://github.com/versatica/mediasoup/issues/164): Sometimes video freezes forever (no RTP received in browser at all).\n- [Fix #160](https://github.com/versatica/mediasoup/issues/160): Assert error in `RTC::Consumer::GetStats()`.\n\n### 2.0.4\n\n- [Fix #159](https://github.com/versatica/mediasoup/issues/159): Don’t rely on VP8 payload descriptor flags to assure the existence of data.\n- [Fix #160](https://github.com/versatica/mediasoup/issues/160): Reset `targetProfile` when the corresponding profile is removed.\n\n### 2.0.3\n\n- worker: Fix crash when VP8 payload has no `PictureId`.\n\n### 2.0.2\n\n- worker: Remove wrong `assert` on `Producer::DeactivateStreamProfiles()`.\n\n### 2.0.1\n\n- Update README file.\n\n### 2.0.0\n\n- New design based on `Producers` and `Consumer` plus a mediasoup protocol and the **mediasoup-client** client side SDK.\n\n### 1.2.8\n\n- Fix a crash due to RTX packet processing while the associated `NackGenerator` is not yet created.\n\n### 1.2.7\n\n- Habemus RTX ([RFC 4588](https://tools.ietf.org/html/rfc4588)) for proper RTP retransmission.\n\n### 1.2.6\n\n- Fix an issue in `buffer.toString()` that makes mediasoup fail in Node 8.\n- Update libuv to version 1.12.0.\n\n### 1.2.5\n\n- Add support for [ICE renomination](https://tools.ietf.org/html/draft-thatcher-ice-renomination).\n\n### 1.2.4\n\n- Fix a SDP negotiation issue when the remote peer does not have compatible codecs.\n\n### 1.2.3\n\n- Add video codecs supported by Microsoft Edge.\n\n### 1.2.2\n\n- `RtpReceiver`: generate RTCP PLI when \"rtpraw\" or \"rtpobject\" event listener is set.\n\n### 1.2.1\n\n- `RtpReceiver`: fix an error producing packets when \"rtpobject\" event is set.\n\n### 1.2.0\n\n- `RtpSender`: allow `disable()`/`enable()` without forcing SDP renegotiation (#114).\n\n### 1.1.0\n\n- Add `Room.on('audiolevels')` event.\n\n### 1.0.2\n\n- Set a maximum value of 1500 bytes for packet storage in `RtpStreamSend`.\n\n### 1.0.1\n\n- Avoid possible segfault if `RemoteBitrateEstimator` generates a bandwidth estimation with zero SSRCs.\n\n### 1.0.0\n\n- First stable release.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to mediasoup\n\nThanks for taking the time to contribute to mediasoup! 🎉👍\n\n## License\n\nBy contributing to mediasoup, you agree that your contributions will be licensed under its ISC License.\n\n## Reporting Bugs\n\nWe primarily use GitHub as an issue tracker. Just open an issue in GitHub if you have encountered a bug in mediasoup.\n\nIf you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead:\n\n- https://mediasoup.discourse.group\n\nIf you got a crash in mediasoup, please try to provide a core dump into the issue report:\n\n- https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump\n\n## Pull Request Process\n\nWhen creating a Pull Request for mediasoup:\n\n- Ensure that changes/additions done in TypeScript files (in `node/src` folder) are also applied to the Rust layer (in `rust` folder), and vice-versa.\n- Test units must be added for both Node.js and Rust.\n- Changes/additions in C++ code may need tests in `worker/test` folder.\n\nOnce all changes are done, run the following commands to verify that the code in your PR conforms to the code syntax of the project, it does not break existing funtionality and tests pass:\n\n- `npm run lint`: Check TypeScript and C++ linting rules. Formating errors can be automatically fixed by running `npm run format`.\n- `npm run typescript:build`: Compile TypeScript code (under `src` folder) into JavaScript code (under `lib` folder).\n- `npm run test`: Run JavaScript and C++ test units.\n- Instead, you can run `npm run release:check` which will run all those steps.\n- `cargo fmt`, `cargo clippy` and `cargo test` to ensure that everything is good in Rust side.\n\nThe full list of `npm` scripts, `invoke` tasks and `cargo` commands is available in the [doc/Building.md](/doc/Building.md) file.\n\n## Coding Style\n\nIn adition to automatic checks performed by commands above, we also enforce other minor things related to coding style:\n\n### Comments in TypeScript and C++\n\nWe use `//` for inline comments in both JavaScript and C++ source files.\n\n- Comments must start with upercase letter.\n- Comments must not exceed 80 columns (split into different lines if necessary).\n- Comments must end with a dot.\n\nExample (good):\n\n```ts\n// Calculate foo based on bar value.\nconst foo = bar / 2;\n```\n\nExample (bad):\n\n```ts\n// calculate foo based on bar value\nconst foo = bar / 2;\n```\n\nWhen adding inline documentation for methods or functions, we use `/** */` syntax. Example:\n\n```ts\n/**\n * Calculates current score for foo and bar.\n */\nfunction calculateScore(): number {\n\t// [...]\n}\n```\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"rust\",\n    \"rust/types\",\n    \"worker\"\n]\n"
  },
  {
    "path": "LICENSE",
    "content": "ISC License\n\nCopyright © 2015, Iñaki Baz Castillo <ibc@aliax.net>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# mediasoup v3\n\n[![][mediasoup-banner]][mediasoup-website]\n\n[![][npm-shield-mediasoup]][npm-mediasoup]\n[![][crates-shield-mediasoup]][crates-mediasoup]\n[![][opencollective-shield-mediasoup]][opencollective-mediasoup]\n\n---\n\n[![][github-actions-shield-mediasoup-node]][github-actions-mediasoup-node]\n[![][github-actions-shield-mediasoup-worker]][github-actions-mediasoup-worker]\n[![][github-actions-shield-mediasoup-rust]][github-actions-mediasoup-rust]\n[![][github-actions-shield-mediasoup-worker-fuzzer]][github-actions-mediasoup-worker-fuzzer]\n[![][github-actions-shield-mediasoup-worker-prebuild]][github-actions-mediasoup-worker-prebuild]\n[![][github-actions-mediasoup-codeql-shield-mediasoup]][github-actions-mediasoup-codeql-mediasoup]\n\n## Website and Documentation\n\n- [mediasoup.org][mediasoup-website]\n\n## Support Forum\n\n- [mediasoup.discourse.group][mediasoup-discourse]\n\n## Design Goals\n\nmediasoup and its client side libraries are designed to accomplish with the following goals:\n\n- Be a SFU (Selective Forwarding Unit).\n- Support both WebRTC and plain RTP input and output.\n- Be a Node.js module or Rust crate in server side.\n- Be a tiny TypeScript and C++ libraries in client side.\n- Be minimalist: just handle the media layer.\n- Be signaling agnostic: do not mandate any signaling protocol.\n- Be super low level API.\n- Support all existing WebRTC endpoints.\n- Enable integration with well known multimedia libraries/tools.\n\n## Architecture\n\n![][mediasoup-architecture]\n\n## Use Cases\n\nmediasoup and its client side libraries provide a super low level API. They are intended to enable different use cases and scenarios, without any constraint or assumption. Some of these use cases are:\n\n- Group video chat applications.\n- One-to-many (or few-to-many) broadcasting applications in real-time.\n- RTP streaming.\n\n## Features\n\n- ECMAScript 6/Idiomatic Rust low level API.\n- Multi-stream: multiple audio/video streams over a single ICE + DTLS transport.\n- IPv6 ready.\n- ICE / DTLS / RTP / RTCP over UDP and TCP.\n- Simulcast and SVC support.\n- Congestion control.\n- Sender and receiver bandwidth estimation with spatial/temporal layers distribution algorithm.\n- Data message exchange (via WebRTC DataChannels, SCTP over plain UDP, and direct termination in Node.js/Rust).\n- Extremely powerful (media worker thread/subprocess coded in C++ on top of [libuv](https://libuv.org)).\n\n## Demo Online\n\n[![][mediasoup-demo-screenshot]][mediasoup-demo]\n\nTry it at [v3demo.mediasoup.org](https://v3demo.mediasoup.org) ([source code](https://github.com/versatica/mediasoup-demo)).\n\n## Authors\n\n- Iñaki Baz Castillo [[website](https://inakibaz.me)|[github](https://github.com/ibc/)]\n- José Luis Millán [[github](https://github.com/jmillan/)]\n- Nazar Mokynskyi [[github](https://github.com/nazar-pc/)]\n\n## Social\n\n- Bluesky: [@mediasoup-sfu.bsky.social](https://bsky.app/profile/mediasoup-sfu.bsky.social)\n\n## Sponsor\n\nYou can support mediasoup by [sponsoring][sponsor] it. Thanks!\n\n## License\n\n[ISC](./LICENSE)\n\n[mediasoup-banner]: /art/mediasoup-banner.png\n[mediasoup-website]: https://mediasoup.org\n[mediasoup-discourse]: https://mediasoup.discourse.group\n[npm-shield-mediasoup]: https://img.shields.io/npm/v/mediasoup.svg\n[npm-mediasoup]: https://npmjs.org/package/mediasoup\n[crates-shield-mediasoup]: https://img.shields.io/crates/v/mediasoup.svg\n[crates-mediasoup]: https://crates.io/crates/mediasoup\n[opencollective-shield-mediasoup]: https://img.shields.io/opencollective/all/mediasoup.svg\n[opencollective-mediasoup]: https://opencollective.com/mediasoup\n[github-actions-shield-mediasoup-node]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-node.yaml/badge.svg?branch=v3\n[github-actions-mediasoup-node]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-node.yaml?query=branch%3Av3\n[github-actions-shield-mediasoup-worker]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker.yaml/badge.svg?branch=v3\n[github-actions-mediasoup-worker]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker.yaml?query=branch%3Av3\n[github-actions-shield-mediasoup-rust]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-rust.yaml/badge.svg?branch=v3\n[github-actions-mediasoup-rust]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-rust.yaml?query=branch%3Av3\n[github-actions-shield-mediasoup-worker-fuzzer]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-fuzzer.yaml/badge.svg?branch=v3\n[github-actions-mediasoup-worker-fuzzer]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-fuzzer.yaml?query=branch%3Av3\n[github-actions-shield-mediasoup-worker-prebuild]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-prebuild.yaml/badge.svg?event=release\n[github-actions-mediasoup-worker-prebuild]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-prebuild.yaml?query=event%3Arelease\n[github-actions-mediasoup-codeql-shield-mediasoup]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-codeql.yaml/badge.svg?branch=v3\n[github-actions-mediasoup-codeql-mediasoup]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-codeql.yaml?query=branch%3Av3\n[sponsor]: https://mediasoup.org/sponsor\n[mediasoup-architecture]: /art/mediasoup-v3-architecture-02.png\n[mediasoup-demo-screenshot]: /art/mediasoup-v3.png\n[mediasoup-demo]: https://v3demo.mediasoup.org\n"
  },
  {
    "path": "doc/Building.md",
    "content": "# Building\n\nThis document is intended for mediasoup developers.\n\n## NPM scripts\n\nThe `package.json` file in the main folder includes the following scripts:\n\n### `npm run typescript:build`\n\nCompiles mediasoup TypeScript code (`node/src` folder) JavaScript and places it into the `node/lib` directory.\n\n### `npm run typescript:watch`\n\nCompiles mediasoup TypeScript code (`node/src` folder) JavaScript, places it into the `node/lib` directory an watches for changes in the TypeScript files.\n\n### `npm run worker:build`\n\nBuilds the `mediasoup-worker` binary. It invokes `invoke`below.\n\n### `npm run worker:prebuild-name`\n\nPrints the name of the corresponding `mediasoup-worker` prebuild tar file.\n\n### `npm run worker:prebuild`\n\nCreates a prebuilt of `mediasoup-worker` binary in the `worker/prebuild` folder.\n\n### `npm run lint`\n\nRuns both `lint:node` and `lint:worker` tasks.\n\n### `npm run lint:node`\n\nValidates mediasoup TypeScript files using [ESLint](https://eslint.org), [Prettier](https://prettier.io) and [Knip](https://knip.dev/).\n\n### `npm run lint:worker`\n\nValidates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke lint` below.\n\nSee [Install clang-format](#install-clang-format) for requirements.\n\n### `npm run format`\n\nRuns both `format:node` and `format:worker` tasks.\n\n### `npm run format:node`\n\nFormat TypeScript and JavaScript code using [Prettier](https://prettier.io).\n\n### `npm run format:worker`\n\nRewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke format` below.\n\nSee [Install clang-format](#install-clang-format) for requirements.\n\n### `npm run tidy:worker`\n\nRuns [clang-tidy](http://clang.llvm.org/extra/clang-tidy) and performs C++ code checks following `worker/.clang-tidy` rules. It invokes `invoke tidy` below.\n\nSee [Install clang-tidy](#install-clang-tidy) for requirements.\n\n### `npm run tidy:worker:fix`\n\nSame as `npm run tidy:worker` but it also applies fixes.\n\n### `npm run flatc`\n\nRuns both `flatc:node` and `flatc:worker` tasks.\n\n### `npm run flatc:node`\n\nCompiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to TypeScript code.\n\n### `npm run flatc:worker`\n\nCompiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to C++ code.\n\n### `npm run test`\n\nRuns both `test:node` and `test:worker` tasks.\n\n### `npm run test:node`\n\nRuns [Jest](https://jestjs.io) test units located at `node/test` folder.\n\nJest command arguments can be given using `--` as follows:\n\n```bash\nnpm run test:node -- --testPathPatterns \"node/src/test/test-Worker.ts\" --testNamePattern \"createWorker\"\n```\n\n### `npm run test:worker`\n\nRuns [Catch2](https://github.com/catchorg/Catch2) test units located at `worker/test` folder. It invokes `invoke test` below.\n\n### `npm run coverage`\n\nRuns `coverage:node` task.\n\n### `npm run coverage:node`\n\nSame as `test:node` task but it also opens a browser window with TypeScript coverage results.\n\n### `npm run release:check`\n\nRuns linters and tests in Node and C++ code.\n\n### `npm run release`\n\nPublishes a new NPM version of mediasoup. Requirements for it to work:\n\n- \"version\" field in `package.json` must have been incremented (and not commited to Git).\n- `CHANGELOG.md` file must have been updated with an entry matching the new version.\n- Of course, permissions to publish in NPM registry are required.\n\n## Rust\n\nThe only special feature in Rust case is special environment variable \"KEEP_BUILD_ARTIFACTS\", that when set to \"1\" will allow incremental recompilation of changed C++ sources during hacking on mediasoup.\n\nIt is not necessary for normal usage of mediasoup as a dependency.\n\n## Python Invoke and `tasks.py` file\n\nmediasoup uses Python [Invoke](https://www.pyinvoke.org) library for managing and organizing tasks in the `worker` folder (mediasoup worker C++ subproject). `Invoke` is basically a replacemente of `make` + `Makefile` written in Python. mediasoup automatically installs `Invoke` in a local custom path during the installation process (in both Node and Rust) so the user doesn't need to worry about it.\n\nTasks are defined in `worker/tasks.py`. For development purposes, developers or contributors can install `Invoke` using `pip3 install invoke` and run tasks below within the `worker` folder.\n\nSee all the tasks by running `invoke --list` within the `worker` folder.\n\n_NOTE:_ For some of these tasks to work, npm dependencies of `worker/scripts/package.json` must be installed:\n\n```bash\nnpm ci --prefix worker/scripts\n```\n\n### `invoke` (default task)\n\nAlias of `invoke mediasoup-worker` task below.\n\n### `invoke meson-ninja`\n\nInstalls `meson` and `ninja` into a local custom path.\n\n### `invoke clean`\n\nCleans built objects and binaries.\n\n### `invoke clean-build`\n\nCleans built objects and other artifacts, but keeps `mediasoup-worker` binary in place.\n\n### `invoke clean-pip`\n\nCleans `meson` and `ninja` installed in local prefix with pip.\n\n### `invoke clean-subprojects`\n\nCleans subprojects downloaded with Meson.\n\n### `invoke clean-all`\n\nCleans built objects and binaries, `meson` and `ninja` installed in local prefix with pip and all subprojects downloaded with Meson.\n\n### `invoke update-wrap-file [subproject]`\n\nUpdates the wrap file of a subproject (those in `worker/subprojects` folder) with Meson. After updating it, `invoke setup` must be called by passing `MESON_ARGS=\"--reconfigure\"` environment variable. Usage example:\n\n```bash\ncd worker\ninvoke update-wrap-file openssl\nMESON_ARGS=\"--reconfigure\" invoke setup\n```\n\n### `invoke mediasoup-worker`\n\nBuilds the `mediasoup-worker` binary at `worker/out/Release`.\n\nIf the \"MEDIASOUP_MAX_CORES\" environment variable is set, the build process will use that number of CPU cores. Otherwise it will auto-detect the number of cores in the machine.\n\n\"MEDIASOUP_BUILDTYPE\" environment variable controls build types, \"Release\" and \"Debug\" are presets optimized for those use cases. Other build types are possible too, but they are not presets and will require \"MESON_ARGS\" use to customize build configuration.\n\nCheck the meaning of useful macros in the `worker/include/Logger.hpp` header file if you want to enable tracing or other debug information.\n\nBinary is built at `worker/out/MEDIASOUP_BUILDTYPE/build`.\n\nIn order to instruct the mediasoup Node.js module to use the \"Debug\" mediasoup-worker` binary, an environment variable must be set before running the Node.js application:\n\n```bash\nMEDIASOUP_BUILDTYPE=Debug node myapp.js\n```\n\nIf the \"MEDIASOUP_WORKER_BIN\" environment variable is set (it must be an absolute file path), mediasoup will use the it as `mediasoup-worker` binary and **won't** compile the binary:\n\n```bash\nMEDIASOUP_WORKER_BIN=\"/home/xxx/src/foo/mediasoup-worker\" node myapp.js\n```\n\n### `invoke libmediasoup-worker`\n\nBuilds the `libmediasoup-worker` static library at `worker/out/Release`.\n\n\"MEDIASOUP_MAX_CORES\"` and \"MEDIASOUP_BUILDTYPE\" environment variables from above still apply for static library build.\n\n### `invoke xcode`\n\nBuilds a Xcode project for the mediasoup worker subproject.\n\n### `invoke lint`\n\nValidates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and rules in `worker/.clang-format`.\n\n**Requirements:**\n\n- A specific version of `clang-format`is required. See [Install clang-format](#install-clang-format).\n- `clang-format-VERSION` or `clang-format` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command.\n\n### `invoke format`\n\nRewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html).\n\n**Requirements:**\n\n- A specific version of `clang-format`is required. See [Install clang-format](#install-clang-format).\n- `clang-format-VERSION` or `clang-format` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command.\n\n### `invoke tidy`\n\nRuns [clang-tidy](http://clang.llvm.org/extra/clang-tidy) and performs C++ code checks following `worker/.clang-tidy` rules.\n\n**Requirements:**\n\n- `invoke clean` must have been called first.\n- A specific version of `clang-tidy`is required. See [Install clang-tidy](#install-clang-tidy).\n- `clang-tidy-VERSION` or `clang-tidy` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command. Same for other `clang-tidy` related executables such as `run-clang-tidy` and `clang-apply-replacements`,\n\n**Environment variables:**\n\n- \"MEDIASOUP_TIDY_CHECKS\": Optional. Comma separated list of checks. Overrides the checks defined in `worker/.clang-tidy` file.\n- \"MEDIASOUP_TIDY_FILES\": Optional. Space separated source file paths to process. All `.cpp` files will be processes by default.\n  - File paths must be relative to `worker/` folder.\n  - File paths can use [glob](https://github.com/isaacs/node-glob) syntax. Example: `\"src/RTC/SCTP/**/*.cpp\"`.\n\n**Usage example in macOS:**\n\n```bash\nPATH=\"/opt/homebrew/opt/llvm/bin/:$PATH\" invoke tidy\n```\n\nIt may happens that `clang-tidy` doesn't know where C++ standard libraries are so it shows lot of warnings about them. Depending on your local setup this may work:\n\n```bash\nPATH=\"/opt/homebrew/opt/llvm/bin/:$PATH\" CPATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 invoke tidy\n```\n\n### `invoke tidy-fix`\n\nSame as `invoke tidy` but it also applies fixes.\n\n### `invoke test`\n\nBuilds and runs the `mediasoup-worker-test` binary at `worker/out/Release` (or at `worker/out/Debug` if the \"MEDIASOUP_BUILDTYPE\" environment variable is set to \"Debug\"), which uses [Catch2](https://github.com/catchorg/Catch2) to run test units located at `worker/test` folder.\n\n### `invoke test-asan-address`\n\nRun test with Address Sanitizer with `-fsanitize=address`.\n\n### `invoke test-asan-undefined`\n\nRun test with Address Sanitizer with `-fsanitize=undefined`.\n\n### `invoke fuzzer`\n\nBuilds the `mediasoup-worker-fuzzer` binary (which uses [libFuzzer](http://llvm.org/docs/LibFuzzer.html)) at `worker/out/Release` (or at `worker/out/Debug/` if the \"MEDIASOUP_BUILDTYPE\" environment variable is set to \"Debug\").\n\n**Requirements:**\n\n- Linux with fuzzer capable clang++.\n- \"CC\" environment variable must point to `clang`.\n- \"CXX\" environment variable must point to `clang++`.\n\nRead the [Fuzzer](Fuzzer.md) documentation for detailed information.\n\n### `invoke fuzzer-run-all`\n\nRuns all fuzzer cases.\n\n### `invoke docker`\n\nBuilds a Linux Ubuntu Docker image with fuzzer capable clang++ and all dependencies to run mediasoup.\n\n### `invoke docker-run`\n\nRuns a container of the Ubuntu Docker image created with `invoke docker`. It automatically executes a `bash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder.\n\n**NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`.\n\n### `invoke docker-alpine`\n\nBuilds a Linux Alpine Docker image with all dependencies to run mediasoup.\n\n### `invoke docker-alpine-run`\n\nRuns a container of the Alpine Docker image created with `invoke docker-alpine`. It automatically executes an `ash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder.\n\n**NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`.\n\n### `invoke docker-386`\n\nBuilds a 386 Linux Debian (32 bits arch) Docker image with all dependencies to run mediasoup.\n\n### `invoke docker-alpine-386`\n\nRuns a container of the 386 Linux Debian (32 bits arch) Docker image created with `invoke docker-386`. It automatically executes an `ash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder.\n\n**NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`.\n**NOTE:** Due to the very old Node v18 in this image, in order to run mediasoup Node tests, `npm ci` must be executed with `--ignore-scripts --engine-strict=false` arguments.\n\n## Makefile\n\nThe `worker` folder contains a `Makefile` file for the mediasoup worker C++ subproject. It acts as a proxy to the `Invoke` tasks defined in `tasks.py`. The `Makefile` file exists to help developers or contributors that prefer keep using `make` commands.\n\nAll tasks defined in `tasks.py` (see above) are available in `Makefile`. There is only one exception:\n\n- The `update-wrap-file` needs a \"SUBPROJECT\" environment variable indicating the subproject to update. Usage example:\n  ```bash\n  cd worker\n  make update-wrap-file SUBPROJECT=openssl\n  ```\n\n## Install clang-format\n\nA specific `clang-format` version is required to be installed in the system, which is defined in [clang-scripts.mjs](../worker/scripts/clang-scripts.mjs).\n\nmacOS:\n\n```bash\nbrew install clang-format@VERSION\n```\n\nLinux:\n\n```bash\napt-get install clang-format-VERSION\n```\n\n## Install clang-tidy\n\nA specific `clang-tidy` version is required to be installed in the system, which is defined in [clang-scripts.mjs](../worker/scripts/clang-scripts.mjs).\n\nmacOS:\n\n```bash\nbrew install clang-tidy@VERSION\n```\n\nLinux:\n\n```bash\napt-get install clang-tidy-VERSION\n```\n"
  },
  {
    "path": "doc/Charts.md",
    "content": "# Charts\n\n## Broadcasting\n\nmediasoup **v2** (a room uses a single media worker subprocess by design, so a single CPU).\n\nCharts provided by [CoSMo](https://www.cosmosoftware.io) team.\n\nScenario:\n\n- 1 peer producing audio and video tracks.\n- N spy peers receiving them.\n\n#### Bandwidth out (Mbps) / number of viewers\n\n![](charts/mediasoup_SFU_BW_out.png)\n\n#### Packets per second / number of viewers\n\n![](charts/mediasoup_SFU_packetspersec.png)\n\n#### Average bitrate (bps) and googRTT (ms) / number of viewers\n\n![](charts/mediasoup_clients_getstats.png)\n\n#### CPU usage / number of viewers\n\n![](charts/mediasoup_SFU_cpu.png)\n"
  },
  {
    "path": "doc/Closures.md",
    "content": "# Closures\n\nSome considerations:\n\n- Any JS `xxxxx.yyyyyClosed()` method is equivalent to the corresponding C++ `~Xxxxx()` destructor. They both silently destroy things without generating any internal notifications/events.\n  - _NOTE:_ Yes, the JS `xxxxx.yyyyyClosed()` produces **public** JS event `xxxxx.on('yyyyyclose')`, but that's not internal stuff.\n\n## JS worker.close()\n\n- Public API.\n- Kills the mediasoup-worker process (if not already died) via signal.\n- Iterates all JS Routers and calls `router.workerClosed()`.\n\n## mediasoup-worker process dies unexpectely\n\n- The JS Worker emits public JS `worker.on('died')`.\n- Iterates all JS Routers and calls `router.workerClosed()`.\n\n## C++ Worker::Close()\n\n- Called when the mediasoup-worker process is killed.\n- Iterates all C++ Routers and calls `delete router`.\n\n## JS router.workerClosed()\n\n- Private API.\n- Emits public JS `router.on('workerclose')`.\n\n## JS router.close()\n\n- Public API.\n- Sends channel request `WORKER_CLOSE_ROUTER`:\n  - Processed by the C++ Worker.\n  - It removes the C++ Router from its map.\n  - It calls C++ `delete router`.\n- Iterates all JS Transports and calls `transport.routerClosed()`.\n- Emits private JS `router.on('@close')` (so the JS Worker cleans its map).\n\n## C++ ~Router() destructor\n\n- Iterates all C++ Transports and calls `delete transport`.\n\n## JS transport.routerClosed()\n\n- Private API.\n- Iterates all JS Producers and calls `producer.transportClosed()`.\n- Iterates all JS Consumers and calls `consumer.transportClosed()`.\n- Emits public JS `transport.on('routerclose')`.\n\n## JS transport.close()\n\n- Public API.\n- Sends channel request `ROUTER_CLOSE_TRANSPORT`.\n  - Processed by the C++ Router.\n  - It calls C++ `transport->Close()` (so the C++ Transport will notify the C++ Router about closed Producers and Consumers in that Transport).\n  - It removes the C++ Transport from its map.\n  - It calls C++ `delete transport`.\n- Iterates all JS Producers and calls `producer.transportClosed()`.\n- For each JS Producer, the JS Transport emits private JS `transport.on('@producerclose')` (so the JS Router cleans its maps).\n- Iterates all JS Consumers and calls `consumer.transportClosed()`.\n- Emits private JS `transport.on('@close')` (so the JS Router cleans its map).\n\n## C++ ~Transport() destructor\n\n- Iterates all C++ Producers and calls `delete producer`.\n- Iterates all C++ Consumer and calls `delete consumer`.\n\n## C++ Transport::Close()\n\n- Iterates all C++ Producers. For each Producer:\n  - Removes it from its map of Producers.\n  - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClosed()` on its associated Consumers).\n  - Calls `delete producer`.\n- It clears its map of C++ Producers.\n- Iterates all C++ Consumer. For each Consumer:\n  - Removes it from its map of Consumers.\n  - Call its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps).\n  - Calls `delete consumer`.\n- It clears its map of C++ Consumers.\n\n_NOTE:_ If a Transport holds a Producer and a Consumer associated to that Producer, ugly things may happen when calling `Transport::Close()`:\n\n- While iterating the C++ Producers as above, the C++ Consumer would be deleted (via `Consumer::ProducerClosed()`).\n  - As far as it's properly removed from the `Transport::mapConsumers` everything would be ok when later iterating the map of Consumers.\n- Must ensure that, in this scenario, the JS event `consumer.on('producerclose')` is not called since `consumer.on('transportclose')` is supposed to happen before.\n  - This won't happen since the JS `Consumer` has removed its channel notifications within its `transportClosed()` method.\n\n## C++ Router::OnTransportProducerClosed(transport, producer)\n\n- Gets the set of C++ Consumers associated to the closed Producer in its `mapProducerConsumers`. For each Consumer:\n  - Calls `consumer->ProducerClosed()`.\n- Deletes the entry in `mapProducerConsumers` with key `producer`.\n- Deletes the entry in `mapProducers` with key `producer->id`.\n\n## C++ Router::OnTransportConsumerClosed(transport, consumer)\n\n- Get the associated C++ Producer from `mapConsumerProducer`.\n- Remove the closed C++ Consumer from the set of Consumers in the corresponding `mapProducerConsumers` entry for the given Producer.\n- Deletes the entry in `mapConsumerProducer` with key `consumer`.\n\n## JS producer.transportClosed()\n\n- Private API.\n- Emits public JS `producer.on('transportclose')`.\n\n## JS producer.close()\n\n- Public API.\n- Sends channel request `TRANSPORT_CLOSE_PRODUCER`.\n  - Processed by the C++ Transport.\n  - Removes it from its map of Producers.\n  - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClose()` on its associated Consumers).\n  - Calls `delete producer`.\n- Emits private JS `producer.on('@close')` (so the JS Transport cleans its map and will also emit private JS `transport.on('@producerclose')` so the JS Router cleans its map).\n\n## C++ ~Producer() destructor\n\n- Destroys its stuff.\n\n## JS consumer.transportClosed()\n\n- Private API.\n- Emits public JS `consumer.on('transportclose')`.\n\n## JS consumer.close()\n\n- Public API.\n- Sends channel request `TRANSPORT_CLOSE_CONSUMER`.\n  - Processed by the C++ Transport.\n  - Removes it from its map of Consumers.\n  - Calls its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps).\n  - Calls `delete consumer`.\n- Emits private JS `consumer.on('@close')` (so the JS Transport cleans its map).\n\n## C++ ~Consumer() destructor\n\n- Destroys its stuff.\n\n## C++ Consumer::ProducerClosed()\n\n- Called from the C++ Router within the `Router::OnTransportProducerClosed()` listener.\n- Send a channel notification `producerclose` to the JS Consumer.\n  - The JS Consumer emits private JS `consumer.on('@produceclose')` (so the JS Transport cleans its map).\n  - The JS Consumer emits public JS `consumer.on('produceclose')`.\n- Notifies its C++ Transport via `listener->onConsumerProducerClosed()` which:\n  - cleans its map of Consumers,\n  - notifies the Router via `listener->OnTransportConsumerClosed()`), and\n  - deletes the Consumer.\n"
  },
  {
    "path": "doc/Fuzzer.md",
    "content": "# Fuzzer\n\nOnce we have built the `mediasoup-worker-fuzzer` target in a Linux environment with `fuzzer` capable clang (see `make fuzzer` documentation in [Building](Building.md)) we can then execute the fuzzer binary at `worker/out/Release/mediasoup-worker-fuzzer`.\n\n**NOTE:** From now on, we assume we are in the mediasoup `worker` directory.\n\n## Related documentation\n\n- [libFuzzer documentation](http://llvm.org/docs/LibFuzzer.html)\n- [libFuzzer Tutorial](https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md)\n- [webrtcH4cKS ~ Lets get better at fuzzing in 2019](https://webrtchacks.com/lets-get-better-at-fuzzing-in-2019-heres-how/)\n- [OSS-Fuzz](https://github.com/google/oss-fuzz) - Continuous fuzzing of open source software (\"fuzz for me\")\n\n## Corpus files\n\nThe `deps/webrtc-fuzzer-corpora/corpora` directory has corpus directories taken from the [webrtc-fuzzer-corpora](https://github.com/RTC-Cartel/webrtc-fuzzer-corpora) project. They should be use to feed fuzzer with appropriate input.\n\nHowever, given how `libFuzzer` [works](http://llvm.org/docs/LibFuzzer.html#options), the first directory given as command line parameter is not just used for reading corpus files, but also to store newly generated ones. So, it's recommended to pass `fuzzer/new-corpus` as first directory. Such a directory is gitignored.\n\n## Crash reports\n\nWhen the fuzzer detects an issue it generates a crash report file which contains the bytes given as input. Those files can be individually used later (instead of passing corpus directories) to the fuzzer to verify that the issue has been fixed.\n\nThe `fuzzer/reports` directory should be used to store those new crash reports. In order to use it, the following option must be given to the fuzzer executable:\n\n```bash\n-artifact_prefix=fuzzer/reports/\n```\n\n## Others\n\nIt's recommended to (also) pass the following options to the fuzzer:\n\n- `-max_len=1400`: We don't need much more input size.\n\nFor memory leak detection enable the following environment variable:\n\n- `LSAN_OPTIONS=verbosity=1:log_threads=1`\n\nThe mediasoup-worker fuzzer reads some custom environment variables to decide which kind of fuzzing perform:\n\n- `MS_FUZZ_STUN=1`: Enable STUN fuzzer.\n- `MS_FUZZ_DTLS=1`: Enable DTLS fuzzer.\n- `MS_FUZZ_SCTP=1`: Enable SCTP fuzzer.\n- `MS_FUZZ_RTP=1`: Enable RTP fuzzer.\n- `MS_FUZZ_RTCP=1`: Enable RTCP fuzzer.\n- `MS_FUZZ_CODECS=1`: Enable audio/video codecs fuzzer.\n- `MS_FUZZ_UTILS=1`: Enable C++ utils fuzzer.\n- If none of them is given, then **all** fuzzers are enabled.\n\nThe log level can also be set by setting the `MS_FUZZ_LOG_LEVEL` environment variable to \"debug\", \"warn\" or \"error\" (it is \"none\" if unset).\n\n## Usage examples\n\n- Detect memory leaks and just fuzz STUN:\n\n```bash\nMS_FUZZ_STUN=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus\n```\n\n- Detect memory leaks and just fuzz DTLS:\n\n```bash\nMS_FUZZ_DTLS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus\n```\n\n- Detect memory leaks and just fuzz SCTP:\n\n```bash\nMS_FUZZ_SCTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus\n```\n\n- Detect memory leaks and just fuzz RTP:\n\n```bash\nMS_FUZZ_RTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus\n```\n\n- Detect memory leaks and just fuzz RTCP:\n\n```bash\nMS_FUZZ_RTCP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus\n```\n\n- Detect memory leaks and just fuzz audio/video codecs:\n\n```bash\nMS_FUZZ_CODECS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus\n```\n\n- Detect memory leaks and just fuzz mediasoup-worker C++ utils:\n\n```bash\nMS_FUZZ_UTILS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=2000 fuzzer/new-corpus\n```\n\n- Detect memory leaks and fuzz everything with log level \"warn\" and enable log tags \"rtp\" and \"rtcp\":\n\n```bash\nMS_FUZZ_LOG_LEVEL=warn MS_FUZZ_LOG_TAGS=\"rtp rtcp\" LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus\n```\n\n- Verify that a specific crash is fixed:\n\n```bash\nLSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer fuzzer/reports/crash-f39771f7a03c0e7e539d4e52f48f7adad8976404\n```\n"
  },
  {
    "path": "doc/README.md",
    "content": "# Internal Documentation\n\n**NOTE:** Internal documentation for developing purposes. Get the mediasoup public documentation at [mediasoup.org](https://mediasoup.org).\n\n- [Building](Building.md)\n- [Fuzzer](Fuzzer.md)\n- [Rust-crates](Rust-crates.md)\n- [RTCP](RTCP.md)\n- [Closures](Closures.md)\n- [Charts](Charts.md)\n"
  },
  {
    "path": "doc/RTCP.md",
    "content": "# RTCP\n\nThis documentation describes how RTCP packets are processed in mediasoup.\n\nBeing a SFU, some received RTCP packets are locally consumed and others are ignored.\n\nThis document also describes which RTCP packets are locally generated by mediasoup.\n\nmediasoup does not forward feedback information from a remote RTP receiver, which could incur in the remote sender modifying the transmition rate in a way that it would affect the reception quality for the overall participants in a router (i.e. limiting the remote RTP sender transmition rate).\n\n### Sender Reports\n\nmediasoup locally generates the Sender Reports of the streams it sends and processes the Sender Reports is receives from producer endpoints.\n\n### Receiver Reports\n\nmediasoup locally generates the Receiver Reports of the streams it receives and consumes the Receiver Reports from every remote RTP receivers.\n\nThe combination of Sender and Receiver Reports are used to determine the quality of each link. Information that is available at the JavaScript API level in order to determine the quality of each participant in the router.\n\n### SDES\n\nSDES information is locally consumed.\n\n### BYE\n\nThis information is ignored.\n\n### APP\n\nThis information is ignored.\n\n### XR\n\nCurrently not implemented (ignored). The same logic should be applied as to Receiver Reports.\n\n## RTP Feedback\n\n### NACK\n\nReceived NACK requests are locally consumed. The solicited RTP packets are re-sent to the remote RTP receiver that requested them.\n\nmediasoup locally generate NACK requests for remote senders.\n\n### TMMBR / TMMBN\n\n```\nRFC 5104:\nA receiver, translator, or mixer uses the Temporary Maximum Media\nStream Bit Rate Request (TMMBR, \"timber\") to request a sender to\nlimit the maximum bit rate for a media stream (see section 2.2) to,\nor below, the provided value.\n```\n\nThis information is ignored.\n\nAs for now, we do not limit the bit rate for the media streams as we are relying on locally generated reception reports (RR) to make the remote RTP senders adjust their transmition rates given such values.\n\n### RTCP-SR-REQ\n\n```\nRFC 6051:\nThis memo outlines how RTP sessions are synchronised, and discusses\nhow rapidly such synchronisation can occur.  We show that most RTP\nsessions can be synchronised immediately, but that the use of video\nswitching multipoint conference units (MCUs) or large source-specific\nmulticast (SSM) groups can greatly increase the synchronisation\ndelay.  This increase in delay can be unacceptable to some\napplications that use layered and/or multi-description codecs.\n```\n\nThis information is ignored.\n\n### RAMS\n\nThis information is ignored.\n\n### TLLEI\n\nThis information is ignored. In future it could be useful to acknowledge remote RTP receivers when mediasoup generates NACK request in order to avoid them sending them, which could generate the so called \"feedback storm\" or \"NACK storm\".\n\n```\nRFC 6642:\nThe RTCP TPLR message can be used by the intermediaries to\ninform the receiver that the sender of the RTCP TPLR has received\nreports that the indicated packets were lost and ask the receiver not\nto send feedback to it regarding these packets.\n```\n\n### RTCP-ECN-FB\n\nThis information is ignored.\n\n### PAUSE-RESUME\n\nThis information is ignored (and not even implemented in any RTP/WebRTC endpoint).\n\n### Transport-wide Congestion Control (TCC)\n\nPlanned for v3.\n\n## PS Feedback\n\n### PLI\n\nThis information is locally consumed and generates a PLI request to the corresponding RTP sender.\n\nAlso it is locally sent when required. This is: a new participant joins the conference for a fast rendering by the rest of participants.\n\n### SLI\n\nThis information is ignored.\n\n### RPSI\n\nThis information is ignored.\n\n### FIR\n\nThis information is locally consumed and generates a PLI request to the corresponding RTP sender.\n\n### TSTR/TSTN\n\nThis information is ignored.\n\n```\nRFC 5104:\nThe Temporal-Spatial Trade-off Request (TSTR) instructs the video\nencoder to change its trade-off between temporal and spatial\nresolution.  Index values from 0 to 31 indicate monotonically a\ndesire for higher frame rate.\n```\n\n### VBCM\n\nThis information is ignored.\n\n### PSLEI\n\nSame applicability as **TLLEI**.\n\n### AFB\n\nThis information is ignored except for the REMB messages, which is locally consumed.\n\n### REMB\n\nThis information is locally consumed to perform sender side bandwidth estimation.\n\nREMB RTCP is generated locally based on the remote bitrate estimation.\n\n## Mediasoup internal behaviour for each type of RTCP\n\n### Generic RTCP\n\n|          | SR  | RR  | SDES | BYE | APP |\n| -------- | --- | --- | ---- | --- | --- |\n| Consumer | G   | C   |      |     |     |\n| Producer | C   | G   | C    | I   | I   |\n\n### RTP Feedback RTCP\n\n|          | NACK | TMMBR | TMMBN | TLLEI | ECN-FB | PAUSE-RESUME | TCC |\n| -------- | ---- | ----- | ----- | ----- | ------ | ------------ | --- |\n| Consumer | C    | I     | I     | I     | I      | I            |     |\n| Producer | G    |       | I     | I     |        |              |     |\n\n## PS Feedback RTCP\n\n|           | PLI | SLI | RPSI | FIR | TSTR | TSTN | VBCM | PSLI | AFB | REMB |\n| --------- | --- | --- | ---- | --- | ---- | ---- | ---- | ---- | --- | ---- |\n| Consumer  | C   | I   | I    | C   | I    |      | I    |      | I   | C    |\n| Producer  | G   |     |      |     |      | I    |      | I    |     |      |\n| Transport |     |     |      |     |      |      |      |      |     | G    |\n\n( ): Does not apply.\n\n(I): Ignore.\n\n(C): Consume locally.\n\n(B): Bypass.\n\n(G): Generate locally.\n"
  },
  {
    "path": "doc/Rust-crates.md",
    "content": "# Rust crates\n\nThere are 3 crates: `mediasoup`, `mediasoup-sys` and `mediasoup-types`:\n\n- `mediasoup-sys` crate wraps C++ worker into Rust.\n- `mediasoup-types` crate defines and exposes mediasoup Rust types.\n- `mediasoup` crate uses `mediasoup-sys` and `mediasoup-types` and it exposes nice user API in idiomatic Rust.\n- `mediasoup-sys` is the only one that needs updating if changes are purely inside the worker or inside the `mediasoup-sys` crate. You can bump them all, but it is not required.\n- If `mediasoup-sys`'s API changes in a breaking way, then its minor version needs to be changed, otherwise patch version needs to be changed. Same for `mediasoup-types` crate.\n\n**Important:** Adding new APIs that `mediasoup` crate has to understand to continue working normally is a breaking change because it'll start crashing/printing errors if unexpected things happen.\n\n## Steps to publish new mediasoup crates\n\n1. Update versions in `worker/Cargo.toml` (for `mediasoup-sys` crate), `rust/types/Cargo.toml` (for `mediasoup-types` crate) and `rust/Cargo.toml` (for `mediasoup` crate). Note that in `rust/Cargo.toml` you may need to update the version of `[dependencies.mediasoup-sys]` and/or `[dependencies.mediasoup-types]` if it also changed.\n2. Update `rust/CHANGELOG.md`.\n3. Run `cargo build` to reflect changes in `Cargo.lock`.\n4. Create PR and have it merged in mediasoup main branch.\n5. Upload Git tags (the new one in `rust/CHANGELOG.md`, so the new `mediasoup` crate version):\n\n```sh\ngit tag -a rust-X.X.X -m rust-X.X.X\ngit push origin rust-X.X.X\n```\n\n6. Publish crates (you need an account and permissions and so on):\n\n```sh\ncd rust/types\ncargo publish\n\ncd worker\ncargo publish\n\ncd rust\ncargo publish\n```\n\n## Notes\n\n- Depending on the state in `worker` directory you may need to run `invoke clean-all` or `make clean-all` in `worker` directory first.\n- `cargo publish` will create the crate package, check if all necessary dependencies are already present on [crates.io](https://crates.io/), will then compile the package (to ensure that you don't publish a broken version) and will upload it to [crates.io](https://crates.io/).\n- Never publish from random branches or local state that is not on GitHub. If you have local files modified Cargo will refuse to publish until you commit all the changes.\n\n## Extras\n\n### Check crate without publishing\n\nIf you want to do everything except publishing itself, `cargo package` command exists. You can also run `cargo package --dry-run` to avoid package generation or `cargo publish --dry-run`.\n\n### Update required Rust version\n\nUsing `rustup` command:\n\n```sh\nrustup update\n```\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import eslint from '@eslint/js';\nimport tsEslint from 'typescript-eslint';\nimport jestEslint from 'eslint-plugin-jest';\nimport prettierRecommendedEslint from 'eslint-plugin-prettier/recommended';\nimport globals from 'globals';\n\nconst config = tsEslint.config(\n\t{\n\t\tlanguageOptions: {\n\t\t\tsourceType: 'module',\n\t\t\tglobals: { ...globals.node },\n\t\t},\n\t\tlinterOptions: {\n\t\t\tnoInlineConfig: false,\n\t\t\treportUnusedDisableDirectives: 'error',\n\t\t},\n\t},\n\teslint.configs.recommended,\n\tprettierRecommendedEslint,\n\t{\n\t\trules: {\n\t\t\t'constructor-super': 2,\n\t\t\tcurly: [2, 'all'],\n\t\t\t// Unfortunatelly `curly` does not apply to blocks in `switch` cases so\n\t\t\t// this is needed.\n\t\t\t'no-restricted-syntax': [\n\t\t\t\t2,\n\t\t\t\t{\n\t\t\t\t\tselector: 'SwitchCase > *.consequent[type!=\"BlockStatement\"]',\n\t\t\t\t\tmessage: 'Switch cases without blocks are disallowed',\n\t\t\t\t},\n\t\t\t],\n\t\t\t'guard-for-in': 2,\n\t\t\t'newline-after-var': 2,\n\t\t\t'newline-before-return': 2,\n\t\t\t'no-alert': 2,\n\t\t\t'no-caller': 2,\n\t\t\t'no-case-declarations': 2,\n\t\t\t'no-catch-shadow': 2,\n\t\t\t'no-class-assign': 2,\n\t\t\t'no-console': 2,\n\t\t\t'no-const-assign': 2,\n\t\t\t'no-debugger': 2,\n\t\t\t'no-dupe-args': 2,\n\t\t\t'no-dupe-keys': 2,\n\t\t\t'no-duplicate-case': 2,\n\t\t\t'no-div-regex': 2,\n\t\t\t'no-empty': [2, { allowEmptyCatch: true }],\n\t\t\t'no-empty-pattern': 2,\n\t\t\t'no-eval': 2,\n\t\t\t'no-extend-native': 2,\n\t\t\t'no-ex-assign': 2,\n\t\t\t'no-extra-bind': 2,\n\t\t\t'no-extra-boolean-cast': 2,\n\t\t\t'no-extra-label': 2,\n\t\t\t'no-fallthrough': 2,\n\t\t\t'no-func-assign': 2,\n\t\t\t'no-global-assign': 2,\n\t\t\t'no-implicit-coercion': 2,\n\t\t\t'no-implicit-globals': 2,\n\t\t\t'no-inner-declarations': 2,\n\t\t\t'no-invalid-regexp': 2,\n\t\t\t'no-invalid-this': 2,\n\t\t\t'no-irregular-whitespace': 2,\n\t\t\t'no-lonely-if': 2,\n\t\t\t'no-multi-str': 2,\n\t\t\t'no-native-reassign': 2,\n\t\t\t'no-negated-in-lhs': 2,\n\t\t\t'no-new': 2,\n\t\t\t'no-new-func': 2,\n\t\t\t'no-new-wrappers': 2,\n\t\t\t'no-obj-calls': 2,\n\t\t\t'no-proto': 2,\n\t\t\t'no-prototype-builtins': 0,\n\t\t\t'no-redeclare': 2,\n\t\t\t'no-regex-spaces': 2,\n\t\t\t'no-restricted-imports': 2,\n\t\t\t'no-return-assign': 2,\n\t\t\t'no-self-assign': 2,\n\t\t\t'no-self-compare': 2,\n\t\t\t'no-sequences': 2,\n\t\t\t'no-shadow': 2,\n\t\t\t'no-shadow-restricted-names': 2,\n\t\t\t'no-sparse-arrays': 2,\n\t\t\t'no-this-before-super': 2,\n\t\t\t'no-throw-literal': 2,\n\t\t\t'no-undef': 2,\n\t\t\t'no-unmodified-loop-condition': 2,\n\t\t\t'no-unreachable': 2,\n\t\t\t'no-unused-vars': [\n\t\t\t\t2,\n\t\t\t\t{ vars: 'all', args: 'after-used', caughtErrors: 'none' },\n\t\t\t],\n\t\t\t'no-unused-private-class-members': 2,\n\t\t\t'no-use-before-define': 0,\n\t\t\t'no-useless-call': 2,\n\t\t\t'no-useless-computed-key': 2,\n\t\t\t'no-useless-concat': 2,\n\t\t\t'no-useless-rename': 2,\n\t\t\t'no-var': 2,\n\t\t\t'object-curly-newline': 0,\n\t\t\t'prefer-const': 2,\n\t\t\t'prefer-rest-params': 2,\n\t\t\t'prefer-spread': 2,\n\t\t\t'prefer-template': 2,\n\t\t\t'spaced-comment': [2, 'always'],\n\t\t\tstrict: 2,\n\t\t\t'valid-typeof': 2,\n\t\t\tyoda: 2,\n\t\t},\n\t},\n\t// NOTE: We need to apply this only to .ts source files (and not to .mjs\n\t// files).\n\t...tsEslint.configs.recommendedTypeChecked.map(item => ({\n\t\t...item,\n\t\tfiles: ['node/src/**/*.ts'],\n\t})),\n\t// NOTE: We need to apply this only to .ts source files (and not to .mjs\n\t// files).\n\t...tsEslint.configs.stylisticTypeChecked.map(item => ({\n\t\t...item,\n\t\tfiles: ['node/src/**/*.ts'],\n\t})),\n\t{\n\t\tname: '.ts source files',\n\t\tfiles: ['node/src/**/*.ts'],\n\t\tlanguageOptions: {\n\t\t\tparserOptions: {\n\t\t\t\tproject: 'tsconfig.json',\n\t\t\t},\n\t\t},\n\t\trules: {\n\t\t\t'@typescript-eslint/class-literal-property-style': [2, 'getters'],\n\t\t\t'@typescript-eslint/consistent-generic-constructors': [\n\t\t\t\t2,\n\t\t\t\t'type-annotation',\n\t\t\t],\n\t\t\t'@typescript-eslint/dot-notation': 0,\n\t\t\t'@typescript-eslint/no-unused-vars': [\n\t\t\t\t2,\n\t\t\t\t{\n\t\t\t\t\tvars: 'all',\n\t\t\t\t\targs: 'after-used',\n\t\t\t\t\tcaughtErrors: 'none',\n\t\t\t\t\tignoreRestSiblings: false,\n\t\t\t\t},\n\t\t\t],\n\t\t\t// We want to use `type` instead of `interface`.\n\t\t\t'@typescript-eslint/consistent-type-definitions': 0,\n\t\t\t'@typescript-eslint/prefer-nullish-coalescing': [\n\t\t\t\t0,\n\t\t\t\t{\n\t\t\t\t\tignorePrimitives: { string: true },\n\t\t\t\t},\n\t\t\t],\n\t\t\t'@typescript-eslint/explicit-function-return-type': [\n\t\t\t\t2,\n\t\t\t\t{ allowExpressions: true },\n\t\t\t],\n\t\t\t'@typescript-eslint/no-unsafe-member-access': 0,\n\t\t\t'@typescript-eslint/no-unsafe-assignment': 0,\n\t\t\t'@typescript-eslint/no-unsafe-call': 0,\n\t\t\t'@typescript-eslint/no-unsafe-return': 0,\n\t\t\t'@typescript-eslint/no-unsafe-argument': 0,\n\t\t\t'@typescript-eslint/consistent-indexed-object-style': 0,\n\t\t\t'@typescript-eslint/no-empty-function': 0,\n\t\t\t'@typescript-eslint/restrict-template-expressions': 0,\n\t\t\t'@typescript-eslint/no-duplicate-type-constituents': [\n\t\t\t\t2,\n\t\t\t\t{ ignoreUnions: true },\n\t\t\t],\n\t\t\t'@typescript-eslint/no-redundant-type-constituents': 0,\n\t\t},\n\t},\n\t{\n\t\tname: '.ts test files',\n\t\t...jestEslint.configs['flat/recommended'],\n\t\tfiles: ['node/src/test/**/*.ts'],\n\t\trules: {\n\t\t\t...jestEslint.configs['flat/recommended'].rules,\n\t\t\t'jest/no-disabled-tests': 2,\n\t\t\t'jest/prefer-expect-assertions': 0,\n\t\t\t'@typescript-eslint/no-unnecessary-type-assertion': 0,\n\t\t},\n\t}\n);\n\nexport default config;\n"
  },
  {
    "path": "jest.config.mjs",
    "content": "const config = {\n\tverbose: true,\n\ttestEnvironment: 'node',\n\ttestRegex: 'node/src/test/test-.*\\\\.ts',\n\ttransform: {\n\t\t// transpilation: true is needed to avoid warnigns. However we lose TS\n\t\t// checks. We don't care since we have TS tasks for that.\n\t\t// See https://kulshekhar.github.io/ts-jest/docs/getting-started/options/transpilation\n\t\t'^.+\\\\.ts?$': ['ts-jest', { transpilation: true }],\n\t},\n\tcoveragePathIgnorePatterns: [\n\t\t'node/src/Logger.ts',\n\t\t'node/src/enhancedEvents.ts',\n\t\t'node/src/fbs',\n\t\t'node/src/test',\n\t],\n\tmodulePathIgnorePatterns: ['worker', 'rust', 'target'],\n\tcacheDirectory: '.cache/jest',\n};\n\nexport default config;\n"
  },
  {
    "path": "knip.config.mjs",
    "content": "const config = {\n\t$schema: 'https://unpkg.com/knip@5/schema.json',\n\tproject: ['node/src/**/*.ts'],\n\tignore: [\n\t\t'node/src/fbsUtils.ts',\n\t\t'node/src/rtpParametersFbsUtils.ts',\n\t\t'node/src/rtpStreamStatsFbsUtils.ts',\n\t\t'node/src/srtpParametersFbsUtils.ts',\n\t],\n\tignoreDependencies: ['open-cli', 'supports-color'],\n\ttypescript: {\n\t\tconfig: ['tsconfig.json'],\n\t},\n\tjest: {\n\t\tconfig: ['jest.config.mjs'],\n\t\tentry: ['node/src/test/**/*.ts'],\n\t},\n};\n\nexport default config;\n"
  },
  {
    "path": "node/src/ActiveSpeakerObserver.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tActiveSpeakerObserver,\n\tActiveSpeakerObserverDominantSpeaker,\n\tActiveSpeakerObserverEvents,\n\tActiveSpeakerObserverObserver,\n\tActiveSpeakerObserverObserverEvents,\n} from './ActiveSpeakerObserverTypes';\nimport type { RtpObserver } from './RtpObserverTypes';\nimport { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver';\nimport type { AppData } from './types';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer';\n\ntype RtpObserverObserverConstructorOptions<ActiveSpeakerObserverAppData> =\n\tRtpObserverConstructorOptions<ActiveSpeakerObserverAppData>;\n\nconst logger = new Logger('ActiveSpeakerObserver');\n\nexport class ActiveSpeakerObserverImpl<\n\tActiveSpeakerObserverAppData extends AppData = AppData,\n>\n\textends RtpObserverImpl<\n\t\tActiveSpeakerObserverAppData,\n\t\tActiveSpeakerObserverEvents,\n\t\tActiveSpeakerObserverObserver\n\t>\n\timplements RtpObserver, ActiveSpeakerObserver\n{\n\tconstructor(\n\t\toptions: RtpObserverObserverConstructorOptions<ActiveSpeakerObserverAppData>\n\t) {\n\t\tconst observer: ActiveSpeakerObserverObserver =\n\t\t\tnew EnhancedEventEmitter<ActiveSpeakerObserverObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'activespeaker' {\n\t\treturn 'activespeaker';\n\t}\n\n\toverride get observer(): ActiveSpeakerObserverObserver {\n\t\treturn super.observer;\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.rtpObserverId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsActiveSpeakerObserver.DominantSpeakerNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst producer = this.getProducerById(notification.producerId()!);\n\n\t\t\t\t\t\tif (!producer) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst dominantSpeaker: ActiveSpeakerObserverDominantSpeaker = {\n\t\t\t\t\t\t\tproducer,\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tthis.safeEmit('dominantspeaker', dominantSpeaker);\n\t\t\t\t\t\tthis.observer.safeEmit('dominantspeaker', dominantSpeaker);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "node/src/ActiveSpeakerObserverTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tRtpObserver,\n\tRtpObserverEvents,\n\tRtpObserverObserverEvents,\n} from './RtpObserverTypes';\nimport type { Producer } from './ProducerTypes';\nimport type { AppData } from './types';\n\nexport type ActiveSpeakerObserverOptions<\n\tActiveSpeakerObserverAppData extends AppData = AppData,\n> = {\n\tinterval?: number;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: ActiveSpeakerObserverAppData;\n};\n\nexport type ActiveSpeakerObserverDominantSpeaker = {\n\t/**\n\t * The audio Producer instance.\n\t */\n\tproducer: Producer;\n};\n\nexport type ActiveSpeakerObserverEvents = RtpObserverEvents & {\n\tdominantspeaker: [ActiveSpeakerObserverDominantSpeaker];\n};\n\nexport type ActiveSpeakerObserverObserver =\n\tEnhancedEventEmitter<ActiveSpeakerObserverObserverEvents>;\n\nexport type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & {\n\tdominantspeaker: [ActiveSpeakerObserverDominantSpeaker];\n};\n\nexport interface ActiveSpeakerObserver<\n\tActiveSpeakerObserverAppData extends AppData = AppData,\n> extends RtpObserver<\n\tActiveSpeakerObserverAppData,\n\tActiveSpeakerObserverEvents,\n\tActiveSpeakerObserverObserver\n> {\n\t/**\n\t * RtpObserver type.\n\t *\n\t * @override\n\t */\n\tget type(): 'activespeaker';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): ActiveSpeakerObserverObserver;\n}\n"
  },
  {
    "path": "node/src/AudioLevelObserver.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tAudioLevelObserver,\n\tAudioLevelObserverVolume,\n\tAudioLevelObserverEvents,\n\tAudioLevelObserverObserver,\n\tAudioLevelObserverObserverEvents,\n} from './AudioLevelObserverTypes';\nimport type { RtpObserver } from './RtpObserverTypes';\nimport { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver';\nimport type { Producer } from './ProducerTypes';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsAudioLevelObserver from './fbs/audio-level-observer';\n\ntype AudioLevelObserverConstructorOptions<AudioLevelObserverAppData> =\n\tRtpObserverConstructorOptions<AudioLevelObserverAppData>;\n\nconst logger = new Logger('AudioLevelObserver');\n\nexport class AudioLevelObserverImpl<\n\tAudioLevelObserverAppData extends AppData = AppData,\n>\n\textends RtpObserverImpl<\n\t\tAudioLevelObserverAppData,\n\t\tAudioLevelObserverEvents,\n\t\tAudioLevelObserverObserver\n\t>\n\timplements RtpObserver, AudioLevelObserver\n{\n\tconstructor(\n\t\toptions: AudioLevelObserverConstructorOptions<AudioLevelObserverAppData>\n\t) {\n\t\tconst observer: AudioLevelObserverObserver =\n\t\t\tnew EnhancedEventEmitter<AudioLevelObserverObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'audiolevel' {\n\t\treturn 'audiolevel';\n\t}\n\n\toverride get observer(): AudioLevelObserverObserver {\n\t\treturn super.observer;\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.rtpObserverId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.AUDIOLEVELOBSERVER_VOLUMES: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsAudioLevelObserver.VolumesNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\t// Get the corresponding Producer instance and remove entries with\n\t\t\t\t\t\t// no Producer (it may have been closed in the meanwhile).\n\t\t\t\t\t\tconst volumes: AudioLevelObserverVolume[] = fbsUtils\n\t\t\t\t\t\t\t.parseVector(notification, 'volumes', parseVolume)\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\t({\n\t\t\t\t\t\t\t\t\tproducerId,\n\t\t\t\t\t\t\t\t\tvolume,\n\t\t\t\t\t\t\t\t}: {\n\t\t\t\t\t\t\t\t\tproducerId: string;\n\t\t\t\t\t\t\t\t\tvolume: number;\n\t\t\t\t\t\t\t\t}) => ({\n\t\t\t\t\t\t\t\t\tproducer: this.getProducerById(producerId)!,\n\t\t\t\t\t\t\t\t\tvolume,\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(({ producer }: { producer: Producer }) => producer);\n\n\t\t\t\t\t\tif (volumes.length > 0) {\n\t\t\t\t\t\t\tthis.safeEmit('volumes', volumes);\n\n\t\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\t\tthis.observer.safeEmit('volumes', volumes);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.AUDIOLEVELOBSERVER_SILENCE: {\n\t\t\t\t\t\tthis.safeEmit('silence');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('silence');\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction parseVolume(binary: FbsAudioLevelObserver.Volume): {\n\tproducerId: string;\n\tvolume: number;\n} {\n\treturn {\n\t\tproducerId: binary.producerId()!,\n\t\tvolume: binary.volume(),\n\t};\n}\n"
  },
  {
    "path": "node/src/AudioLevelObserverTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tRtpObserver,\n\tRtpObserverEvents,\n\tRtpObserverObserverEvents,\n} from './RtpObserverTypes';\nimport type { Producer } from './ProducerTypes';\nimport type { AppData } from './types';\n\nexport type AudioLevelObserverOptions<\n\tAudioLevelObserverAppData extends AppData = AppData,\n> = {\n\t/**\n\t * Maximum number of entries in the 'volumes”' event. Default 1.\n\t */\n\tmaxEntries?: number;\n\n\t/**\n\t * Minimum average volume (in dBvo from -127 to 0) for entries in the\n\t * 'volumes' event.\tDefault -80.\n\t */\n\tthreshold?: number;\n\n\t/**\n\t * Interval in ms for checking audio volumes. Default 1000.\n\t */\n\tinterval?: number;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: AudioLevelObserverAppData;\n};\n\nexport type AudioLevelObserverVolume = {\n\t/**\n\t * The audio Producer instance.\n\t */\n\tproducer: Producer;\n\n\t/**\n\t * The average volume (in dBvo from -127 to 0) of the audio Producer in the\n\t * last interval.\n\t */\n\tvolume: number;\n};\n\nexport type AudioLevelObserverEvents = RtpObserverEvents & {\n\tvolumes: [AudioLevelObserverVolume[]];\n\tsilence: [];\n};\n\nexport type AudioLevelObserverObserver =\n\tEnhancedEventEmitter<AudioLevelObserverObserverEvents>;\n\nexport type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & {\n\tvolumes: [AudioLevelObserverVolume[]];\n\tsilence: [];\n};\n\nexport interface AudioLevelObserver<\n\tAudioLevelObserverAppData extends AppData = AppData,\n> extends RtpObserver<\n\tAudioLevelObserverAppData,\n\tAudioLevelObserverEvents,\n\tAudioLevelObserverObserver\n> {\n\t/**\n\t * RtpObserver type.\n\t *\n\t * @override\n\t */\n\tget type(): 'audiolevel';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): AudioLevelObserverObserver;\n}\n"
  },
  {
    "path": "node/src/Channel.ts",
    "content": "import * as os from 'node:os';\nimport type { Duplex } from 'node:stream';\nimport { info, warn } from 'node:console';\nimport * as flatbuffers from 'flatbuffers';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport { InvalidStateError } from './errors';\nimport { Body as RequestBody, Method, Request } from './fbs/request';\nimport { Response } from './fbs/response';\nimport { Message, Body as MessageBody } from './fbs/message';\nimport {\n\tNotification,\n\tBody as NotificationBody,\n\tEvent,\n} from './fbs/notification';\nimport { Log } from './fbs/log';\n\nconst IS_LITTLE_ENDIAN = os.endianness() === 'LE';\n\nconst logger = new Logger('Channel');\n\ntype Sent = {\n\tid: number;\n\tmethod: string;\n\tresolve: (data: Response | PromiseLike<Response>) => void;\n\treject: (error: Error) => void;\n\tclose: () => void;\n};\n\n// Binary length for a 4194304 bytes payload.\nconst MESSAGE_MAX_LEN = 4194308;\nconst PAYLOAD_MAX_LEN = 4194304;\n\nexport class Channel extends EnhancedEventEmitter {\n\t// Closed flag.\n\t#closed = false;\n\n\t// Unix Socket instance for sending messages to the worker process.\n\treadonly #producerSocket: Duplex;\n\n\t// Unix Socket instance for receiving messages to the worker process.\n\treadonly #consumerSocket: Duplex;\n\n\t// Next id for messages sent to the worker process.\n\t#nextId = 0;\n\n\t// Map of pending sent requests.\n\treadonly #sents: Map<number, Sent> = new Map();\n\n\t// Buffer for reading messages from the worker.\n\t#recvBuffer: Buffer = Buffer.alloc(0);\n\n\t// flatbuffers builder.\n\t#bufferBuilder: flatbuffers.Builder = new flatbuffers.Builder(1024);\n\n\tconstructor({\n\t\tproducerSocket,\n\t\tconsumerSocket,\n\t\tpid,\n\t}: {\n\t\tproducerSocket: Duplex;\n\t\tconsumerSocket: Duplex;\n\t\tpid: number;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#producerSocket = producerSocket;\n\t\tthis.#consumerSocket = consumerSocket;\n\n\t\t// Read Channel responses/notifications from the worker.\n\t\tthis.#consumerSocket.on('data', (buffer: Buffer) => {\n\t\t\tif (!this.#recvBuffer.length) {\n\t\t\t\tthis.#recvBuffer = buffer;\n\t\t\t} else {\n\t\t\t\tthis.#recvBuffer = Buffer.concat(\n\t\t\t\t\t[this.#recvBuffer, buffer],\n\t\t\t\t\tthis.#recvBuffer.length + buffer.length\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (this.#recvBuffer.length > PAYLOAD_MAX_LEN) {\n\t\t\t\tlogger.error('receiving buffer is full, discarding all data in it');\n\n\t\t\t\t// Reset the buffer and exit.\n\t\t\t\tthis.#recvBuffer = Buffer.alloc(0);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet msgStart = 0;\n\n\t\t\twhile (true) {\n\t\t\t\tconst readLen = this.#recvBuffer.length - msgStart;\n\n\t\t\t\tif (readLen < 4) {\n\t\t\t\t\t// Incomplete data.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst dataView = new DataView(\n\t\t\t\t\tthis.#recvBuffer.buffer,\n\t\t\t\t\tthis.#recvBuffer.byteOffset + msgStart\n\t\t\t\t);\n\t\t\t\tconst msgLen = dataView.getUint32(0, IS_LITTLE_ENDIAN);\n\n\t\t\t\tif (readLen < 4 + msgLen) {\n\t\t\t\t\t// Incomplete data.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst payload = this.#recvBuffer.subarray(\n\t\t\t\t\tmsgStart + 4,\n\t\t\t\t\tmsgStart + 4 + msgLen\n\t\t\t\t);\n\n\t\t\t\tmsgStart += 4 + msgLen;\n\n\t\t\t\tconst buf = new flatbuffers.ByteBuffer(new Uint8Array(payload));\n\t\t\t\tconst message = Message.getRootAsMessage(buf);\n\n\t\t\t\ttry {\n\t\t\t\t\tswitch (message.dataType()) {\n\t\t\t\t\t\tcase MessageBody.Response: {\n\t\t\t\t\t\t\tconst response = new Response();\n\n\t\t\t\t\t\t\tmessage.data(response);\n\n\t\t\t\t\t\t\tthis.processResponse(response);\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase MessageBody.Notification: {\n\t\t\t\t\t\t\tconst notification = new Notification();\n\n\t\t\t\t\t\t\tmessage.data(notification);\n\n\t\t\t\t\t\t\tthis.processNotification(notification);\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase MessageBody.Log: {\n\t\t\t\t\t\t\tconst log = new Log();\n\n\t\t\t\t\t\t\tmessage.data(log);\n\n\t\t\t\t\t\t\tthis.processLog(pid, log);\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault: {\n\t\t\t\t\t\t\twarn(\n\t\t\t\t\t\t\t\t`worker[pid:${pid}] unexpected data: ${payload.toString(\n\t\t\t\t\t\t\t\t\t'utf8',\n\t\t\t\t\t\t\t\t\t1\n\t\t\t\t\t\t\t\t)}`\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`received invalid message from the worker process: ${error}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (msgStart != 0) {\n\t\t\t\tthis.#recvBuffer = this.#recvBuffer.slice(msgStart);\n\t\t\t}\n\t\t});\n\n\t\tthis.#consumerSocket.on('end', () => {\n\t\t\tlogger.debug('Consumer Channel ended by the worker process');\n\t\t});\n\n\t\tthis.#consumerSocket.on('error', error => {\n\t\t\tlogger.error(`Consumer Channel error: ${error}`);\n\t\t});\n\n\t\tthis.#producerSocket.on('end', () => {\n\t\t\tlogger.debug('Producer Channel ended by the worker process');\n\t\t});\n\n\t\tthis.#producerSocket.on('error', error => {\n\t\t\tlogger.error(`Producer Channel error: ${error}`);\n\t\t});\n\t}\n\n\t/**\n\t * flatbuffer builder.\n\t */\n\tget bufferBuilder(): flatbuffers.Builder {\n\t\treturn this.#bufferBuilder;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Close every pending sent.\n\t\tfor (const sent of this.#sents.values()) {\n\t\t\tsent.close();\n\t\t}\n\n\t\t// Remove event listeners but leave a fake 'error' hander to avoid\n\t\t// propagation.\n\t\tthis.#consumerSocket.removeAllListeners('end');\n\t\tthis.#consumerSocket.removeAllListeners('error');\n\t\tthis.#consumerSocket.on('error', () => {});\n\n\t\tthis.#producerSocket.removeAllListeners('end');\n\t\tthis.#producerSocket.removeAllListeners('error');\n\t\tthis.#producerSocket.on('error', () => {});\n\n\t\t// Destroy the sockets.\n\t\ttry {\n\t\t\tthis.#producerSocket.destroy();\n\t\t} catch (error) {}\n\t\ttry {\n\t\t\tthis.#consumerSocket.destroy();\n\t\t} catch (error) {}\n\t}\n\n\tnotify(\n\t\tevent: Event,\n\t\tbodyType?: NotificationBody,\n\t\tbodyOffset?: number,\n\t\thandlerId?: string\n\t): void {\n\t\tlogger.debug(`notify() [event:${Event[event]}]`);\n\n\t\tif (this.#closed) {\n\t\t\tthrow new InvalidStateError(\n\t\t\t\t`Channel closed, cannot send notification [event:${Event[event]}]`\n\t\t\t);\n\t\t}\n\n\t\tconst handlerIdOffset = this.#bufferBuilder.createString(handlerId ?? '');\n\n\t\tlet notificationOffset: number;\n\n\t\tif (bodyType && bodyOffset) {\n\t\t\tnotificationOffset = Notification.createNotification(\n\t\t\t\tthis.#bufferBuilder,\n\t\t\t\thandlerIdOffset,\n\t\t\t\tevent,\n\t\t\t\tbodyType,\n\t\t\t\tbodyOffset\n\t\t\t);\n\t\t} else {\n\t\t\tnotificationOffset = Notification.createNotification(\n\t\t\t\tthis.#bufferBuilder,\n\t\t\t\thandlerIdOffset,\n\t\t\t\tevent,\n\t\t\t\tNotificationBody.NONE,\n\t\t\t\t0\n\t\t\t);\n\t\t}\n\n\t\tconst messageOffset = Message.createMessage(\n\t\t\tthis.#bufferBuilder,\n\t\t\tMessageBody.Notification,\n\t\t\tnotificationOffset\n\t\t);\n\n\t\t// Finalizes the buffer and adds a 4 byte prefix with the size of the buffer.\n\t\tthis.#bufferBuilder.finishSizePrefixed(messageOffset);\n\n\t\t// Create a new buffer with this data so multiple contiguous flatbuffers\n\t\t// do not point to the builder buffer overriding others info.\n\t\tconst buffer = new Uint8Array(this.#bufferBuilder.asUint8Array());\n\n\t\t// Clear the buffer builder so it's reused for the next request.\n\t\tthis.#bufferBuilder.clear();\n\n\t\tif (buffer.byteLength > MESSAGE_MAX_LEN) {\n\t\t\tlogger.error(`notify() | notification too big [event:${Event[event]}]`);\n\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\t// This may throw if closed or remote side ended.\n\t\t\tthis.#producerSocket.write(buffer, 'binary');\n\t\t} catch (error) {\n\t\t\tlogger.error(`notify() | sending notification failed: ${error}`);\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\tasync request(\n\t\tmethod: Method,\n\t\tbodyType?: RequestBody,\n\t\tbodyOffset?: number,\n\t\thandlerId?: string\n\t): Promise<Response> {\n\t\tlogger.debug(`request() [method:${Method[method]}]`);\n\n\t\tif (this.#closed) {\n\t\t\tthrow new InvalidStateError(\n\t\t\t\t`Channel closed, cannot send request [method:${Method[method]}]`\n\t\t\t);\n\t\t}\n\n\t\tif (this.#nextId < 4294967295) {\n\t\t\t++this.#nextId;\n\t\t} else {\n\t\t\tthis.#nextId = 1;\n\t\t}\n\n\t\tconst id = this.#nextId;\n\t\tconst handlerIdOffset = this.#bufferBuilder.createString(handlerId ?? '');\n\n\t\tlet requestOffset: number;\n\n\t\tif (bodyType && bodyOffset) {\n\t\t\trequestOffset = Request.createRequest(\n\t\t\t\tthis.#bufferBuilder,\n\t\t\t\tid,\n\t\t\t\tmethod,\n\t\t\t\thandlerIdOffset,\n\t\t\t\tbodyType,\n\t\t\t\tbodyOffset\n\t\t\t);\n\t\t} else {\n\t\t\trequestOffset = Request.createRequest(\n\t\t\t\tthis.#bufferBuilder,\n\t\t\t\tid,\n\t\t\t\tmethod,\n\t\t\t\thandlerIdOffset,\n\t\t\t\tRequestBody.NONE,\n\t\t\t\t0\n\t\t\t);\n\t\t}\n\n\t\tconst messageOffset = Message.createMessage(\n\t\t\tthis.#bufferBuilder,\n\t\t\tMessageBody.Request,\n\t\t\trequestOffset\n\t\t);\n\n\t\t// Finalizes the buffer and adds a 4 byte prefix with the size of the buffer.\n\t\tthis.#bufferBuilder.finishSizePrefixed(messageOffset);\n\n\t\t// Create a new buffer with this data so multiple contiguous flatbuffers\n\t\t// do not point to the builder buffer overriding others info.\n\t\tconst buffer = new Uint8Array(this.#bufferBuilder.asUint8Array());\n\n\t\t// Clear the buffer builder so it's reused for the next request.\n\t\tthis.#bufferBuilder.clear();\n\n\t\tif (buffer.byteLength > MESSAGE_MAX_LEN) {\n\t\t\tthrow new Error(`request too big [method:${Method[method]}]`);\n\t\t}\n\n\t\t// This may throw if closed or remote side ended.\n\t\tthis.#producerSocket.write(buffer, 'binary');\n\n\t\treturn new Promise((pResolve, pReject) => {\n\t\t\tconst sent: Sent = {\n\t\t\t\tid: id,\n\t\t\t\tmethod: Method[method],\n\t\t\t\tresolve: data2 => {\n\t\t\t\t\tif (!this.#sents.delete(id)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tpResolve(data2);\n\t\t\t\t},\n\t\t\t\treject: error => {\n\t\t\t\t\tif (!this.#sents.delete(id)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tpReject(error);\n\t\t\t\t},\n\t\t\t\tclose: () => {\n\t\t\t\t\tpReject(\n\t\t\t\t\t\tnew InvalidStateError(\n\t\t\t\t\t\t\t`Channel closed, pending request aborted [method:${Method[method]}, id:${id}]`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Add sent stuff to the map.\n\t\t\tthis.#sents.set(id, sent);\n\t\t});\n\t}\n\n\tprivate processResponse(response: Response): void {\n\t\tconst sent = this.#sents.get(response.id());\n\n\t\tif (!sent) {\n\t\t\tlogger.error(\n\t\t\t\t`received response does not match any sent request [id:${response.id()}]`\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (response.accepted()) {\n\t\t\tlogger.debug(`request succeeded [method:${sent.method}, id:${sent.id}]`);\n\n\t\t\tsent.resolve(response);\n\t\t} else if (response.error()) {\n\t\t\tlogger.warn(\n\t\t\t\t`request failed [method:${sent.method}, id:${\n\t\t\t\t\tsent.id\n\t\t\t\t}]: ${response.reason()}`\n\t\t\t);\n\n\t\t\tswitch (response.error()!) {\n\t\t\t\tcase 'TypeError': {\n\t\t\t\t\tsent.reject(new TypeError(response.reason()!));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault: {\n\t\t\t\t\tsent.reject(new Error(response.reason()!));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlogger.error(\n\t\t\t\t`received response is not accepted nor rejected [method:${sent.method}, id:${sent.id}]`\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate processNotification(notification: Notification): void {\n\t\t// Due to how Promises work, it may happen that we receive a response\n\t\t// from the worker followed by a notification from the worker. If we\n\t\t// emit the notification immediately it may reach its target **before**\n\t\t// the response, destroying the ordered delivery. So we must wait a bit\n\t\t// here.\n\t\t// See https://github.com/versatica/mediasoup/issues/510\n\t\tsetImmediate(() =>\n\t\t\tthis.emit(notification.handlerId()!, notification.event(), notification)\n\t\t);\n\t}\n\n\tprivate processLog(pid: number, log: Log): void {\n\t\tconst logData = log.data()!;\n\n\t\tswitch (logData[0]) {\n\t\t\t// 'D' (a debug log).\n\t\t\tcase 'D': {\n\t\t\t\tlogger.debug(`[pid:${pid}] ${logData.slice(1)}`);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// 'W' (a warn log).\n\t\t\tcase 'W': {\n\t\t\t\tlogger.warn(`[pid:${pid}] ${logData.slice(1)}`);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// 'E' (a error log).\n\t\t\tcase 'E': {\n\t\t\t\tlogger.error(`[pid:${pid}] ${logData.slice(1)}`);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// 'X' (a dump log).\n\t\t\tcase 'X': {\n\t\t\t\tinfo(logData.slice(1));\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node/src/Consumer.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tConsumer,\n\tConsumerType,\n\tConsumerScore,\n\tConsumerLayers,\n\tConsumerDump,\n\tSimpleConsumerDump,\n\tSimulcastConsumerDump,\n\tSvcConsumerDump,\n\tPipeConsumerDump,\n\tBaseConsumerDump,\n\tRtpStreamDump,\n\tRtpStreamParametersDump,\n\tRtxStreamDump,\n\tRtxStreamParameters,\n\tConsumerStat,\n\tConsumerTraceEventType,\n\tConsumerTraceEventData,\n\tConsumerEvents,\n\tConsumerObserver,\n\tConsumerObserverEvents,\n} from './ConsumerTypes';\nimport { Channel } from './Channel';\nimport type { TransportInternal } from './Transport';\nimport type { ProducerStat } from './ProducerTypes';\nimport type { MediaKind, RtpParameters } from './rtpParametersTypes';\nimport {\n\tparseRtpEncodingParameters,\n\tparseRtpParameters,\n} from './rtpParametersFbsUtils';\nimport { parseRtpStreamStats } from './rtpStreamStatsFbsUtils';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Event, Notification } from './fbs/notification';\nimport { TraceDirection as FbsTraceDirection } from './fbs/common';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsConsumer from './fbs/consumer';\nimport * as FbsConsumerTraceInfo from './fbs/consumer/trace-info';\nimport * as FbsRtpStream from './fbs/rtp-stream';\nimport * as FbsRtxStream from './fbs/rtx-stream';\nimport { Type as FbsRtpParametersType } from './fbs/rtp-parameters';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\n\ntype ConsumerInternal = TransportInternal & {\n\tconsumerId: string;\n};\n\ntype ConsumerData = {\n\tproducerId: string;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n\ttype: ConsumerType;\n};\n\nconst logger = new Logger('Consumer');\n\nexport class ConsumerImpl<ConsumerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<ConsumerEvents>\n\timplements Consumer\n{\n\t// Internal data.\n\treadonly #internal: ConsumerInternal;\n\n\t// Consumer data.\n\treadonly #data: ConsumerData;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Custom app data.\n\t#appData: ConsumerAppData;\n\n\t// Paused flag.\n\t#paused = false;\n\n\t// Associated Producer paused flag.\n\t#producerPaused = false;\n\n\t// Current priority.\n\t#priority = 1;\n\n\t// Current score.\n\t#score: ConsumerScore;\n\n\t// Preferred layers.\n\t#preferredLayers?: ConsumerLayers;\n\n\t// Curent layers.\n\t#currentLayers?: ConsumerLayers;\n\n\t// Observer instance.\n\treadonly #observer: ConsumerObserver =\n\t\tnew EnhancedEventEmitter<ConsumerObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tdata,\n\t\tchannel,\n\t\tappData,\n\t\tpaused,\n\t\tproducerPaused,\n\t\tscore = { score: 10, producerScore: 10, producerScores: [] },\n\t\tpreferredLayers,\n\t}: {\n\t\tinternal: ConsumerInternal;\n\t\tdata: ConsumerData;\n\t\tchannel: Channel;\n\t\tappData?: ConsumerAppData;\n\t\tpaused: boolean;\n\t\tproducerPaused: boolean;\n\t\tscore?: ConsumerScore;\n\t\tpreferredLayers?: ConsumerLayers;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#data = data;\n\t\tthis.#channel = channel;\n\t\tthis.#paused = paused;\n\t\tthis.#producerPaused = producerPaused;\n\t\tthis.#score = score;\n\t\tthis.#preferredLayers = preferredLayers;\n\t\tthis.#appData = appData ?? ({} as ConsumerAppData);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.consumerId;\n\t}\n\n\tget producerId(): string {\n\t\treturn this.#data.producerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget kind(): MediaKind {\n\t\treturn this.#data.kind;\n\t}\n\n\tget rtpParameters(): RtpParameters {\n\t\treturn this.#data.rtpParameters;\n\t}\n\n\tget type(): ConsumerType {\n\t\treturn this.#data.type;\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#paused;\n\t}\n\n\tget producerPaused(): boolean {\n\t\treturn this.#producerPaused;\n\t}\n\n\tget priority(): number {\n\t\treturn this.#priority;\n\t}\n\n\tget score(): ConsumerScore {\n\t\treturn this.#score;\n\t}\n\n\tget preferredLayers(): ConsumerLayers | undefined {\n\t\treturn this.#preferredLayers;\n\t}\n\n\tget currentLayers(): ConsumerLayers | undefined {\n\t\treturn this.#currentLayers;\n\t}\n\n\tget appData(): ConsumerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: ConsumerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): ConsumerObserver {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t *\n\t * @private\n\t */\n\tget channelForTesting(): Channel {\n\t\treturn this.#channel;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.consumerId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.CloseConsumerRequestT(\n\t\t\tthis.#internal.consumerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.TRANSPORT_CLOSE_CONSUMER,\n\t\t\t\tFbsRequest.Body.Transport_CloseConsumerRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.#internal.transportId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\ttransportClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('transportClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.consumerId);\n\n\t\tthis.safeEmit('transportclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<ConsumerDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsConsumer.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parseConsumerDumpResponse(data);\n\t}\n\n\tasync getStats(): Promise<(ConsumerStat | ProducerStat)[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsConsumer.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parseConsumerStats(data);\n\t}\n\n\tasync pause(): Promise<void> {\n\t\tlogger.debug('pause()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_PAUSE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = true;\n\n\t\t// Emit observer event.\n\t\tif (!wasPaused && !this.#producerPaused) {\n\t\t\tthis.#observer.safeEmit('pause');\n\t\t}\n\t}\n\n\tasync resume(): Promise<void> {\n\t\tlogger.debug('resume()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_RESUME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = false;\n\n\t\t// Emit observer event.\n\t\tif (wasPaused && !this.#producerPaused) {\n\t\t\tthis.#observer.safeEmit('resume');\n\t\t}\n\t}\n\n\tasync setPreferredLayers({\n\t\tspatialLayer,\n\t\ttemporalLayer,\n\t}: ConsumerLayers): Promise<void> {\n\t\tlogger.debug('setPreferredLayers()');\n\n\t\tif (typeof spatialLayer !== 'number') {\n\t\t\tthrow new TypeError('spatialLayer must be a number');\n\t\t}\n\t\tif (temporalLayer && typeof temporalLayer !== 'number') {\n\t\t\tthrow new TypeError('if given, temporalLayer must be a number');\n\t\t}\n\n\t\tconst builder = this.#channel.bufferBuilder;\n\n\t\tconst preferredLayersOffset =\n\t\t\tFbsConsumer.ConsumerLayers.createConsumerLayers(\n\t\t\t\tbuilder,\n\t\t\t\tspatialLayer,\n\t\t\t\ttemporalLayer ?? null\n\t\t\t);\n\t\tconst requestOffset =\n\t\t\tFbsConsumer.SetPreferredLayersRequest.createSetPreferredLayersRequest(\n\t\t\t\tbuilder,\n\t\t\t\tpreferredLayersOffset\n\t\t\t);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_SET_PREFERRED_LAYERS,\n\t\t\tFbsRequest.Body.Consumer_SetPreferredLayersRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsConsumer.SetPreferredLayersResponse();\n\n\t\tlet preferredLayers: ConsumerLayers | undefined;\n\n\t\t// Response is empty for non Simulcast Consumers.\n\t\tif (response.body(data)) {\n\t\t\tconst status = data.unpack();\n\n\t\t\tif (status.preferredLayers) {\n\t\t\t\tpreferredLayers = {\n\t\t\t\t\tspatialLayer: status.preferredLayers.spatialLayer,\n\t\t\t\t\ttemporalLayer: status.preferredLayers.temporalLayer ?? undefined,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tthis.#preferredLayers = preferredLayers;\n\t}\n\n\tasync setPriority(priority: number): Promise<void> {\n\t\tlogger.debug('setPriority()');\n\n\t\tif (typeof priority !== 'number' || priority < 0) {\n\t\t\tthrow new TypeError('priority must be a positive number');\n\t\t}\n\n\t\tconst requestOffset =\n\t\t\tFbsConsumer.SetPriorityRequest.createSetPriorityRequest(\n\t\t\t\tthis.#channel.bufferBuilder,\n\t\t\t\tpriority\n\t\t\t);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_SET_PRIORITY,\n\t\t\tFbsRequest.Body.Consumer_SetPriorityRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\n\t\tconst data = new FbsConsumer.SetPriorityResponse();\n\n\t\tresponse.body(data);\n\n\t\tconst status = data.unpack();\n\n\t\tthis.#priority = status.priority;\n\t}\n\n\tasync unsetPriority(): Promise<void> {\n\t\tlogger.debug('unsetPriority()');\n\n\t\tawait this.setPriority(1);\n\t}\n\n\tasync requestKeyFrame(): Promise<void> {\n\t\tlogger.debug('requestKeyFrame()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_REQUEST_KEY_FRAME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\t}\n\n\tasync enableTraceEvent(types: ConsumerTraceEventType[] = []): Promise<void> {\n\t\tlogger.debug('enableTraceEvent()');\n\n\t\tif (!Array.isArray(types)) {\n\t\t\tthrow new TypeError('types must be an array');\n\t\t}\n\t\tif (types.find(type => typeof type !== 'string')) {\n\t\t\tthrow new TypeError('every type must be a string');\n\t\t}\n\n\t\t// Convert event types.\n\t\tconst fbsEventTypes: FbsConsumer.TraceEventType[] = [];\n\n\t\tfor (const eventType of types) {\n\t\t\ttry {\n\t\t\t\tfbsEventTypes.push(consumerTraceEventTypeToFbs(eventType));\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn('enableTraceEvent() | [error:${error}]');\n\t\t\t}\n\t\t}\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsConsumer.EnableTraceEventRequestT(\n\t\t\tfbsEventTypes\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.CONSUMER_ENABLE_TRACE_EVENT,\n\t\t\tFbsRequest.Body.Consumer_EnableTraceEventRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.consumerId\n\t\t);\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.#channel.on(\n\t\t\tthis.#internal.consumerId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.CONSUMER_PRODUCER_CLOSE: {\n\t\t\t\t\t\tif (this.#closed) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#closed = true;\n\n\t\t\t\t\t\t// Remove notification subscriptions.\n\t\t\t\t\t\tthis.#channel.removeAllListeners(this.#internal.consumerId);\n\n\t\t\t\t\t\tthis.emit('@producerclose');\n\t\t\t\t\t\tthis.safeEmit('producerclose');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('close');\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_PRODUCER_PAUSE: {\n\t\t\t\t\t\tif (this.#producerPaused) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#producerPaused = true;\n\n\t\t\t\t\t\tthis.safeEmit('producerpause');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tif (!this.#paused) {\n\t\t\t\t\t\t\tthis.#observer.safeEmit('pause');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_PRODUCER_RESUME: {\n\t\t\t\t\t\tif (!this.#producerPaused) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#producerPaused = false;\n\n\t\t\t\t\t\tthis.safeEmit('producerresume');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tif (!this.#paused) {\n\t\t\t\t\t\t\tthis.#observer.safeEmit('resume');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_SCORE: {\n\t\t\t\t\t\tconst notification = new FbsConsumer.ScoreNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst score: ConsumerScore = notification.score()!.unpack();\n\n\t\t\t\t\t\tthis.#score = score;\n\n\t\t\t\t\t\tthis.safeEmit('score', score);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('score', score);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_LAYERS_CHANGE: {\n\t\t\t\t\t\tconst notification = new FbsConsumer.LayersChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst layers: ConsumerLayers | undefined = notification.layers()\n\t\t\t\t\t\t\t? parseConsumerLayers(notification.layers()!)\n\t\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\t\tthis.#currentLayers = layers;\n\n\t\t\t\t\t\tthis.safeEmit('layerschange', layers);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('layerschange', layers);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsConsumer.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace: ConsumerTraceEventData =\n\t\t\t\t\t\t\tparseTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.CONSUMER_RTP: {\n\t\t\t\t\t\tif (this.#closed) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst notification = new FbsConsumer.RtpNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tthis.safeEmit('rtp', Buffer.from(notification.dataArray()!));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction parseTraceEventData(\n\ttrace: FbsConsumer.TraceNotification\n): ConsumerTraceEventData {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tlet info: any;\n\n\tif (trace.infoType() !== FbsConsumer.TraceInfo.NONE) {\n\t\tconst accessor = trace.info.bind(trace);\n\n\t\tinfo = FbsConsumerTraceInfo.unionToTraceInfo(trace.infoType(), accessor);\n\n\t\ttrace.info(info);\n\t}\n\n\treturn {\n\t\ttype: consumerTraceEventTypeFromFbs(trace.type()),\n\t\ttimestamp: Number(trace.timestamp()),\n\t\tdirection:\n\t\t\ttrace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out',\n\t\tinfo: info?.unpack(),\n\t};\n}\n\nfunction consumerTraceEventTypeToFbs(\n\teventType: ConsumerTraceEventType\n): FbsConsumer.TraceEventType {\n\tswitch (eventType) {\n\t\tcase 'keyframe': {\n\t\t\treturn FbsConsumer.TraceEventType.KEYFRAME;\n\t\t}\n\n\t\tcase 'fir': {\n\t\t\treturn FbsConsumer.TraceEventType.FIR;\n\t\t}\n\n\t\tcase 'nack': {\n\t\t\treturn FbsConsumer.TraceEventType.NACK;\n\t\t}\n\n\t\tcase 'pli': {\n\t\t\treturn FbsConsumer.TraceEventType.PLI;\n\t\t}\n\n\t\tcase 'rtp': {\n\t\t\treturn FbsConsumer.TraceEventType.RTP;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid ConsumerTraceEventType: ${eventType}`);\n\t\t}\n\t}\n}\n\nfunction consumerTraceEventTypeFromFbs(\n\ttraceType: FbsConsumer.TraceEventType\n): ConsumerTraceEventType {\n\tswitch (traceType) {\n\t\tcase FbsConsumer.TraceEventType.KEYFRAME: {\n\t\t\treturn 'keyframe';\n\t\t}\n\n\t\tcase FbsConsumer.TraceEventType.FIR: {\n\t\t\treturn 'fir';\n\t\t}\n\n\t\tcase FbsConsumer.TraceEventType.NACK: {\n\t\t\treturn 'nack';\n\t\t}\n\n\t\tcase FbsConsumer.TraceEventType.PLI: {\n\t\t\treturn 'pli';\n\t\t}\n\n\t\tcase FbsConsumer.TraceEventType.RTP: {\n\t\t\treturn 'rtp';\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid FbsConsumer.TraceEventType: ${traceType}`);\n\t\t}\n\t}\n}\n\nfunction parseConsumerLayers(data: FbsConsumer.ConsumerLayers): ConsumerLayers {\n\tconst spatialLayer = data.spatialLayer();\n\tconst temporalLayer =\n\t\tdata.temporalLayer() !== null ? data.temporalLayer()! : undefined;\n\n\treturn {\n\t\tspatialLayer,\n\t\ttemporalLayer,\n\t};\n}\n\nfunction parseRtpStream(data: FbsRtpStream.Dump): RtpStreamDump {\n\tconst params = parseRtpStreamParameters(data.params()!);\n\n\tlet rtxStream: RtxStreamDump | undefined;\n\n\tif (data.rtxStream()) {\n\t\trtxStream = parseRtxStream(data.rtxStream()!);\n\t}\n\n\treturn {\n\t\tparams,\n\t\tscore: data.score(),\n\t\trtxStream,\n\t};\n}\n\nfunction parseRtpStreamParameters(\n\tdata: FbsRtpStream.Params\n): RtpStreamParametersDump {\n\treturn {\n\t\tencodingIdx: data.encodingIdx(),\n\t\tssrc: data.ssrc(),\n\t\tpayloadType: data.payloadType(),\n\t\tmimeType: data.mimeType()!,\n\t\tclockRate: data.clockRate(),\n\t\trid: data.rid()!.length > 0 ? data.rid()! : undefined,\n\t\tcname: data.cname()!,\n\t\trtxSsrc: data.rtxSsrc() !== null ? data.rtxSsrc()! : undefined,\n\t\trtxPayloadType:\n\t\t\tdata.rtxPayloadType() !== null ? data.rtxPayloadType()! : undefined,\n\t\tuseNack: data.useNack(),\n\t\tusePli: data.usePli(),\n\t\tuseFir: data.useFir(),\n\t\tuseInBandFec: data.useInBandFec(),\n\t\tuseDtx: data.useDtx(),\n\t\tspatialLayers: data.spatialLayers(),\n\t\ttemporalLayers: data.temporalLayers(),\n\t};\n}\n\nfunction parseRtxStream(data: FbsRtxStream.RtxDump): RtxStreamDump {\n\tconst params = parseRtxStreamParameters(data.params()!);\n\n\treturn {\n\t\tparams,\n\t};\n}\n\nfunction parseRtxStreamParameters(\n\tdata: FbsRtxStream.Params\n): RtxStreamParameters {\n\treturn {\n\t\tssrc: data.ssrc(),\n\t\tpayloadType: data.payloadType(),\n\t\tmimeType: data.mimeType()!,\n\t\tclockRate: data.clockRate(),\n\t\trrid: data.rrid()!.length > 0 ? data.rrid()! : undefined,\n\t\tcname: data.cname()!,\n\t};\n}\n\nfunction parseBaseConsumerDump(\n\tdata: FbsConsumer.BaseConsumerDump\n): BaseConsumerDump {\n\treturn {\n\t\tid: data.id()!,\n\t\tproducerId: data.producerId()!,\n\t\tkind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video',\n\t\trtpParameters: parseRtpParameters(data.rtpParameters()!),\n\t\tconsumableRtpEncodings:\n\t\t\tdata.consumableRtpEncodingsLength() > 0\n\t\t\t\t? fbsUtils.parseVector(\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\t'consumableRtpEncodings',\n\t\t\t\t\t\tparseRtpEncodingParameters\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\ttraceEventTypes: fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'traceEventTypes',\n\t\t\tconsumerTraceEventTypeFromFbs\n\t\t),\n\t\tsupportedCodecPayloadTypes: fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'supportedCodecPayloadTypes'\n\t\t),\n\t\tpaused: data.paused(),\n\t\tproducerPaused: data.producerPaused(),\n\t\tpriority: data.priority(),\n\t};\n}\n\nfunction parseSimpleConsumerDump(\n\tdata: FbsConsumer.ConsumerDump\n): SimpleConsumerDump {\n\tconst base = parseBaseConsumerDump(data.base()!);\n\tconst rtpStream = parseRtpStream(data.rtpStreams(0)!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'simple',\n\t\trtpStream,\n\t};\n}\n\nfunction parseSimulcastConsumerDump(\n\tdata: FbsConsumer.ConsumerDump\n): SimulcastConsumerDump {\n\tconst base = parseBaseConsumerDump(data.base()!);\n\tconst rtpStream = parseRtpStream(data.rtpStreams(0)!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'simulcast',\n\t\trtpStream,\n\t\tpreferredSpatialLayer: data.preferredSpatialLayer()!,\n\t\ttargetSpatialLayer: data.targetSpatialLayer()!,\n\t\tcurrentSpatialLayer: data.currentSpatialLayer()!,\n\t\tpreferredTemporalLayer: data.preferredTemporalLayer()!,\n\t\ttargetTemporalLayer: data.targetTemporalLayer()!,\n\t\tcurrentTemporalLayer: data.currentTemporalLayer()!,\n\t};\n}\n\nfunction parseSvcConsumerDump(data: FbsConsumer.ConsumerDump): SvcConsumerDump {\n\tconst dump = parseSimulcastConsumerDump(data);\n\n\tdump.type = 'svc';\n\n\treturn dump;\n}\n\nfunction parsePipeConsumerDump(\n\tdata: FbsConsumer.ConsumerDump\n): PipeConsumerDump {\n\tconst base = parseBaseConsumerDump(data.base()!);\n\tconst rtpStreams = fbsUtils.parseVector(data, 'rtpStreams', parseRtpStream);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'pipe',\n\t\trtpStreams,\n\t};\n}\n\nfunction parseConsumerDumpResponse(\n\tdata: FbsConsumer.DumpResponse\n): ConsumerDump {\n\tconst type = data.data()!.base()!.type();\n\n\tswitch (type) {\n\t\tcase FbsRtpParametersType.SIMPLE: {\n\t\t\tconst dump = new FbsConsumer.ConsumerDump();\n\n\t\t\tdata.data(dump);\n\n\t\t\treturn parseSimpleConsumerDump(dump);\n\t\t}\n\n\t\tcase FbsRtpParametersType.SIMULCAST: {\n\t\t\tconst dump = new FbsConsumer.ConsumerDump();\n\n\t\t\tdata.data(dump);\n\n\t\t\treturn parseSimulcastConsumerDump(dump);\n\t\t}\n\n\t\tcase FbsRtpParametersType.SVC: {\n\t\t\tconst dump = new FbsConsumer.ConsumerDump();\n\n\t\t\tdata.data(dump);\n\n\t\t\treturn parseSvcConsumerDump(dump);\n\t\t}\n\n\t\tcase FbsRtpParametersType.PIPE: {\n\t\t\tconst dump = new FbsConsumer.ConsumerDump();\n\n\t\t\tdata.data(dump);\n\n\t\t\treturn parsePipeConsumerDump(dump);\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid Consumer type: ${type}`);\n\t\t}\n\t}\n}\n\nfunction parseConsumerStats(\n\tbinary: FbsConsumer.GetStatsResponse\n): (ConsumerStat | ProducerStat)[] {\n\treturn fbsUtils.parseVector(binary, 'stats', parseRtpStreamStats);\n}\n"
  },
  {
    "path": "node/src/ConsumerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { ProducerStat } from './ProducerTypes';\nimport type {\n\tMediaKind,\n\tRtpCapabilities,\n\tRtpEncodingParameters,\n\tRtpParameters,\n} from './rtpParametersTypes';\nimport type { RtpStreamSendStats } from './rtpStreamStatsTypes';\nimport type { AppData } from './types';\n\nexport type ConsumerOptions<ConsumerAppData extends AppData = AppData> = {\n\t/**\n\t * The id of the Producer to consume.\n\t */\n\tproducerId: string;\n\n\t/**\n\t * RTP capabilities of the consuming endpoint.\n\t */\n\trtpCapabilities: RtpCapabilities;\n\n\t/**\n\t * Whether the consumer must start in paused mode. Default false.\n\t *\n\t * When creating a video Consumer, it's recommended to set paused to true,\n\t * then transmit the Consumer parameters to the consuming endpoint and, once\n\t * the consuming endpoint has created its local side Consumer, unpause the\n\t * server side Consumer using the resume() method. This is an optimization\n\t * to make it possible for the consuming endpoint to render the video as far\n\t * as possible. If the server side Consumer was created with paused: false,\n\t * mediasoup will immediately request a key frame to the remote Producer and\n\t * suych a key frame may reach the consuming endpoint even before it's ready\n\t * to consume it, generating “black” video until the device requests a keyframe\n\t * by itself.\n\t */\n\tpaused?: boolean;\n\n\t/**\n\t * The MID for the Consumer. If not specified, a sequentially growing\n\t * number will be assigned.\n\t */\n\tmid?: string;\n\n\t/**\n\t * Preferred spatial and temporal layer for simulcast or SVC media sources.\n\t * If unset, the highest ones are selected.\n\t */\n\tpreferredLayers?: ConsumerLayers;\n\n\t/**\n\t * Whether this Consumer should enable RTP retransmissions, storing sent RTP\n\t * and processing the incoming RTCP NACK from the remote Consumer. If not set\n\t * it's true by default for video codecs and false for audio codecs. If set\n\t * to true, NACK will be enabled if both endpoints (mediasoup and the remote\n\t * Consumer) support NACK for this codec. When it comes to audio codecs, just\n\t * OPUS supports NACK.\n\t */\n\tenableRtx?: boolean;\n\n\t/**\n\t * Whether this Consumer should ignore DTX packets (only valid for Opus codec).\n\t * If set, DTX packets are not forwarded to the remote Consumer.\n\t */\n\tignoreDtx?: boolean;\n\n\t/**\n\t * Whether this Consumer should consume all RTP streams generated by the\n\t * Producer.\n\t */\n\tpipe?: boolean;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: ConsumerAppData;\n};\n\n/**\n * Consumer type.\n */\nexport type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe';\n\nexport type ConsumerScore = {\n\t/**\n\t * The score of the RTP stream of the consumer.\n\t */\n\tscore: number;\n\n\t/**\n\t * The score of the currently selected RTP stream of the producer.\n\t */\n\tproducerScore: number;\n\n\t/**\n\t * The scores of all RTP streams in the producer ordered by encoding (just\n\t * useful when the producer uses simulcast).\n\t */\n\tproducerScores: number[];\n};\n\nexport type ConsumerLayers = {\n\t/**\n\t * The spatial layer index (from 0 to N).\n\t */\n\tspatialLayer: number;\n\n\t/**\n\t * The temporal layer index (from 0 to N).\n\t */\n\ttemporalLayer?: number;\n};\n\nexport type ConsumerDump =\n\t| SimpleConsumerDump\n\t| SimulcastConsumerDump\n\t| SvcConsumerDump\n\t| PipeConsumerDump;\n\nexport type SimpleConsumerDump = BaseConsumerDump & {\n\ttype: string;\n\trtpStream: RtpStreamDump;\n};\n\nexport type SimulcastConsumerDump = BaseConsumerDump & {\n\ttype: string;\n\trtpStream: RtpStreamDump;\n\tpreferredSpatialLayer: number;\n\ttargetSpatialLayer: number;\n\tcurrentSpatialLayer: number;\n\tpreferredTemporalLayer: number;\n\ttargetTemporalLayer: number;\n\tcurrentTemporalLayer: number;\n};\n\nexport type SvcConsumerDump = SimulcastConsumerDump;\n\nexport type PipeConsumerDump = BaseConsumerDump & {\n\ttype: string;\n\trtpStreams: RtpStreamDump[];\n};\n\nexport type BaseConsumerDump = {\n\tid: string;\n\tproducerId: string;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n\tconsumableRtpEncodings?: RtpEncodingParameters[];\n\tsupportedCodecPayloadTypes: number[];\n\ttraceEventTypes: string[];\n\tpaused: boolean;\n\tproducerPaused: boolean;\n\tpriority: number;\n};\n\nexport type RtpStreamDump = {\n\tparams: RtpStreamParametersDump;\n\tscore: number;\n\trtxStream?: RtxStreamDump;\n};\n\nexport type RtpStreamParametersDump = {\n\tencodingIdx: number;\n\tssrc: number;\n\tpayloadType: number;\n\tmimeType: string;\n\tclockRate: number;\n\trid?: string;\n\tcname: string;\n\trtxSsrc?: number;\n\trtxPayloadType?: number;\n\tuseNack: boolean;\n\tusePli: boolean;\n\tuseFir: boolean;\n\tuseInBandFec: boolean;\n\tuseDtx: boolean;\n\tspatialLayers: number;\n\ttemporalLayers: number;\n};\n\nexport type RtxStreamDump = {\n\tparams: RtxStreamParameters;\n};\n\nexport type RtxStreamParameters = {\n\tssrc: number;\n\tpayloadType: number;\n\tmimeType: string;\n\tclockRate: number;\n\trrid?: string;\n\tcname: string;\n};\n\nexport type ConsumerStat = RtpStreamSendStats;\n\n/**\n * Valid types for 'trace' event.\n */\nexport type ConsumerTraceEventType =\n\t| 'rtp'\n\t| 'keyframe'\n\t| 'nack'\n\t| 'pli'\n\t| 'fir';\n\n/**\n * 'trace' event data.\n */\nexport type ConsumerTraceEventData = {\n\t/**\n\t * Trace type.\n\t */\n\ttype: ConsumerTraceEventType;\n\n\t/**\n\t * Event timestamp.\n\t */\n\ttimestamp: number;\n\n\t/**\n\t * Event direction.\n\t */\n\tdirection: 'in' | 'out';\n\n\t/**\n\t * Per type information.\n\t */\n\tinfo: Record<string, unknown>;\n};\n\nexport type ConsumerEvents = {\n\ttransportclose: [];\n\tproducerclose: [];\n\tproducerpause: [];\n\tproducerresume: [];\n\tscore: [ConsumerScore];\n\tlayerschange: [ConsumerLayers?];\n\ttrace: [ConsumerTraceEventData];\n\trtp: [Buffer];\n\t// Private events.\n\t'@close': [];\n\t'@producerclose': [];\n};\n\nexport type ConsumerObserver = EnhancedEventEmitter<ConsumerObserverEvents>;\n\nexport type ConsumerObserverEvents = {\n\tclose: [];\n\tpause: [];\n\tresume: [];\n\tscore: [ConsumerScore];\n\tlayerschange: [ConsumerLayers?];\n\ttrace: [ConsumerTraceEventData];\n};\n\nexport interface Consumer<\n\tConsumerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<ConsumerEvents> {\n\t/**\n\t * Consumer id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Associated Producer id.\n\t */\n\tget producerId(): string;\n\n\t/**\n\t * Whether the Consumer is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * Media kind.\n\t */\n\tget kind(): MediaKind;\n\n\t/**\n\t * RTP parameters.\n\t */\n\tget rtpParameters(): RtpParameters;\n\n\t/**\n\t * Consumer type.\n\t */\n\tget type(): ConsumerType;\n\n\t/**\n\t * Whether the Consumer is paused.\n\t */\n\tget paused(): boolean;\n\n\t/**\n\t * Whether the associate Producer is paused.\n\t */\n\tget producerPaused(): boolean;\n\n\t/**\n\t * Current priority.\n\t */\n\tget priority(): number;\n\n\t/**\n\t * Consumer score.\n\t */\n\tget score(): ConsumerScore;\n\n\t/**\n\t * Preferred video layers.\n\t */\n\tget preferredLayers(): ConsumerLayers | undefined;\n\n\t/**\n\t * Current video layers.\n\t */\n\tget currentLayers(): ConsumerLayers | undefined;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): ConsumerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: ConsumerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): ConsumerObserver;\n\n\t/**\n\t * Close the Consumer.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Transport was closed.\n\t *\n\t * @private\n\t */\n\ttransportClosed(): void;\n\n\t/**\n\t * Dump Consumer.\n\t */\n\tdump(): Promise<ConsumerDump>;\n\n\t/**\n\t * Get Consumer stats.\n\t */\n\tgetStats(): Promise<(ConsumerStat | ProducerStat)[]>;\n\n\t/**\n\t * Pause the Consumer.\n\t */\n\tpause(): Promise<void>;\n\n\t/**\n\t * Resume the Consumer.\n\t */\n\tresume(): Promise<void>;\n\n\t/**\n\t * Set preferred video layers.\n\t */\n\tsetPreferredLayers({\n\t\tspatialLayer,\n\t\ttemporalLayer,\n\t}: ConsumerLayers): Promise<void>;\n\n\t/**\n\t * Set priority.\n\t */\n\tsetPriority(priority: number): Promise<void>;\n\n\t/**\n\t * Unset priority.\n\t */\n\tunsetPriority(): Promise<void>;\n\n\t/**\n\t * Request a key frame to the Producer.\n\t */\n\trequestKeyFrame(): Promise<void>;\n\n\t/**\n\t * Enable 'trace' event.\n\t */\n\tenableTraceEvent(types?: ConsumerTraceEventType[]): Promise<void>;\n}\n"
  },
  {
    "path": "node/src/DataConsumer.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tDataConsumer,\n\tDataConsumerType,\n\tDataConsumerDump,\n\tDataConsumerStat,\n\tDataConsumerEvents,\n\tDataConsumerObserver,\n\tDataConsumerObserverEvents,\n} from './DataConsumerTypes';\nimport { Channel } from './Channel';\nimport type { TransportInternal } from './Transport';\nimport type { SctpStreamParameters } from './sctpParametersTypes';\nimport { parseSctpStreamParameters } from './sctpParametersFbsUtils';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsDataConsumer from './fbs/data-consumer';\nimport * as FbsDataProducer from './fbs/data-producer';\n\ntype DataConsumerInternal = TransportInternal & {\n\tdataConsumerId: string;\n};\n\ntype DataConsumerData = {\n\tdataProducerId: string;\n\ttype: DataConsumerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n\tbufferedAmountLowThreshold: number;\n};\n\nconst logger = new Logger('DataConsumer');\n\nexport class DataConsumerImpl<DataConsumerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<DataConsumerEvents>\n\timplements DataConsumer\n{\n\t// Internal data.\n\treadonly #internal: DataConsumerInternal;\n\n\t// DataConsumer data.\n\treadonly #data: DataConsumerData;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Paused flag.\n\t#paused = false;\n\n\t// Associated DataProducer paused flag.\n\t#dataProducerPaused = false;\n\n\t// Subchannels subscribed to.\n\t#subchannels: number[];\n\n\t// Custom app data.\n\t#appData: DataConsumerAppData;\n\n\t// Observer instance.\n\treadonly #observer: DataConsumerObserver =\n\t\tnew EnhancedEventEmitter<DataConsumerObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tdata,\n\t\tchannel,\n\t\tpaused,\n\t\tdataProducerPaused,\n\t\tsubchannels,\n\t\tappData,\n\t}: {\n\t\tinternal: DataConsumerInternal;\n\t\tdata: DataConsumerData;\n\t\tchannel: Channel;\n\t\tpaused: boolean;\n\t\tdataProducerPaused: boolean;\n\t\tsubchannels: number[];\n\t\tappData?: DataConsumerAppData;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#data = data;\n\t\tthis.#channel = channel;\n\t\tthis.#paused = paused;\n\t\tthis.#dataProducerPaused = dataProducerPaused;\n\t\tthis.#subchannels = subchannels;\n\t\tthis.#appData = appData ?? ({} as DataConsumerAppData);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.dataConsumerId;\n\t}\n\n\tget dataProducerId(): string {\n\t\treturn this.#data.dataProducerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget type(): DataConsumerType {\n\t\treturn this.#data.type;\n\t}\n\n\tget sctpStreamParameters(): SctpStreamParameters | undefined {\n\t\treturn this.#data.sctpStreamParameters;\n\t}\n\n\tget label(): string {\n\t\treturn this.#data.label;\n\t}\n\n\tget protocol(): string {\n\t\treturn this.#data.protocol;\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#paused;\n\t}\n\n\tget dataProducerPaused(): boolean {\n\t\treturn this.#dataProducerPaused;\n\t}\n\n\tget subchannels(): number[] {\n\t\treturn Array.from(this.#subchannels);\n\t}\n\n\tget appData(): DataConsumerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: DataConsumerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): DataConsumerObserver {\n\t\treturn this.#observer;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.dataConsumerId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.CloseDataConsumerRequestT(\n\t\t\tthis.#internal.dataConsumerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.TRANSPORT_CLOSE_DATACONSUMER,\n\t\t\t\tFbsRequest.Body.Transport_CloseDataConsumerRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.#internal.transportId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\ttransportClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('transportClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.dataConsumerId);\n\n\t\tthis.safeEmit('transportclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<DataConsumerDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst dumpResponse = new FbsDataConsumer.DumpResponse();\n\n\t\tresponse.body(dumpResponse);\n\n\t\treturn parseDataConsumerDumpResponse(dumpResponse);\n\t}\n\n\tasync getStats(): Promise<DataConsumerStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDataConsumer.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseDataConsumerStats(data)];\n\t}\n\n\tasync pause(): Promise<void> {\n\t\tlogger.debug('pause()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_PAUSE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = true;\n\n\t\t// Emit observer event.\n\t\tif (!wasPaused && !this.#dataProducerPaused) {\n\t\t\tthis.#observer.safeEmit('pause');\n\t\t}\n\t}\n\n\tasync resume(): Promise<void> {\n\t\tlogger.debug('resume()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_RESUME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = false;\n\n\t\t// Emit observer event.\n\t\tif (wasPaused && !this.#dataProducerPaused) {\n\t\t\tthis.#observer.safeEmit('resume');\n\t\t}\n\t}\n\n\tasync setBufferedAmountLowThreshold(threshold: number): Promise<void> {\n\t\tlogger.debug(`setBufferedAmountLowThreshold() [threshold:${threshold}]`);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset =\n\t\t\tFbsDataConsumer.SetBufferedAmountLowThresholdRequest.createSetBufferedAmountLowThresholdRequest(\n\t\t\t\tthis.#channel.bufferBuilder,\n\t\t\t\tthreshold\n\t\t\t);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD,\n\t\t\tFbsRequest.Body.DataConsumer_SetBufferedAmountLowThresholdRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\t}\n\n\tasync getBufferedAmount(): Promise<number> {\n\t\tlogger.debug('getBufferedAmount()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_GET_BUFFERED_AMOUNT,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\tconst data = new FbsDataConsumer.GetBufferedAmountResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn data.bufferedAmount();\n\t}\n\n\tasync send(message: string | Buffer, ppid?: number): Promise<void> {\n\t\tif (typeof message !== 'string' && !Buffer.isBuffer(message)) {\n\t\t\tthrow new TypeError('message must be a string or a Buffer');\n\t\t}\n\n\t\t/*\n\t\t * +-------------------------------+----------+\n\t\t * | Value                         | SCTP     |\n\t\t * |                               | PPID     |\n\t\t * +-------------------------------+----------+\n\t\t * | WebRTC String                 | 51       |\n\t\t * | WebRTC Binary Partial         | 52       |\n\t\t * | (Deprecated)                  |          |\n\t\t * | WebRTC Binary                 | 53       |\n\t\t * | WebRTC String Partial         | 54       |\n\t\t * | (Deprecated)                  |          |\n\t\t * | WebRTC String Empty           | 56       |\n\t\t * | WebRTC Binary Empty           | 57       |\n\t\t * +-------------------------------+----------+\n\t\t */\n\n\t\tif (typeof ppid !== 'number') {\n\t\t\tppid =\n\t\t\t\ttypeof message === 'string'\n\t\t\t\t\t? message.length > 0\n\t\t\t\t\t\t? 51\n\t\t\t\t\t\t: 56\n\t\t\t\t\t: message.length > 0\n\t\t\t\t\t\t? 53\n\t\t\t\t\t\t: 57;\n\t\t}\n\n\t\t// Ensure we honor PPIDs.\n\t\tif (ppid === 56) {\n\t\t\tmessage = ' ';\n\t\t} else if (ppid === 57) {\n\t\t\tmessage = Buffer.alloc(1);\n\t\t}\n\n\t\tconst builder = this.#channel.bufferBuilder;\n\n\t\tif (typeof message === 'string') {\n\t\t\tmessage = Buffer.from(message);\n\t\t}\n\n\t\tconst dataOffset = FbsDataConsumer.SendRequest.createDataVector(\n\t\t\tbuilder,\n\t\t\tmessage\n\t\t);\n\n\t\tconst requestOffset = FbsDataConsumer.SendRequest.createSendRequest(\n\t\t\tbuilder,\n\t\t\tppid,\n\t\t\tdataOffset\n\t\t);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_SEND,\n\t\t\tFbsRequest.Body.DataConsumer_SendRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\t}\n\n\tasync setSubchannels(subchannels: number[]): Promise<void> {\n\t\tlogger.debug('setSubchannels()');\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsDataConsumer.SetSubchannelsRequestT(\n\t\t\tsubchannels\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_SET_SUBCHANNELS,\n\t\t\tFbsRequest.Body.DataConsumer_SetSubchannelsRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDataConsumer.SetSubchannelsResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update subchannels.\n\t\tthis.#subchannels = fbsUtils.parseVector(data, 'subchannels');\n\t}\n\n\tasync addSubchannel(subchannel: number): Promise<void> {\n\t\tlogger.debug('addSubchannel()');\n\n\t\t/* Build Request. */\n\t\tconst requestOffset =\n\t\t\tFbsDataConsumer.AddSubchannelRequest.createAddSubchannelRequest(\n\t\t\t\tthis.#channel.bufferBuilder,\n\t\t\t\tsubchannel\n\t\t\t);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_ADD_SUBCHANNEL,\n\t\t\tFbsRequest.Body.DataConsumer_AddSubchannelRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDataConsumer.AddSubchannelResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update subchannels.\n\t\tthis.#subchannels = fbsUtils.parseVector(data, 'subchannels');\n\t}\n\n\tasync removeSubchannel(subchannel: number): Promise<void> {\n\t\tlogger.debug('removeSubchannel()');\n\n\t\t/* Build Request. */\n\t\tconst requestOffset =\n\t\t\tFbsDataConsumer.RemoveSubchannelRequest.createRemoveSubchannelRequest(\n\t\t\t\tthis.#channel.bufferBuilder,\n\t\t\t\tsubchannel\n\t\t\t);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATACONSUMER_REMOVE_SUBCHANNEL,\n\t\t\tFbsRequest.Body.DataConsumer_RemoveSubchannelRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.dataConsumerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDataConsumer.RemoveSubchannelResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update subchannels.\n\t\tthis.#subchannels = fbsUtils.parseVector(data, 'subchannels');\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.#channel.on(\n\t\t\tthis.#internal.dataConsumerId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.DATACONSUMER_DATAPRODUCER_CLOSE: {\n\t\t\t\t\t\tif (this.#closed) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#closed = true;\n\n\t\t\t\t\t\t// Remove notification subscriptions.\n\t\t\t\t\t\tthis.#channel.removeAllListeners(this.#internal.dataConsumerId);\n\n\t\t\t\t\t\tthis.emit('@dataproducerclose');\n\t\t\t\t\t\tthis.safeEmit('dataproducerclose');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('close');\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DATACONSUMER_DATAPRODUCER_PAUSE: {\n\t\t\t\t\t\tif (this.#dataProducerPaused) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#dataProducerPaused = true;\n\n\t\t\t\t\t\tthis.safeEmit('dataproducerpause');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tif (!this.#paused) {\n\t\t\t\t\t\t\tthis.#observer.safeEmit('pause');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DATACONSUMER_DATAPRODUCER_RESUME: {\n\t\t\t\t\t\tif (!this.#dataProducerPaused) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.#dataProducerPaused = false;\n\n\t\t\t\t\t\tthis.safeEmit('dataproducerresume');\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tif (!this.#paused) {\n\t\t\t\t\t\t\tthis.#observer.safeEmit('resume');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DATACONSUMER_SCTP_SENDBUFFER_FULL: {\n\t\t\t\t\t\tthis.safeEmit('sctpsendbufferfull');\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DATACONSUMER_BUFFERED_AMOUNT_LOW: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsDataConsumer.BufferedAmountLowNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst bufferedAmount = notification.bufferedAmount();\n\n\t\t\t\t\t\tthis.safeEmit('bufferedamountlow', bufferedAmount);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DATACONSUMER_MESSAGE: {\n\t\t\t\t\t\tif (this.#closed) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst notification = new FbsDataConsumer.MessageNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tthis.safeEmit(\n\t\t\t\t\t\t\t'message',\n\t\t\t\t\t\t\tBuffer.from(notification.dataArray()!),\n\t\t\t\t\t\t\tnotification.ppid()\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nexport function dataConsumerTypeToFbs(\n\ttype: DataConsumerType\n): FbsDataProducer.Type {\n\tswitch (type) {\n\t\tcase 'sctp': {\n\t\t\treturn FbsDataProducer.Type.SCTP;\n\t\t}\n\n\t\tcase 'direct': {\n\t\t\treturn FbsDataProducer.Type.DIRECT;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError('invalid DataConsumerType: ${type}');\n\t\t}\n\t}\n}\n\nfunction dataConsumerTypeFromFbs(type: FbsDataProducer.Type): DataConsumerType {\n\tswitch (type) {\n\t\tcase FbsDataProducer.Type.SCTP: {\n\t\t\treturn 'sctp';\n\t\t}\n\n\t\tcase FbsDataProducer.Type.DIRECT: {\n\t\t\treturn 'direct';\n\t\t}\n\t}\n}\n\nexport function parseDataConsumerDumpResponse(\n\tdata: FbsDataConsumer.DumpResponse\n): DataConsumerDump {\n\treturn {\n\t\tid: data.id()!,\n\t\tdataProducerId: data.dataProducerId()!,\n\t\ttype: dataConsumerTypeFromFbs(data.type()),\n\t\tsctpStreamParameters:\n\t\t\tdata.sctpStreamParameters() !== null\n\t\t\t\t? parseSctpStreamParameters(data.sctpStreamParameters()!)\n\t\t\t\t: undefined,\n\t\tlabel: data.label()!,\n\t\tprotocol: data.protocol()!,\n\t\tbufferedAmountLowThreshold: data.bufferedAmountLowThreshold(),\n\t\tpaused: data.paused(),\n\t\tdataProducerPaused: data.dataProducerPaused(),\n\t\tsubchannels: fbsUtils.parseVector(data, 'subchannels'),\n\t};\n}\n\nfunction parseDataConsumerStats(\n\tbinary: FbsDataConsumer.GetStatsResponse\n): DataConsumerStat {\n\treturn {\n\t\ttype: 'data-consumer',\n\t\ttimestamp: Number(binary.timestamp()),\n\t\tlabel: binary.label()!,\n\t\tprotocol: binary.protocol()!,\n\t\tmessagesSent: Number(binary.messagesSent()),\n\t\tbytesSent: Number(binary.bytesSent()),\n\t\tbufferedAmount: binary.bufferedAmount(),\n\t};\n}\n"
  },
  {
    "path": "node/src/DataConsumerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { SctpStreamParameters } from './sctpParametersTypes';\nimport type { AppData } from './types';\n\nexport type DataConsumerOptions<DataConsumerAppData extends AppData = AppData> =\n\t{\n\t\t/**\n\t\t * The id of the DataProducer to consume.\n\t\t */\n\t\tdataProducerId: string;\n\n\t\t/**\n\t\t * Just if consuming over SCTP.\n\t\t * Whether data messages must be received in order. If true the messages will\n\t\t * be sent reliably. Defaults to the value in the DataProducer if it has type\n\t\t * 'sctp' or to true if it has type 'direct'.\n\t\t */\n\t\tordered?: boolean;\n\n\t\t/**\n\t\t * Just if consuming over SCTP.\n\t\t * When ordered is false indicates the time (in milliseconds) after which a\n\t\t * SCTP packet will stop being retransmitted. Defaults to the value in the\n\t\t * DataProducer if it has type 'sctp' or unset if it has type 'direct'.\n\t\t */\n\t\tmaxPacketLifeTime?: number;\n\n\t\t/**\n\t\t * Just if consuming over SCTP.\n\t\t * When ordered is false indicates the maximum number of times a packet will\n\t\t * be retransmitted. Defaults to the value in the DataProducer if it has type\n\t\t * 'sctp' or unset if it has type 'direct'.\n\t\t */\n\t\tmaxRetransmits?: number;\n\n\t\t/**\n\t\t * Whether the data consumer must start in paused mode. Default false.\n\t\t */\n\t\tpaused?: boolean;\n\n\t\t/**\n\t\t * Subchannels this data consumer initially subscribes to.\n\t\t * Only used in case this data consumer receives messages from a local data\n\t\t * producer that specifies subchannel(s) when calling send().\n\t\t */\n\t\tsubchannels?: number[];\n\n\t\t/**\n\t\t * Custom application data.\n\t\t */\n\t\tappData?: DataConsumerAppData;\n\t};\n\n/**\n * DataConsumer type.\n */\nexport type DataConsumerType = 'sctp' | 'direct';\n\nexport type DataConsumerDump = {\n\tid: string;\n\tpaused: boolean;\n\tdataProducerPaused: boolean;\n\tsubchannels: number[];\n\tdataProducerId: string;\n\ttype: DataConsumerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n\tbufferedAmountLowThreshold: number;\n};\n\nexport type DataConsumerStat = {\n\ttype: string;\n\ttimestamp: number;\n\tlabel: string;\n\tprotocol: string;\n\tmessagesSent: number;\n\tbytesSent: number;\n\tbufferedAmount: number;\n};\n\nexport type DataConsumerEvents = {\n\ttransportclose: [];\n\tdataproducerclose: [];\n\tdataproducerpause: [];\n\tdataproducerresume: [];\n\tmessage: [Buffer, number];\n\tsctpsendbufferfull: [];\n\tbufferedamountlow: [number];\n\t// Private events.\n\t'@close': [];\n\t'@dataproducerclose': [];\n};\n\nexport type DataConsumerObserver =\n\tEnhancedEventEmitter<DataConsumerObserverEvents>;\n\nexport type DataConsumerObserverEvents = {\n\tclose: [];\n\tpause: [];\n\tresume: [];\n};\n\nexport interface DataConsumer<\n\tDataConsumerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<DataConsumerEvents> {\n\t/**\n\t * DataConsumer id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Associated DataProducer id.\n\t */\n\tget dataProducerId(): string;\n\n\t/**\n\t * Whether the DataConsumer is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * DataConsumer type.\n\t */\n\tget type(): DataConsumerType;\n\n\t/**\n\t * SCTP stream parameters.\n\t */\n\tget sctpStreamParameters(): SctpStreamParameters | undefined;\n\n\t/**\n\t * DataChannel label.\n\t */\n\tget label(): string;\n\n\t/**\n\t * DataChannel protocol.\n\t */\n\tget protocol(): string;\n\n\t/**\n\t * Whether the DataConsumer is paused.\n\t */\n\tget paused(): boolean;\n\n\t/**\n\t * Whether the associate DataProducer is paused.\n\t */\n\tget dataProducerPaused(): boolean;\n\n\t/**\n\t * Get current subchannels this data consumer is subscribed to.\n\t */\n\tget subchannels(): number[];\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): DataConsumerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: DataConsumerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): DataConsumerObserver;\n\n\t/**\n\t * Close the DataConsumer.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Transport was closed.\n\t *\n\t * @private\n\t */\n\ttransportClosed(): void;\n\n\t/**\n\t * Dump DataConsumer.\n\t */\n\tdump(): Promise<DataConsumerDump>;\n\n\t/**\n\t * Get DataConsumer stats.\n\t */\n\tgetStats(): Promise<DataConsumerStat[]>;\n\n\t/**\n\t * Pause the DataConsumer.\n\t */\n\tpause(): Promise<void>;\n\n\t/**\n\t * Resume the DataConsumer.\n\t */\n\tresume(): Promise<void>;\n\n\t/**\n\t * Set buffered amount low threshold.\n\t */\n\tsetBufferedAmountLowThreshold(threshold: number): Promise<void>;\n\n\t/**\n\t * Get buffered amount size.\n\t */\n\tgetBufferedAmount(): Promise<number>;\n\n\t/**\n\t * Send a message.\n\t */\n\tsend(message: string | Buffer, ppid?: number): Promise<void>;\n\n\t/**\n\t * Set subchannels.\n\t */\n\tsetSubchannels(subchannels: number[]): Promise<void>;\n\n\t/**\n\t * Add a subchannel.\n\t */\n\taddSubchannel(subchannel: number): Promise<void>;\n\n\t/**\n\t * Remove a subchannel.\n\t */\n\tremoveSubchannel(subchannel: number): Promise<void>;\n}\n"
  },
  {
    "path": "node/src/DataProducer.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tDataProducer,\n\tDataProducerType,\n\tDataProducerDump,\n\tDataProducerStat,\n\tDataProducerEvents,\n\tDataProducerObserver,\n\tDataProducerObserverEvents,\n} from './DataProducerTypes';\nimport { Channel } from './Channel';\nimport type { TransportInternal } from './Transport';\nimport type { SctpStreamParameters } from './sctpParametersTypes';\nimport { parseSctpStreamParameters } from './sctpParametersFbsUtils';\nimport type { AppData } from './types';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsNotification from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsDataProducer from './fbs/data-producer';\n\ntype DataProducerInternal = TransportInternal & {\n\tdataProducerId: string;\n};\n\ntype DataProducerData = {\n\ttype: DataProducerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n};\n\nconst logger = new Logger('DataProducer');\n\nexport class DataProducerImpl<DataProducerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<DataProducerEvents>\n\timplements DataProducer\n{\n\t// Internal data.\n\treadonly #internal: DataProducerInternal;\n\n\t// DataProducer data.\n\treadonly #data: DataProducerData;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Paused flag.\n\t#paused = false;\n\n\t// Custom app data.\n\t#appData: DataProducerAppData;\n\n\t// Observer instance.\n\treadonly #observer: DataProducerObserver =\n\t\tnew EnhancedEventEmitter<DataProducerObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tdata,\n\t\tchannel,\n\t\tpaused,\n\t\tappData,\n\t}: {\n\t\tinternal: DataProducerInternal;\n\t\tdata: DataProducerData;\n\t\tchannel: Channel;\n\t\tpaused: boolean;\n\t\tappData?: DataProducerAppData;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#data = data;\n\t\tthis.#channel = channel;\n\t\tthis.#paused = paused;\n\t\tthis.#appData = appData ?? ({} as DataProducerAppData);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.dataProducerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget type(): DataProducerType {\n\t\treturn this.#data.type;\n\t}\n\n\tget sctpStreamParameters(): SctpStreamParameters | undefined {\n\t\treturn this.#data.sctpStreamParameters;\n\t}\n\n\tget label(): string {\n\t\treturn this.#data.label;\n\t}\n\n\tget protocol(): string {\n\t\treturn this.#data.protocol;\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#paused;\n\t}\n\n\tget appData(): DataProducerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: DataProducerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): DataProducerObserver {\n\t\treturn this.#observer;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.dataProducerId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.CloseDataProducerRequestT(\n\t\t\tthis.#internal.dataProducerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.TRANSPORT_CLOSE_DATAPRODUCER,\n\t\t\t\tFbsRequest.Body.Transport_CloseDataProducerRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.#internal.transportId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\ttransportClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('transportClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.dataProducerId);\n\n\t\tthis.safeEmit('transportclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<DataProducerDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATAPRODUCER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataProducerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst produceResponse = new FbsDataProducer.DumpResponse();\n\n\t\tresponse.body(produceResponse);\n\n\t\treturn parseDataProducerDumpResponse(produceResponse);\n\t}\n\n\tasync getStats(): Promise<DataProducerStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.DATAPRODUCER_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataProducerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDataProducer.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseDataProducerStats(data)];\n\t}\n\n\tasync pause(): Promise<void> {\n\t\tlogger.debug('pause()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATAPRODUCER_PAUSE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataProducerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = true;\n\n\t\t// Emit observer event.\n\t\tif (!wasPaused) {\n\t\t\tthis.#observer.safeEmit('pause');\n\t\t}\n\t}\n\n\tasync resume(): Promise<void> {\n\t\tlogger.debug('resume()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.DATAPRODUCER_RESUME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.dataProducerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = false;\n\n\t\t// Emit observer event.\n\t\tif (wasPaused) {\n\t\t\tthis.#observer.safeEmit('resume');\n\t\t}\n\t}\n\n\tsend(\n\t\tmessage: string | Buffer,\n\t\tppid?: number,\n\t\tsubchannels?: number[],\n\t\trequiredSubchannel?: number\n\t): void {\n\t\tif (typeof message !== 'string' && !Buffer.isBuffer(message)) {\n\t\t\tthrow new TypeError('message must be a string or a Buffer');\n\t\t}\n\n\t\t/*\n\t\t * +-------------------------------+----------+\n\t\t * | Value                         | SCTP     |\n\t\t * |                               | PPID     |\n\t\t * +-------------------------------+----------+\n\t\t * | WebRTC String                 | 51       |\n\t\t * | WebRTC Binary Partial         | 52       |\n\t\t * | (Deprecated)                  |          |\n\t\t * | WebRTC Binary                 | 53       |\n\t\t * | WebRTC String Partial         | 54       |\n\t\t * | (Deprecated)                  |          |\n\t\t * | WebRTC String Empty           | 56       |\n\t\t * | WebRTC Binary Empty           | 57       |\n\t\t * +-------------------------------+----------+\n\t\t */\n\n\t\tif (typeof ppid !== 'number') {\n\t\t\tppid =\n\t\t\t\ttypeof message === 'string'\n\t\t\t\t\t? message.length > 0\n\t\t\t\t\t\t? 51\n\t\t\t\t\t\t: 56\n\t\t\t\t\t: message.length > 0\n\t\t\t\t\t\t? 53\n\t\t\t\t\t\t: 57;\n\t\t}\n\n\t\t// Ensure we honor PPIDs.\n\t\tif (ppid === 56) {\n\t\t\tmessage = ' ';\n\t\t} else if (ppid === 57) {\n\t\t\tmessage = Buffer.alloc(1);\n\t\t}\n\n\t\tconst builder = this.#channel.bufferBuilder;\n\n\t\tconst subchannelsOffset =\n\t\t\tFbsDataProducer.SendNotification.createSubchannelsVector(\n\t\t\t\tbuilder,\n\t\t\t\tsubchannels ?? []\n\t\t\t);\n\n\t\tif (typeof message === 'string') {\n\t\t\tmessage = Buffer.from(message);\n\t\t}\n\n\t\tconst dataOffset = FbsDataProducer.SendNotification.createDataVector(\n\t\t\tbuilder,\n\t\t\tmessage\n\t\t);\n\n\t\tconst notificationOffset =\n\t\t\tFbsDataProducer.SendNotification.createSendNotification(\n\t\t\t\tbuilder,\n\t\t\t\tppid,\n\t\t\t\tdataOffset,\n\t\t\t\tsubchannelsOffset,\n\t\t\t\trequiredSubchannel ?? null\n\t\t\t);\n\n\t\tthis.#channel.notify(\n\t\t\tFbsNotification.Event.DATAPRODUCER_SEND,\n\t\t\tFbsNotification.Body.DataProducer_SendNotification,\n\t\t\tnotificationOffset,\n\t\t\tthis.#internal.dataProducerId\n\t\t);\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\t// No need to subscribe to any event.\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nexport function dataProducerTypeToFbs(\n\ttype: DataProducerType\n): FbsDataProducer.Type {\n\tswitch (type) {\n\t\tcase 'sctp': {\n\t\t\treturn FbsDataProducer.Type.SCTP;\n\t\t}\n\n\t\tcase 'direct': {\n\t\t\treturn FbsDataProducer.Type.DIRECT;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError('invalid DataConsumerType: ${type}');\n\t\t}\n\t}\n}\n\nfunction dataProducerTypeFromFbs(type: FbsDataProducer.Type): DataProducerType {\n\tswitch (type) {\n\t\tcase FbsDataProducer.Type.SCTP: {\n\t\t\treturn 'sctp';\n\t\t}\n\n\t\tcase FbsDataProducer.Type.DIRECT: {\n\t\t\treturn 'direct';\n\t\t}\n\t}\n}\n\nexport function parseDataProducerDumpResponse(\n\tdata: FbsDataProducer.DumpResponse\n): DataProducerDump {\n\treturn {\n\t\tid: data.id()!,\n\t\ttype: dataProducerTypeFromFbs(data.type()),\n\t\tsctpStreamParameters:\n\t\t\tdata.sctpStreamParameters() !== null\n\t\t\t\t? parseSctpStreamParameters(data.sctpStreamParameters()!)\n\t\t\t\t: undefined,\n\t\tlabel: data.label()!,\n\t\tprotocol: data.protocol()!,\n\t\tpaused: data.paused(),\n\t};\n}\n\nfunction parseDataProducerStats(\n\tbinary: FbsDataProducer.GetStatsResponse\n): DataProducerStat {\n\treturn {\n\t\ttype: 'data-producer',\n\t\ttimestamp: Number(binary.timestamp()),\n\t\tlabel: binary.label()!,\n\t\tprotocol: binary.protocol()!,\n\t\tmessagesReceived: Number(binary.messagesReceived()),\n\t\tbytesReceived: Number(binary.bytesReceived()),\n\t};\n}\n"
  },
  {
    "path": "node/src/DataProducerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { SctpStreamParameters } from './sctpParametersTypes';\nimport type { AppData } from './types';\n\nexport type DataProducerOptions<DataProducerAppData extends AppData = AppData> =\n\t{\n\t\t/**\n\t\t * DataProducer id (just for Router.pipeToRouter() method).\n\t\t */\n\t\tid?: string;\n\n\t\t/**\n\t\t * SCTP parameters defining how the endpoint is sending the data.\n\t\t * Just if messages are sent over SCTP.\n\t\t */\n\t\tsctpStreamParameters?: SctpStreamParameters;\n\n\t\t/**\n\t\t * A label which can be used to distinguish this DataChannel from others.\n\t\t */\n\t\tlabel?: string;\n\n\t\t/**\n\t\t * Name of the sub-protocol used by this DataChannel.\n\t\t */\n\t\tprotocol?: string;\n\n\t\t/**\n\t\t * Whether the data producer must start in paused mode. Default false.\n\t\t */\n\t\tpaused?: boolean;\n\n\t\t/**\n\t\t * Custom application data.\n\t\t */\n\t\tappData?: DataProducerAppData;\n\t};\n\n/**\n * DataProducer type.\n */\nexport type DataProducerType = 'sctp' | 'direct';\n\nexport type DataProducerDump = {\n\tid: string;\n\tpaused: boolean;\n\ttype: DataProducerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n};\n\nexport type DataProducerStat = {\n\ttype: string;\n\ttimestamp: number;\n\tlabel: string;\n\tprotocol: string;\n\tmessagesReceived: number;\n\tbytesReceived: number;\n};\n\nexport type DataProducerEvents = {\n\ttransportclose: [];\n\t// Private events.\n\t'@close': [];\n};\n\nexport type DataProducerObserver =\n\tEnhancedEventEmitter<DataProducerObserverEvents>;\n\nexport type DataProducerObserverEvents = {\n\tclose: [];\n\tpause: [];\n\tresume: [];\n};\n\nexport interface DataProducer<\n\tDataProducerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<DataProducerEvents> {\n\t/**\n\t * DataProducer id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the DataProducer is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * DataProducer type.\n\t */\n\tget type(): DataProducerType;\n\n\t/**\n\t * SCTP stream parameters.\n\t */\n\tget sctpStreamParameters(): SctpStreamParameters | undefined;\n\n\t/**\n\t * DataChannel label.\n\t */\n\tget label(): string;\n\n\t/**\n\t * DataChannel protocol.\n\t */\n\tget protocol(): string;\n\n\t/**\n\t * Whether the DataProducer is paused.\n\t */\n\tget paused(): boolean;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): DataProducerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: DataProducerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): DataProducerObserver;\n\n\t/**\n\t * Close the DataProducer.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Transport was closed.\n\t *\n\t * @private\n\t */\n\ttransportClosed(): void;\n\n\t/**\n\t * Dump DataProducer.\n\t */\n\tdump(): Promise<DataProducerDump>;\n\n\t/**\n\t * Get DataProducer stats.\n\t */\n\tgetStats(): Promise<DataProducerStat[]>;\n\n\t/**\n\t * Pause the DataProducer.\n\t */\n\tpause(): Promise<void>;\n\n\t/**\n\t * Resume the DataProducer.\n\t */\n\tresume(): Promise<void>;\n\n\t/**\n\t * Send data (just valid for DataProducers created on a DirectTransport).\n\t */\n\tsend(\n\t\tmessage: string | Buffer,\n\t\tppid?: number,\n\t\tsubchannels?: number[],\n\t\trequiredSubchannel?: number\n\t): void;\n}\n"
  },
  {
    "path": "node/src/DirectTransport.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tDirectTransport,\n\tDirectTransportDump,\n\tDirectTransportStat,\n\tDirectTransportEvents,\n\tDirectTransportObserver,\n\tDirectTransportObserverEvents,\n} from './DirectTransportTypes';\nimport type { Transport, BaseTransportDump } from './TransportTypes';\nimport {\n\tTransportImpl,\n\tTransportConstructorOptions,\n\tparseBaseTransportDump,\n\tparseBaseTransportStats,\n\tparseTransportTraceEventData,\n} from './Transport';\nimport type { SctpParameters } from './sctpParametersTypes';\nimport type { AppData } from './types';\nimport { UnsupportedError } from './errors';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsDirectTransport from './fbs/direct-transport';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsNotification from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\n\ntype DirectTransportConstructorOptions<DirectTransportAppData> =\n\tTransportConstructorOptions<DirectTransportAppData> & {\n\t\tdata: DirectTransportData;\n\t};\n\nexport type DirectTransportData = {\n\tsctpParameters?: SctpParameters;\n};\n\nconst logger = new Logger('DirectTransport');\n\nexport class DirectTransportImpl<\n\tDirectTransportAppData extends AppData = AppData,\n>\n\textends TransportImpl<\n\t\tDirectTransportAppData,\n\t\tDirectTransportEvents,\n\t\tDirectTransportObserver\n\t>\n\timplements Transport, DirectTransport\n{\n\t// DirectTransport data.\n\t// eslint-disable-next-line no-unused-private-class-members\n\treadonly #data: DirectTransportData;\n\n\tconstructor(\n\t\toptions: DirectTransportConstructorOptions<DirectTransportAppData>\n\t) {\n\t\tconst observer: DirectTransportObserver =\n\t\t\tnew EnhancedEventEmitter<DirectTransportObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#data = {\n\t\t\t// Nothing.\n\t\t};\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'direct' {\n\t\treturn 'direct';\n\t}\n\n\toverride get observer(): DirectTransportObserver {\n\t\treturn super.observer;\n\t}\n\n\toverride close(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tsuper.close();\n\t}\n\n\toverride routerClosed(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tsuper.routerClosed();\n\t}\n\n\tasync dump(): Promise<DirectTransportDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDirectTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parseDirectTransportDumpResponse(data);\n\t}\n\n\tasync getStats(): Promise<DirectTransportStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDirectTransport.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseGetStatsResponse(data)];\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/require-await\n\tasync connect(): Promise<void> {\n\t\tlogger.debug('connect()');\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await\n\toverride async setMaxIncomingBitrate(bitrate: number): Promise<void> {\n\t\tthrow new UnsupportedError(\n\t\t\t'setMaxIncomingBitrate() not implemented in DirectTransport'\n\t\t);\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await\n\toverride async setMaxOutgoingBitrate(bitrate: number): Promise<void> {\n\t\tthrow new UnsupportedError(\n\t\t\t'setMaxOutgoingBitrate() not implemented in DirectTransport'\n\t\t);\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await\n\toverride async setMinOutgoingBitrate(bitrate: number): Promise<void> {\n\t\tthrow new UnsupportedError(\n\t\t\t'setMinOutgoingBitrate() not implemented in DirectTransport'\n\t\t);\n\t}\n\n\tsendRtcp(rtcpPacket: Buffer): void {\n\t\tif (!Buffer.isBuffer(rtcpPacket)) {\n\t\t\tthrow new TypeError('rtcpPacket must be a Buffer');\n\t\t}\n\n\t\tconst builder = this.channel.bufferBuilder;\n\t\tconst dataOffset = FbsTransport.SendRtcpNotification.createDataVector(\n\t\t\tbuilder,\n\t\t\trtcpPacket\n\t\t);\n\t\tconst notificationOffset =\n\t\t\tFbsTransport.SendRtcpNotification.createSendRtcpNotification(\n\t\t\t\tbuilder,\n\t\t\t\tdataOffset\n\t\t\t);\n\n\t\tthis.channel.notify(\n\t\t\tFbsNotification.Event.TRANSPORT_SEND_RTCP,\n\t\t\tFbsNotification.Body.Transport_SendRtcpNotification,\n\t\t\tnotificationOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.transportId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.TRANSPORT_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace = parseTransportTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.DIRECTTRANSPORT_RTCP: {\n\t\t\t\t\t\tif (this.closed) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst notification = new FbsDirectTransport.RtcpNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tthis.safeEmit('rtcp', Buffer.from(notification.dataArray()!));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nexport function parseDirectTransportDumpResponse(\n\tbinary: FbsDirectTransport.DumpResponse\n): BaseTransportDump {\n\treturn parseBaseTransportDump(binary.base()!);\n}\n\nfunction parseGetStatsResponse(\n\tbinary: FbsDirectTransport.GetStatsResponse\n): DirectTransportStat {\n\tconst base = parseBaseTransportStats(binary.base()!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'direct-transport',\n\t};\n}\n"
  },
  {
    "path": "node/src/DirectTransportTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tTransport,\n\tBaseTransportDump,\n\tBaseTransportStats,\n\tTransportEvents,\n\tTransportObserverEvents,\n} from './TransportTypes';\nimport type { AppData } from './types';\n\nexport type DirectTransportOptions<\n\tDirectTransportAppData extends AppData = AppData,\n> = {\n\t/**\n\t * Maximum allowed size for direct messages sent from DataProducers.\n\t * Default 262144.\n\t */\n\tmaxMessageSize: number;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: DirectTransportAppData;\n};\n\nexport type DirectTransportDump = BaseTransportDump;\n\nexport type DirectTransportStat = BaseTransportStats & {\n\ttype: string;\n};\n\nexport type DirectTransportEvents = TransportEvents & {\n\trtcp: [Buffer];\n};\n\nexport type DirectTransportObserver =\n\tEnhancedEventEmitter<DirectTransportObserverEvents>;\n\nexport type DirectTransportObserverEvents = TransportObserverEvents & {\n\trtcp: [Buffer];\n};\n\nexport interface DirectTransport<\n\tDirectTransportAppData extends AppData = AppData,\n> extends Transport<\n\tDirectTransportAppData,\n\tDirectTransportEvents,\n\tDirectTransportObserver\n> {\n\t/**\n\t * Transport type.\n\t *\n\t * @override\n\t */\n\tget type(): 'direct';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): DirectTransportObserver;\n\n\t/**\n\t * Dump DirectTransport.\n\t *\n\t * @override\n\t */\n\tdump(): Promise<DirectTransportDump>;\n\n\t/**\n\t * Get DirectTransport stats.\n\t *\n\t * @override\n\t */\n\tgetStats(): Promise<DirectTransportStat[]>;\n\n\t/**\n\t * NO-OP method in DirectTransport.\n\t *\n\t * @override\n\t */\n\tconnect(): Promise<void>;\n\n\t/**\n\t * Send RTCP packet.\n\t */\n\tsendRtcp(rtcpPacket: Buffer): void;\n}\n"
  },
  {
    "path": "node/src/Logger.ts",
    "content": "import {\n\tdebug as consoleDebug,\n\twarn as consoleWarn,\n\terror as consoleError,\n} from 'node:console';\nimport debug from 'debug';\nimport type { EnhancedEventEmitter } from './enhancedEvents';\n\nconst APP_NAME = 'mediasoup';\n\ntype LoggerEmitterEvents = {\n\tdebuglog: [string, string];\n\twarnlog: [string, string];\n\terrorlog: [string, string, Error?];\n};\n\nexport type LoggerEmitter = EnhancedEventEmitter<LoggerEmitterEvents>;\n\nexport class Logger {\n\tprivate static debugLogEmitter?: LoggerEmitter;\n\tprivate static warnLogEmitter?: LoggerEmitter;\n\tprivate static errorLogEmitter?: LoggerEmitter;\n\n\treadonly #debug: debug.Debugger;\n\treadonly #warn: debug.Debugger;\n\treadonly #error: debug.Debugger;\n\n\tstatic setEmitters(\n\t\tdebugLogEmitter?: LoggerEmitter,\n\t\twarnLogEmitter?: LoggerEmitter,\n\t\terrorLogEmitter?: LoggerEmitter\n\t): void {\n\t\tLogger.debugLogEmitter = debugLogEmitter;\n\t\tLogger.warnLogEmitter = warnLogEmitter;\n\t\tLogger.errorLogEmitter = errorLogEmitter;\n\t}\n\n\tconstructor(prefix?: string) {\n\t\tif (prefix) {\n\t\t\tthis.#debug = debug(`${APP_NAME}:${prefix}`);\n\t\t\tthis.#warn = debug(`${APP_NAME}:WARN:${prefix}`);\n\t\t\tthis.#error = debug(`${APP_NAME}:ERROR:${prefix}`);\n\t\t} else {\n\t\t\tthis.#debug = debug(APP_NAME);\n\t\t\tthis.#warn = debug(`${APP_NAME}:WARN`);\n\t\t\tthis.#error = debug(`${APP_NAME}:ERROR`);\n\t\t}\n\n\t\tthis.#debug.log = consoleDebug;\n\t\tthis.#warn.log = consoleWarn;\n\t\tthis.#error.log = consoleError;\n\t}\n\n\tdebug(log: string): void {\n\t\tthis.#debug(log);\n\n\t\tLogger.debugLogEmitter?.safeEmit('debuglog', this.#debug.namespace, log);\n\t}\n\n\twarn(log: string): void {\n\t\tthis.#warn(log);\n\n\t\tLogger.warnLogEmitter?.safeEmit('warnlog', this.#warn.namespace, log);\n\t}\n\n\terror(log: string, error?: Error): void {\n\t\tthis.#error(log, error);\n\n\t\tLogger.errorLogEmitter?.safeEmit(\n\t\t\t'errorlog',\n\t\t\tthis.#error.namespace,\n\t\t\tlog,\n\t\t\terror\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "node/src/PipeTransport.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport * as ortc from './ortc';\nimport type {\n\tPipeTransport,\n\tPipeConsumerOptions,\n\tPipeTransportDump,\n\tPipeTransportStat,\n\tPipeTransportEvents,\n\tPipeTransportObserver,\n\tPipeTransportObserverEvents,\n} from './PipeTransportTypes';\nimport type { Transport, TransportTuple, SctpState } from './TransportTypes';\nimport {\n\tTransportImpl,\n\tTransportConstructorOptions,\n\tparseBaseTransportDump,\n\tparseBaseTransportStats,\n\tparseSctpState,\n\tparseTuple,\n\tparseTransportTraceEventData,\n} from './Transport';\nimport type { Producer } from './ProducerTypes';\nimport type { Consumer, ConsumerType } from './ConsumerTypes';\nimport { ConsumerImpl } from './Consumer';\nimport type { RtpParameters } from './rtpParametersTypes';\nimport {\n\tserializeRtpEncodingParameters,\n\tserializeRtpParameters,\n} from './rtpParametersFbsUtils';\nimport type { SctpParameters } from './sctpParametersTypes';\nimport type { SrtpParameters } from './srtpParametersTypes';\nimport {\n\tparseSrtpParameters,\n\tserializeSrtpParameters,\n} from './srtpParametersFbsUtils';\nimport type { AppData } from './types';\nimport { generateUUIDv4 } from './utils';\nimport { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsPipeTransport from './fbs/pipe-transport';\n\ntype PipeTransportConstructorOptions<PipeTransportAppData> =\n\tTransportConstructorOptions<PipeTransportAppData> & {\n\t\tdata: PipeTransportData;\n\t};\n\nexport type PipeTransportData = {\n\ttuple: TransportTuple;\n\tsctpParameters?: SctpParameters;\n\tsctpState?: SctpState;\n\trtx: boolean;\n\tsrtpParameters?: SrtpParameters;\n};\n\nconst logger = new Logger('PipeTransport');\n\nexport class PipeTransportImpl<PipeTransportAppData extends AppData = AppData>\n\textends TransportImpl<\n\t\tPipeTransportAppData,\n\t\tPipeTransportEvents,\n\t\tPipeTransportObserver\n\t>\n\timplements Transport, PipeTransport\n{\n\t// PipeTransport data.\n\treadonly #data: PipeTransportData;\n\n\tconstructor(options: PipeTransportConstructorOptions<PipeTransportAppData>) {\n\t\tconst observer: PipeTransportObserver =\n\t\t\tnew EnhancedEventEmitter<PipeTransportObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tlogger.debug('constructor()');\n\n\t\tconst { data } = options;\n\n\t\tthis.#data = {\n\t\t\ttuple: data.tuple,\n\t\t\tsctpParameters: data.sctpParameters,\n\t\t\tsctpState: data.sctpState,\n\t\t\trtx: data.rtx,\n\t\t\tsrtpParameters: data.srtpParameters,\n\t\t};\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'pipe' {\n\t\treturn 'pipe';\n\t}\n\n\toverride get observer(): PipeTransportObserver {\n\t\treturn super.observer;\n\t}\n\n\tget tuple(): TransportTuple {\n\t\treturn this.#data.tuple;\n\t}\n\n\tget sctpParameters(): SctpParameters | undefined {\n\t\treturn this.#data.sctpParameters;\n\t}\n\n\tget sctpState(): SctpState | undefined {\n\t\treturn this.#data.sctpState;\n\t}\n\n\tget srtpParameters(): SrtpParameters | undefined {\n\t\treturn this.#data.srtpParameters;\n\t}\n\n\toverride close(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.close();\n\t}\n\n\toverride routerClosed(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.routerClosed();\n\t}\n\n\tasync dump(): Promise<PipeTransportDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPipeTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parsePipeTransportDumpResponse(data);\n\t}\n\n\tasync getStats(): Promise<PipeTransportStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPipeTransport.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseGetStatsResponse(data)];\n\t}\n\n\tasync connect({\n\t\tip,\n\t\tport,\n\t\tsrtpParameters,\n\t}: {\n\t\tip: string;\n\t\tport: number;\n\t\tsrtpParameters?: SrtpParameters;\n\t}): Promise<void> {\n\t\tlogger.debug('connect()');\n\n\t\tconst requestOffset = createConnectRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tip,\n\t\t\tport,\n\t\t\tsrtpParameters,\n\t\t});\n\n\t\t// Wait for response.\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.PIPETRANSPORT_CONNECT,\n\t\t\tFbsRequest.Body.PipeTransport_ConnectRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPipeTransport.ConnectResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update data.\n\t\tif (data.tuple()) {\n\t\t\tthis.#data.tuple = parseTuple(data.tuple()!);\n\t\t}\n\t}\n\n\toverride async consume<ConsumerAppData extends AppData = AppData>({\n\t\tproducerId,\n\t\tappData,\n\t}: PipeConsumerOptions<ConsumerAppData>): Promise<Consumer<ConsumerAppData>> {\n\t\tlogger.debug('consume()');\n\n\t\tif (!producerId || typeof producerId !== 'string') {\n\t\t\tthrow new TypeError('missing producerId');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tconst producer = this.getProducerById(producerId);\n\n\t\tif (!producer) {\n\t\t\tthrow Error(`Producer with id \"${producerId}\" not found`);\n\t\t}\n\n\t\t// This may throw.\n\t\tconst rtpParameters = ortc.getPipeConsumerRtpParameters({\n\t\t\tconsumableRtpParameters: producer.consumableRtpParameters,\n\t\t\tenableRtx: this.#data.rtx,\n\t\t});\n\n\t\tconst consumerId = generateUUIDv4();\n\n\t\tconst consumeRequestOffset = createConsumeRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tconsumerId,\n\t\t\tproducer,\n\t\t\trtpParameters,\n\t\t});\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_CONSUME,\n\t\t\tFbsRequest.Body.Transport_ConsumeRequest,\n\t\t\tconsumeRequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst consumeResponse = new FbsTransport.ConsumeResponse();\n\n\t\tresponse.body(consumeResponse);\n\n\t\tconst status = consumeResponse.unpack();\n\n\t\tconst data = {\n\t\t\tproducerId,\n\t\t\tkind: producer.kind,\n\t\t\trtpParameters,\n\t\t\ttype: 'pipe' as ConsumerType,\n\t\t};\n\n\t\tconst consumer: Consumer<ConsumerAppData> = new ConsumerImpl({\n\t\t\tinternal: {\n\t\t\t\t...this.internal,\n\t\t\t\tconsumerId,\n\t\t\t},\n\t\t\tdata,\n\t\t\tchannel: this.channel,\n\t\t\tappData,\n\t\t\tpaused: status.paused,\n\t\t\tproducerPaused: status.producerPaused,\n\t\t});\n\n\t\tthis.consumers.set(consumer.id, consumer);\n\t\tconsumer.on('@close', () => this.consumers.delete(consumer.id));\n\t\tconsumer.on('@producerclose', () => this.consumers.delete(consumer.id));\n\n\t\t// Emit observer event.\n\t\tthis.observer.safeEmit('newconsumer', consumer);\n\n\t\treturn consumer;\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.transportId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.TRANSPORT_SCTP_STATE_CHANGE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.SctpStateChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst sctpState = parseSctpState(notification.sctpState());\n\n\t\t\t\t\t\tthis.#data.sctpState = sctpState;\n\n\t\t\t\t\t\tthis.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.TRANSPORT_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace = parseTransportTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\n/*\n * flatbuffers helpers.\n */\n\nexport function parsePipeTransportDumpResponse(\n\tbinary: FbsPipeTransport.DumpResponse\n): PipeTransportDump {\n\t// Retrieve BaseTransportDump.\n\tconst baseTransportDump = parseBaseTransportDump(binary.base()!);\n\t// Retrieve RTP Tuple.\n\tconst tuple = parseTuple(binary.tuple()!);\n\n\t// Retrieve SRTP Parameters.\n\tlet srtpParameters: SrtpParameters | undefined;\n\n\tif (binary.srtpParameters()) {\n\t\tsrtpParameters = parseSrtpParameters(binary.srtpParameters()!);\n\t}\n\n\treturn {\n\t\t...baseTransportDump,\n\t\ttuple: tuple,\n\t\trtx: binary.rtx(),\n\t\tsrtpParameters: srtpParameters,\n\t};\n}\n\nfunction parseGetStatsResponse(\n\tbinary: FbsPipeTransport.GetStatsResponse\n): PipeTransportStat {\n\tconst base = parseBaseTransportStats(binary.base()!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'pipe-transport',\n\t\ttuple: parseTuple(binary.tuple()!),\n\t};\n}\n\nfunction createConsumeRequest({\n\tbuilder,\n\tconsumerId,\n\tproducer,\n\trtpParameters,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tconsumerId: string;\n\tproducer: Producer;\n\trtpParameters: RtpParameters;\n}): number {\n\t// Build the request.\n\tconst producerIdOffset = builder.createString(producer.id);\n\tconst consumerIdOffset = builder.createString(consumerId);\n\tconst rtpParametersOffset = serializeRtpParameters(builder, rtpParameters);\n\tlet consumableRtpEncodingsOffset: number | undefined;\n\n\tif (producer.consumableRtpParameters.encodings) {\n\t\tconsumableRtpEncodingsOffset = serializeRtpEncodingParameters(\n\t\t\tbuilder,\n\t\t\tproducer.consumableRtpParameters.encodings\n\t\t);\n\t}\n\n\tconst ConsumeRequest = FbsTransport.ConsumeRequest;\n\n\t// Create Consume Request.\n\tConsumeRequest.startConsumeRequest(builder);\n\tConsumeRequest.addConsumerId(builder, consumerIdOffset);\n\tConsumeRequest.addProducerId(builder, producerIdOffset);\n\tConsumeRequest.addKind(\n\t\tbuilder,\n\t\tproducer.kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO\n\t);\n\tConsumeRequest.addRtpParameters(builder, rtpParametersOffset);\n\tConsumeRequest.addType(builder, FbsRtpParameters.Type.PIPE);\n\n\tif (consumableRtpEncodingsOffset) {\n\t\tConsumeRequest.addConsumableRtpEncodings(\n\t\t\tbuilder,\n\t\t\tconsumableRtpEncodingsOffset\n\t\t);\n\t}\n\n\treturn ConsumeRequest.endConsumeRequest(builder);\n}\n\nfunction createConnectRequest({\n\tbuilder,\n\tip,\n\tport,\n\tsrtpParameters,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tip?: string;\n\tport?: number;\n\tsrtpParameters?: SrtpParameters;\n}): number {\n\tlet ipOffset = 0;\n\tlet srtpParametersOffset = 0;\n\n\tif (ip) {\n\t\tipOffset = builder.createString(ip);\n\t}\n\n\t// Serialize SrtpParameters.\n\tif (srtpParameters) {\n\t\tsrtpParametersOffset = serializeSrtpParameters(builder, srtpParameters);\n\t}\n\n\t// Create PlainTransportConnectData.\n\tFbsPipeTransport.ConnectRequest.startConnectRequest(builder);\n\tFbsPipeTransport.ConnectRequest.addIp(builder, ipOffset);\n\n\tif (typeof port === 'number') {\n\t\tFbsPipeTransport.ConnectRequest.addPort(builder, port);\n\t}\n\tif (srtpParameters) {\n\t\tFbsPipeTransport.ConnectRequest.addSrtpParameters(\n\t\t\tbuilder,\n\t\t\tsrtpParametersOffset\n\t\t);\n\t}\n\n\treturn FbsPipeTransport.ConnectRequest.endConnectRequest(builder);\n}\n"
  },
  {
    "path": "node/src/PipeTransportTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tTransport,\n\tTransportListenInfo,\n\tTransportListenIp,\n\tTransportTuple,\n\tSctpState,\n\tBaseTransportDump,\n\tBaseTransportStats,\n\tTransportEvents,\n\tTransportObserverEvents,\n} from './TransportTypes';\nimport type { Consumer } from './ConsumerTypes';\nimport type { SrtpParameters } from './srtpParametersTypes';\nimport type { SctpParameters, NumSctpStreams } from './sctpParametersTypes';\nimport type { Either, AppData } from './types';\n\nexport type PipeTransportOptions<\n\tPipeTransportAppData extends AppData = AppData,\n> = {\n\t/**\n\t * Create a SCTP association. Default false.\n\t */\n\tenableSctp?: boolean;\n\n\t/**\n\t * SCTP streams number.\n\t */\n\tnumSctpStreams?: NumSctpStreams;\n\n\t/**\n\t * Maximum allowed size for SCTP messages sent by DataProducers.\n\t * Default 268435456.\n\t */\n\tmaxSctpMessageSize?: number;\n\n\t/**\n\t * Maximum SCTP send buffer used by DataConsumers.\n\t * Default 268435456.\n\t */\n\tsctpSendBufferSize?: number;\n\n\t/**\n\t * Enable RTX and NACK for RTP retransmission. Useful if both Routers are\n\t * located in different hosts and there is packet lost in the link. For this\n\t * to work, both PipeTransports must enable this setting. Default false.\n\t */\n\tenableRtx?: boolean;\n\n\t/**\n\t * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers\n\t * are located in different hosts. For this to work, connect() must be called\n\t * with remote SRTP parameters. Default false.\n\t */\n\tenableSrtp?: boolean;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: PipeTransportAppData;\n} & PipeTransportListen;\n\ntype PipeTransportListen = Either<\n\tPipeTransportListenInfo,\n\tPipeTransportListenIp\n>;\n\ntype PipeTransportListenInfo = {\n\t/**\n\t * Listening info.\n\t */\n\tlistenInfo: TransportListenInfo;\n};\n\ntype PipeTransportListenIp = {\n\t/**\n\t * Listening IP address.\n\t */\n\tlistenIp: TransportListenIp | string;\n\n\t/**\n\t * Fixed port to listen on instead of selecting automatically from Worker's\n\t * port range.\n\t */\n\tport?: number;\n};\n\nexport type PipeConsumerOptions<ConsumerAppData extends AppData = AppData> = {\n\t/**\n\t * The id of the Producer to consume.\n\t */\n\tproducerId: string;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: ConsumerAppData;\n};\n\nexport type PipeTransportDump = BaseTransportDump & {\n\ttuple: TransportTuple;\n\trtx: boolean;\n\tsrtpParameters?: SrtpParameters;\n};\n\nexport type PipeTransportStat = BaseTransportStats & {\n\ttype: string;\n\ttuple: TransportTuple;\n};\n\nexport type PipeTransportEvents = TransportEvents & {\n\tsctpstatechange: [SctpState];\n};\n\nexport type PipeTransportObserver =\n\tEnhancedEventEmitter<PipeTransportObserverEvents>;\n\nexport type PipeTransportObserverEvents = TransportObserverEvents & {\n\tsctpstatechange: [SctpState];\n};\n\nexport interface PipeTransport<\n\tPipeTransportAppData extends AppData = AppData,\n> extends Transport<\n\tPipeTransportAppData,\n\tPipeTransportEvents,\n\tPipeTransportObserver\n> {\n\t/**\n\t * Transport type.\n\t *\n\t * @override\n\t */\n\tget type(): 'pipe';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): PipeTransportObserver;\n\n\t/**\n\t * PipeTransport tuple.\n\t */\n\tget tuple(): TransportTuple;\n\n\t/**\n\t * SCTP parameters.\n\t */\n\tget sctpParameters(): SctpParameters | undefined;\n\n\t/**\n\t * SCTP state.\n\t */\n\tget sctpState(): SctpState | undefined;\n\n\t/**\n\t * SRTP parameters.\n\t */\n\tget srtpParameters(): SrtpParameters | undefined;\n\n\t/**\n\t * Dump PipeTransport.\n\t *\n\t * @override\n\t */\n\tdump(): Promise<PipeTransportDump>;\n\n\t/**\n\t * Get PipeTransport stats.\n\t *\n\t * @override\n\t */\n\tgetStats(): Promise<PipeTransportStat[]>;\n\n\t/**\n\t * Provide the PipeTransport remote parameters.\n\t *\n\t * @override\n\t */\n\tconnect({\n\t\tip,\n\t\tport,\n\t\tsrtpParameters,\n\t}: {\n\t\tip: string;\n\t\tport: number;\n\t\tsrtpParameters?: SrtpParameters;\n\t}): Promise<void>;\n\n\t/**\n\t * Create a pipe Consumer.\n\t *\n\t * @override\n\t */\n\tconsume<ConsumerAppData extends AppData = AppData>({\n\t\tproducerId,\n\t\tappData,\n\t}: PipeConsumerOptions<ConsumerAppData>): Promise<Consumer<ConsumerAppData>>;\n}\n"
  },
  {
    "path": "node/src/PlainTransport.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tPlainTransport,\n\tPlainTransportDump,\n\tPlainTransportStat,\n\tPlainTransportEvents,\n\tPlainTransportObserver,\n\tPlainTransportObserverEvents,\n} from './PlainTransportTypes';\nimport type { Transport, TransportTuple, SctpState } from './TransportTypes';\nimport {\n\tTransportImpl,\n\tTransportConstructorOptions,\n\tparseSctpState,\n\tparseTuple,\n\tparseBaseTransportDump,\n\tparseBaseTransportStats,\n\tparseTransportTraceEventData,\n} from './Transport';\nimport type { SctpParameters } from './sctpParametersTypes';\nimport type { SrtpParameters } from './srtpParametersTypes';\nimport {\n\tparseSrtpParameters,\n\tserializeSrtpParameters,\n} from './srtpParametersFbsUtils';\nimport type { AppData } from './types';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsPlainTransport from './fbs/plain-transport';\n\ntype PlainTransportConstructorOptions<PlainTransportAppData> =\n\tTransportConstructorOptions<PlainTransportAppData> & {\n\t\tdata: PlainTransportData;\n\t};\n\nexport type PlainTransportData = {\n\trtcpMux?: boolean;\n\tcomedia?: boolean;\n\ttuple: TransportTuple;\n\trtcpTuple?: TransportTuple;\n\tsctpParameters?: SctpParameters;\n\tsctpState?: SctpState;\n\tsrtpParameters?: SrtpParameters;\n};\n\nconst logger = new Logger('PlainTransport');\n\nexport class PlainTransportImpl<PlainTransportAppData extends AppData = AppData>\n\textends TransportImpl<\n\t\tPlainTransportAppData,\n\t\tPlainTransportEvents,\n\t\tPlainTransportObserver\n\t>\n\timplements Transport, PlainTransport\n{\n\t// PlainTransport data.\n\treadonly #data: PlainTransportData;\n\n\tconstructor(\n\t\toptions: PlainTransportConstructorOptions<PlainTransportAppData>\n\t) {\n\t\tconst observer: PlainTransportObserver =\n\t\t\tnew EnhancedEventEmitter<PlainTransportObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tlogger.debug('constructor()');\n\n\t\tconst { data } = options;\n\n\t\tthis.#data = {\n\t\t\trtcpMux: data.rtcpMux,\n\t\t\tcomedia: data.comedia,\n\t\t\ttuple: data.tuple,\n\t\t\trtcpTuple: data.rtcpTuple,\n\t\t\tsctpParameters: data.sctpParameters,\n\t\t\tsctpState: data.sctpState,\n\t\t\tsrtpParameters: data.srtpParameters,\n\t\t};\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'plain' {\n\t\treturn 'plain';\n\t}\n\n\toverride get observer(): PlainTransportObserver {\n\t\treturn super.observer;\n\t}\n\n\tget tuple(): TransportTuple {\n\t\treturn this.#data.tuple;\n\t}\n\n\tget rtcpTuple(): TransportTuple | undefined {\n\t\treturn this.#data.rtcpTuple;\n\t}\n\n\tget sctpParameters(): SctpParameters | undefined {\n\t\treturn this.#data.sctpParameters;\n\t}\n\n\tget sctpState(): SctpState | undefined {\n\t\treturn this.#data.sctpState;\n\t}\n\n\tget srtpParameters(): SrtpParameters | undefined {\n\t\treturn this.#data.srtpParameters;\n\t}\n\n\toverride close(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.close();\n\t}\n\n\toverride routerClosed(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.routerClosed();\n\t}\n\n\tasync dump(): Promise<PlainTransportDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPlainTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parsePlainTransportDumpResponse(data);\n\t}\n\n\tasync getStats(): Promise<PlainTransportStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPlainTransport.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseGetStatsResponse(data)];\n\t}\n\n\tasync connect({\n\t\tip,\n\t\tport,\n\t\trtcpPort,\n\t\tsrtpParameters,\n\t}: {\n\t\tip?: string;\n\t\tport?: number;\n\t\trtcpPort?: number;\n\t\tsrtpParameters?: SrtpParameters;\n\t}): Promise<void> {\n\t\tlogger.debug('connect()');\n\n\t\tconst requestOffset = createConnectRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tip,\n\t\t\tport,\n\t\t\trtcpPort,\n\t\t\tsrtpParameters,\n\t\t});\n\n\t\t// Wait for response.\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.PLAINTRANSPORT_CONNECT,\n\t\t\tFbsRequest.Body.PlainTransport_ConnectRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPlainTransport.ConnectResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update data.\n\t\tif (data.tuple()) {\n\t\t\tthis.#data.tuple = parseTuple(data.tuple()!);\n\t\t}\n\n\t\tif (data.rtcpTuple()) {\n\t\t\tthis.#data.rtcpTuple = parseTuple(data.rtcpTuple()!);\n\t\t}\n\n\t\tif (data.srtpParameters()) {\n\t\t\tthis.#data.srtpParameters = parseSrtpParameters(data.srtpParameters()!);\n\t\t}\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.transportId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.PLAINTRANSPORT_TUPLE: {\n\t\t\t\t\t\tconst notification = new FbsPlainTransport.TupleNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst tuple = parseTuple(notification.tuple()!);\n\n\t\t\t\t\t\tthis.#data.tuple = tuple;\n\n\t\t\t\t\t\tthis.safeEmit('tuple', tuple);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('tuple', tuple);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.PLAINTRANSPORT_RTCP_TUPLE: {\n\t\t\t\t\t\tconst notification = new FbsPlainTransport.RtcpTupleNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst rtcpTuple = parseTuple(notification.tuple()!);\n\n\t\t\t\t\t\tthis.#data.rtcpTuple = rtcpTuple;\n\n\t\t\t\t\t\tthis.safeEmit('rtcptuple', rtcpTuple);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('rtcptuple', rtcpTuple);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.TRANSPORT_SCTP_STATE_CHANGE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.SctpStateChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst sctpState = parseSctpState(notification.sctpState());\n\n\t\t\t\t\t\tthis.#data.sctpState = sctpState;\n\n\t\t\t\t\t\tthis.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.TRANSPORT_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace = parseTransportTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nexport function parsePlainTransportDumpResponse(\n\tbinary: FbsPlainTransport.DumpResponse\n): PlainTransportDump {\n\t// Retrieve BaseTransportDump.\n\tconst baseTransportDump = parseBaseTransportDump(binary.base()!);\n\t// Retrieve RTP Tuple.\n\tconst tuple = parseTuple(binary.tuple()!);\n\n\t// Retrieve RTCP Tuple.\n\tlet rtcpTuple: TransportTuple | undefined;\n\n\tif (binary.rtcpTuple()) {\n\t\trtcpTuple = parseTuple(binary.rtcpTuple()!);\n\t}\n\n\t// Retrieve SRTP Parameters.\n\tlet srtpParameters: SrtpParameters | undefined;\n\n\tif (binary.srtpParameters()) {\n\t\tsrtpParameters = parseSrtpParameters(binary.srtpParameters()!);\n\t}\n\n\treturn {\n\t\t...baseTransportDump,\n\t\trtcpMux: binary.rtcpMux(),\n\t\tcomedia: binary.comedia(),\n\t\ttuple: tuple,\n\t\trtcpTuple: rtcpTuple,\n\t\tsrtpParameters: srtpParameters,\n\t};\n}\n\nfunction parseGetStatsResponse(\n\tbinary: FbsPlainTransport.GetStatsResponse\n): PlainTransportStat {\n\tconst base = parseBaseTransportStats(binary.base()!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'plain-rtp-transport',\n\t\trtcpMux: binary.rtcpMux(),\n\t\tcomedia: binary.comedia(),\n\t\ttuple: parseTuple(binary.tuple()!),\n\t\trtcpTuple: binary.rtcpTuple() ? parseTuple(binary.rtcpTuple()!) : undefined,\n\t};\n}\n\nfunction createConnectRequest({\n\tbuilder,\n\tip,\n\tport,\n\trtcpPort,\n\tsrtpParameters,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tip?: string;\n\tport?: number;\n\trtcpPort?: number;\n\tsrtpParameters?: SrtpParameters;\n}): number {\n\tlet ipOffset = 0;\n\tlet srtpParametersOffset = 0;\n\n\tif (ip) {\n\t\tipOffset = builder.createString(ip);\n\t}\n\n\t// Serialize SrtpParameters.\n\tif (srtpParameters) {\n\t\tsrtpParametersOffset = serializeSrtpParameters(builder, srtpParameters);\n\t}\n\n\t// Create PlainTransportConnectData.\n\tFbsPlainTransport.ConnectRequest.startConnectRequest(builder);\n\tFbsPlainTransport.ConnectRequest.addIp(builder, ipOffset);\n\n\tif (typeof port === 'number') {\n\t\tFbsPlainTransport.ConnectRequest.addPort(builder, port);\n\t}\n\tif (typeof rtcpPort === 'number') {\n\t\tFbsPlainTransport.ConnectRequest.addRtcpPort(builder, rtcpPort);\n\t}\n\tif (srtpParameters) {\n\t\tFbsPlainTransport.ConnectRequest.addSrtpParameters(\n\t\t\tbuilder,\n\t\t\tsrtpParametersOffset\n\t\t);\n\t}\n\n\treturn FbsPlainTransport.ConnectRequest.endConnectRequest(builder);\n}\n"
  },
  {
    "path": "node/src/PlainTransportTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tTransport,\n\tTransportListenInfo,\n\tTransportListenIp,\n\tTransportTuple,\n\tSctpState,\n\tBaseTransportDump,\n\tBaseTransportStats,\n\tTransportEvents,\n\tTransportObserverEvents,\n} from './TransportTypes';\nimport type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes';\nimport type { SctpParameters, NumSctpStreams } from './sctpParametersTypes';\nimport type { Either, AppData } from './types';\n\nexport type PlainTransportOptions<\n\tPlainTransportAppData extends AppData = AppData,\n> = {\n\t/**\n\t * Use RTCP-mux (RTP and RTCP in the same port). Default true.\n\t */\n\trtcpMux?: boolean;\n\n\t/**\n\t * Whether remote IP:port should be auto-detected based on first RTP/RTCP\n\t * packet received. If enabled, connect() method must not be called unless\n\t * SRTP is enabled. If so, it must be called with just remote SRTP parameters.\n\t * Default false.\n\t */\n\tcomedia?: boolean;\n\n\t/**\n\t * Create a SCTP association. Default false.\n\t */\n\tenableSctp?: boolean;\n\n\t/**\n\t * SCTP streams number.\n\t */\n\tnumSctpStreams?: NumSctpStreams;\n\n\t/**\n\t * Maximum allowed size for SCTP messages sent by DataProducers.\n\t * Default 262144.\n\t */\n\tmaxSctpMessageSize?: number;\n\n\t/**\n\t * Maximum SCTP send buffer used by DataConsumers.\n\t * Default 262144.\n\t */\n\tsctpSendBufferSize?: number;\n\n\t/**\n\t * Enable SRTP. For this to work, connect() must be called\n\t * with remote SRTP parameters. Default false.\n\t */\n\tenableSrtp?: boolean;\n\n\t/**\n\t * The SRTP crypto suite to be used if enableSrtp is set. Default\n\t * 'AES_CM_128_HMAC_SHA1_80'.\n\t */\n\tsrtpCryptoSuite?: SrtpCryptoSuite;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: PlainTransportAppData;\n} & PlainTransportListen;\n\ntype PlainTransportListen = Either<\n\tPlainTransportListenInfo,\n\tPlainTransportListenIp\n>;\n\ntype PlainTransportListenInfo = {\n\t/**\n\t * Listening info.\n\t */\n\tlistenInfo: TransportListenInfo;\n\n\t/**\n\t * Optional listening info for RTCP.\n\t */\n\trtcpListenInfo?: TransportListenInfo;\n};\n\ntype PlainTransportListenIp = {\n\t/**\n\t * Listening IP address.\n\t */\n\tlistenIp: TransportListenIp | string;\n\n\t/**\n\t * Fixed port to listen on instead of selecting automatically from Worker's\n\t * port range.\n\t */\n\tport?: number;\n};\n\nexport type PlainTransportDump = BaseTransportDump & {\n\trtcpMux: boolean;\n\tcomedia: boolean;\n\ttuple: TransportTuple;\n\trtcpTuple?: TransportTuple;\n\tsrtpParameters?: SrtpParameters;\n};\n\nexport type PlainTransportStat = BaseTransportStats & {\n\ttype: string;\n\trtcpMux: boolean;\n\tcomedia: boolean;\n\ttuple: TransportTuple;\n\trtcpTuple?: TransportTuple;\n};\n\nexport type PlainTransportEvents = TransportEvents & {\n\ttuple: [TransportTuple];\n\trtcptuple: [TransportTuple];\n\tsctpstatechange: [SctpState];\n};\n\nexport type PlainTransportObserver =\n\tEnhancedEventEmitter<PlainTransportObserverEvents>;\n\nexport type PlainTransportObserverEvents = TransportObserverEvents & {\n\ttuple: [TransportTuple];\n\trtcptuple: [TransportTuple];\n\tsctpstatechange: [SctpState];\n};\n\nexport interface PlainTransport<\n\tPlainTransportAppData extends AppData = AppData,\n> extends Transport<\n\tPlainTransportAppData,\n\tPlainTransportEvents,\n\tPlainTransportObserver\n> {\n\t/**\n\t * Transport type.\n\t *\n\t * @override\n\t */\n\tget type(): 'plain';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): PlainTransportObserver;\n\n\t/**\n\t * PlainTransport tuple.\n\t */\n\tget tuple(): TransportTuple;\n\n\t/**\n\t * PlainTransport RTCP tuple.\n\t */\n\tget rtcpTuple(): TransportTuple | undefined;\n\n\t/**\n\t * SCTP parameters.\n\t */\n\tget sctpParameters(): SctpParameters | undefined;\n\n\t/**\n\t * SCTP state.\n\t */\n\tget sctpState(): SctpState | undefined;\n\n\t/**\n\t * SRTP parameters.\n\t */\n\tget srtpParameters(): SrtpParameters | undefined;\n\n\t/**\n\t * Dump PlainTransport.\n\t *\n\t * @override\n\t */\n\tdump(): Promise<PlainTransportDump>;\n\n\t/**\n\t * Get PlainTransport stats.\n\t *\n\t * @override\n\t */\n\tgetStats(): Promise<PlainTransportStat[]>;\n\n\t/**\n\t * Provide the PlainTransport remote parameters.\n\t *\n\t * @override\n\t */\n\tconnect({\n\t\tip,\n\t\tport,\n\t\trtcpPort,\n\t\tsrtpParameters,\n\t}: {\n\t\tip?: string;\n\t\tport?: number;\n\t\trtcpPort?: number;\n\t\tsrtpParameters?: SrtpParameters;\n\t}): Promise<void>;\n}\n"
  },
  {
    "path": "node/src/Producer.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tProducer,\n\tProducerType,\n\tProducerScore,\n\tProducerVideoOrientation,\n\tProducerDump,\n\tProducerStat,\n\tProducerTraceEventType,\n\tProducerTraceEventData,\n\tProducerEvents,\n\tProducerObserver,\n\tProducerObserverEvents,\n} from './ProducerTypes';\nimport { Channel } from './Channel';\nimport type { TransportInternal } from './Transport';\nimport type { MediaKind, RtpParameters } from './rtpParametersTypes';\nimport { parseRtpParameters } from './rtpParametersFbsUtils';\nimport { parseRtpStreamRecvStats } from './rtpStreamStatsFbsUtils';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Event, Notification } from './fbs/notification';\nimport { TraceDirection as FbsTraceDirection } from './fbs/common';\nimport * as FbsNotification from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsProducer from './fbs/producer';\nimport * as FbsProducerTraceInfo from './fbs/producer/trace-info';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\n\ntype ProducerInternal = TransportInternal & {\n\tproducerId: string;\n};\n\nconst logger = new Logger('Producer');\n\ntype ProducerData = {\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n\ttype: ProducerType;\n\tconsumableRtpParameters: RtpParameters;\n};\n\nexport class ProducerImpl<ProducerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<ProducerEvents>\n\timplements Producer\n{\n\t// Internal data.\n\treadonly #internal: ProducerInternal;\n\n\t// Producer data.\n\treadonly #data: ProducerData;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Paused flag.\n\t#paused = false;\n\n\t// Custom app data.\n\t#appData: ProducerAppData;\n\n\t// Current score.\n\t#score: ProducerScore[] = [];\n\n\t// Observer instance.\n\treadonly #observer: ProducerObserver =\n\t\tnew EnhancedEventEmitter<ProducerObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tdata,\n\t\tchannel,\n\t\tappData,\n\t\tpaused,\n\t}: {\n\t\tinternal: ProducerInternal;\n\t\tdata: ProducerData;\n\t\tchannel: Channel;\n\t\tappData?: ProducerAppData;\n\t\tpaused: boolean;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#data = data;\n\t\tthis.#channel = channel;\n\t\tthis.#paused = paused;\n\t\tthis.#appData = appData ?? ({} as ProducerAppData);\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.producerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget kind(): MediaKind {\n\t\treturn this.#data.kind;\n\t}\n\n\tget rtpParameters(): RtpParameters {\n\t\treturn this.#data.rtpParameters;\n\t}\n\n\tget type(): ProducerType {\n\t\treturn this.#data.type;\n\t}\n\n\tget consumableRtpParameters(): RtpParameters {\n\t\treturn this.#data.consumableRtpParameters;\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#paused;\n\t}\n\n\tget score(): ProducerScore[] {\n\t\treturn this.#score;\n\t}\n\n\tget appData(): ProducerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: ProducerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): ProducerObserver {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t *\n\t * @private\n\t */\n\tget channelForTesting(): Channel {\n\t\treturn this.#channel;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.producerId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.CloseProducerRequestT(\n\t\t\tthis.#internal.producerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.TRANSPORT_CLOSE_PRODUCER,\n\t\t\t\tFbsRequest.Body.Transport_CloseProducerRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.#internal.transportId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\ttransportClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('transportClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.#channel.removeAllListeners(this.#internal.producerId);\n\n\t\tthis.safeEmit('transportclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<ProducerDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.PRODUCER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.producerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst dumpResponse = new FbsProducer.DumpResponse();\n\n\t\tresponse.body(dumpResponse);\n\n\t\treturn parseProducerDump(dumpResponse);\n\t}\n\n\tasync getStats(): Promise<ProducerStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.PRODUCER_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.producerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsProducer.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parseProducerStats(data);\n\t}\n\n\tasync pause(): Promise<void> {\n\t\tlogger.debug('pause()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.PRODUCER_PAUSE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.producerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = true;\n\n\t\t// Emit observer event.\n\t\tif (!wasPaused) {\n\t\t\tthis.#observer.safeEmit('pause');\n\t\t}\n\t}\n\n\tasync resume(): Promise<void> {\n\t\tlogger.debug('resume()');\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.PRODUCER_RESUME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.producerId\n\t\t);\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tthis.#paused = false;\n\n\t\t// Emit observer event.\n\t\tif (wasPaused) {\n\t\t\tthis.#observer.safeEmit('resume');\n\t\t}\n\t}\n\n\tasync enableTraceEvent(types: ProducerTraceEventType[] = []): Promise<void> {\n\t\tlogger.debug('enableTraceEvent()');\n\n\t\tif (!Array.isArray(types)) {\n\t\t\tthrow new TypeError('types must be an array');\n\t\t}\n\t\tif (types.find(type => typeof type !== 'string')) {\n\t\t\tthrow new TypeError('every type must be a string');\n\t\t}\n\n\t\t// Convert event types.\n\t\tconst fbsEventTypes: FbsProducer.TraceEventType[] = [];\n\n\t\tfor (const eventType of types) {\n\t\t\ttry {\n\t\t\t\tfbsEventTypes.push(producerTraceEventTypeToFbs(eventType));\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn('enableTraceEvent() | [error:${error}]');\n\t\t\t}\n\t\t}\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsProducer.EnableTraceEventRequestT(\n\t\t\tfbsEventTypes\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.PRODUCER_ENABLE_TRACE_EVENT,\n\t\t\tFbsRequest.Body.Producer_EnableTraceEventRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.producerId\n\t\t);\n\t}\n\n\tsend(rtpPacket: Buffer): void {\n\t\tif (!Buffer.isBuffer(rtpPacket)) {\n\t\t\tthrow new TypeError('rtpPacket must be a Buffer');\n\t\t}\n\n\t\tconst builder = this.#channel.bufferBuilder;\n\t\tconst dataOffset = FbsProducer.SendNotification.createDataVector(\n\t\t\tbuilder,\n\t\t\trtpPacket\n\t\t);\n\t\tconst notificationOffset =\n\t\t\tFbsProducer.SendNotification.createSendNotification(builder, dataOffset);\n\n\t\tthis.#channel.notify(\n\t\t\tFbsNotification.Event.PRODUCER_SEND,\n\t\t\tFbsNotification.Body.Producer_SendNotification,\n\t\t\tnotificationOffset,\n\t\t\tthis.#internal.producerId\n\t\t);\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.#channel.on(\n\t\t\tthis.#internal.producerId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.PRODUCER_SCORE: {\n\t\t\t\t\t\tconst notification = new FbsProducer.ScoreNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst score: ProducerScore[] = fbsUtils.parseVector(\n\t\t\t\t\t\t\tnotification,\n\t\t\t\t\t\t\t'scores',\n\t\t\t\t\t\t\tparseProducerScore\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tthis.#score = score;\n\n\t\t\t\t\t\tthis.safeEmit('score', score);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('score', score);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.PRODUCER_VIDEO_ORIENTATION_CHANGE: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsProducer.VideoOrientationChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst videoOrientation: ProducerVideoOrientation =\n\t\t\t\t\t\t\tnotification.unpack();\n\n\t\t\t\t\t\tthis.safeEmit('videoorientationchange', videoOrientation);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('videoorientationchange', videoOrientation);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.PRODUCER_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsProducer.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace: ProducerTraceEventData =\n\t\t\t\t\t\t\tparseTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.#observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nexport function producerTypeFromFbs(type: FbsRtpParameters.Type): ProducerType {\n\tswitch (type) {\n\t\tcase FbsRtpParameters.Type.SIMPLE: {\n\t\t\treturn 'simple';\n\t\t}\n\n\t\tcase FbsRtpParameters.Type.SIMULCAST: {\n\t\t\treturn 'simulcast';\n\t\t}\n\n\t\tcase FbsRtpParameters.Type.SVC: {\n\t\t\treturn 'svc';\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid FbsRtpParameters.Type: ${type}`);\n\t\t}\n\t}\n}\n\nexport function producerTypeToFbs(type: ProducerType): FbsRtpParameters.Type {\n\tswitch (type) {\n\t\tcase 'simple': {\n\t\t\treturn FbsRtpParameters.Type.SIMPLE;\n\t\t}\n\n\t\tcase 'simulcast': {\n\t\t\treturn FbsRtpParameters.Type.SIMULCAST;\n\t\t}\n\n\t\tcase 'svc': {\n\t\t\treturn FbsRtpParameters.Type.SVC;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid ProducerType: ${type}`);\n\t\t}\n\t}\n}\n\nfunction producerTraceEventTypeToFbs(\n\teventType: ProducerTraceEventType\n): FbsProducer.TraceEventType {\n\tswitch (eventType) {\n\t\tcase 'keyframe': {\n\t\t\treturn FbsProducer.TraceEventType.KEYFRAME;\n\t\t}\n\n\t\tcase 'fir': {\n\t\t\treturn FbsProducer.TraceEventType.FIR;\n\t\t}\n\n\t\tcase 'nack': {\n\t\t\treturn FbsProducer.TraceEventType.NACK;\n\t\t}\n\n\t\tcase 'pli': {\n\t\t\treturn FbsProducer.TraceEventType.PLI;\n\t\t}\n\n\t\tcase 'rtp': {\n\t\t\treturn FbsProducer.TraceEventType.RTP;\n\t\t}\n\n\t\tcase 'sr': {\n\t\t\treturn FbsProducer.TraceEventType.SR;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid ProducerTraceEventType: ${eventType}`);\n\t\t}\n\t}\n}\n\nfunction producerTraceEventTypeFromFbs(\n\teventType: FbsProducer.TraceEventType\n): ProducerTraceEventType {\n\tswitch (eventType) {\n\t\tcase FbsProducer.TraceEventType.KEYFRAME: {\n\t\t\treturn 'keyframe';\n\t\t}\n\n\t\tcase FbsProducer.TraceEventType.FIR: {\n\t\t\treturn 'fir';\n\t\t}\n\n\t\tcase FbsProducer.TraceEventType.NACK: {\n\t\t\treturn 'nack';\n\t\t}\n\n\t\tcase FbsProducer.TraceEventType.PLI: {\n\t\t\treturn 'pli';\n\t\t}\n\n\t\tcase FbsProducer.TraceEventType.RTP: {\n\t\t\treturn 'rtp';\n\t\t}\n\n\t\tcase FbsProducer.TraceEventType.SR: {\n\t\t\treturn 'sr';\n\t\t}\n\t}\n}\n\nfunction parseProducerDump(data: FbsProducer.DumpResponse): ProducerDump {\n\treturn {\n\t\tid: data.id()!,\n\t\tkind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video',\n\t\ttype: producerTypeFromFbs(data.type()),\n\t\trtpParameters: parseRtpParameters(data.rtpParameters()!),\n\t\t// NOTE: optional values are represented with null instead of undefined.\n\t\t// TODO: Make flatbuffers TS return undefined instead of null.\n\t\trtpMapping: data.rtpMapping()?.unpack(),\n\t\t// NOTE: optional values are represented with null instead of undefined.\n\t\t// TODO: Make flatbuffers TS return undefined instead of null.\n\t\trtpStreams:\n\t\t\tdata.rtpStreamsLength() > 0\n\t\t\t\t? fbsUtils.parseVector(data, 'rtpStreams', rtpStream =>\n\t\t\t\t\t\trtpStream.unpack()\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\ttraceEventTypes: fbsUtils.parseVector<ProducerTraceEventType>(\n\t\t\tdata,\n\t\t\t'traceEventTypes',\n\t\t\tproducerTraceEventTypeFromFbs\n\t\t),\n\t\tpaused: data.paused(),\n\t};\n}\n\nfunction parseProducerStats(\n\tbinary: FbsProducer.GetStatsResponse\n): ProducerStat[] {\n\treturn fbsUtils.parseVector(binary, 'stats', parseRtpStreamRecvStats);\n}\n\nfunction parseProducerScore(binary: FbsProducer.Score): ProducerScore {\n\treturn {\n\t\tencodingIdx: binary.encodingIdx(),\n\t\tssrc: binary.ssrc(),\n\t\trid: binary.rid() ?? undefined,\n\t\tscore: binary.score(),\n\t};\n}\n\nfunction parseTraceEventData(\n\ttrace: FbsProducer.TraceNotification\n): ProducerTraceEventData {\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tlet info: any;\n\n\tif (trace.infoType() !== FbsProducer.TraceInfo.NONE) {\n\t\tconst accessor = trace.info.bind(trace);\n\n\t\tinfo = FbsProducerTraceInfo.unionToTraceInfo(trace.infoType(), accessor);\n\n\t\ttrace.info(info);\n\t}\n\n\treturn {\n\t\ttype: producerTraceEventTypeFromFbs(trace.type()),\n\t\ttimestamp: Number(trace.timestamp()),\n\t\tdirection:\n\t\t\ttrace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out',\n\t\tinfo: info?.unpack(),\n\t};\n}\n"
  },
  {
    "path": "node/src/ProducerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { MediaKind, RtpParameters } from './rtpParametersTypes';\nimport type { RtpStreamRecvStats } from './rtpStreamStatsTypes';\nimport type { AppData } from './types';\n\nexport type ProducerOptions<ProducerAppData extends AppData = AppData> = {\n\t/**\n\t * Producer id (just for Router.pipeToRouter() method).\n\t */\n\tid?: string;\n\n\t/**\n\t * Media kind ('audio' or 'video').\n\t */\n\tkind: MediaKind;\n\n\t/**\n\t * RTP parameters defining what the endpoint is sending.\n\t */\n\trtpParameters: RtpParameters;\n\n\t/**\n\t * Whether the producer must start in paused mode. Default false.\n\t */\n\tpaused?: boolean;\n\n\t/**\n\t * Just for video. Time (in ms) before asking the sender for a new key frame\n\t * after having asked a previous one. Default 0.\n\t */\n\tkeyFrameRequestDelay?: number;\n\n\t/**\n\t * Add mediasoup custom 'urn:mediasoup:params:rtp-hdrext:packet-id' header\n\t * extension to RTP packets received from the sender endpoint.\n\t */\n\tenableMediasoupPacketIdHeaderExtension?: boolean;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: ProducerAppData;\n};\n\n/**\n * Producer type.\n */\nexport type ProducerType = 'simple' | 'simulcast' | 'svc';\n\nexport type ProducerScore = {\n\t/**\n\t * Index of the RTP stream in the rtpParameters.encodings array.\n\t */\n\tencodingIdx: number;\n\n\t/**\n\t * SSRC of the RTP stream.\n\t */\n\tssrc: number;\n\n\t/**\n\t * RID of the RTP stream.\n\t */\n\trid?: string;\n\n\t/**\n\t * The score of the RTP stream.\n\t */\n\tscore: number;\n};\n\nexport type ProducerVideoOrientation = {\n\t/**\n\t * Whether the source is a video camera.\n\t */\n\tcamera: boolean;\n\n\t/**\n\t * Whether the video source is flipped.\n\t */\n\tflip: boolean;\n\n\t/**\n\t * Rotation degrees (0, 90, 180 or 270).\n\t */\n\trotation: number;\n};\n\nexport type ProducerDump = {\n\tid: string;\n\tkind: string;\n\ttype: ProducerType;\n\trtpParameters: RtpParameters;\n\trtpMapping: unknown;\n\trtpStreams: unknown;\n\ttraceEventTypes: string[];\n\tpaused: boolean;\n};\n\nexport type ProducerStat = RtpStreamRecvStats;\n\n/**\n * Valid types for 'trace' event.\n */\nexport type ProducerTraceEventType =\n\t| 'rtp'\n\t| 'keyframe'\n\t| 'nack'\n\t| 'pli'\n\t| 'fir'\n\t| 'sr';\n\n/**\n * 'trace' event data.\n */\nexport type ProducerTraceEventData = {\n\t/**\n\t * Trace type.\n\t */\n\ttype: ProducerTraceEventType;\n\n\t/**\n\t * Event timestamp.\n\t */\n\ttimestamp: number;\n\n\t/**\n\t * Event direction.\n\t */\n\tdirection: 'in' | 'out';\n\n\t/**\n\t * Per type information.\n\t */\n\tinfo: Record<string, unknown>;\n};\n\nexport type ProducerEvents = {\n\ttransportclose: [];\n\tscore: [ProducerScore[]];\n\tvideoorientationchange: [ProducerVideoOrientation];\n\ttrace: [ProducerTraceEventData];\n\t// Private events.\n\t'@close': [];\n};\n\nexport type ProducerObserver = EnhancedEventEmitter<ProducerObserverEvents>;\n\nexport type ProducerObserverEvents = {\n\tclose: [];\n\tpause: [];\n\tresume: [];\n\tscore: [ProducerScore[]];\n\tvideoorientationchange: [ProducerVideoOrientation];\n\ttrace: [ProducerTraceEventData];\n};\n\nexport interface Producer<\n\tProducerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<ProducerEvents> {\n\t/**\n\t * Producer id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the Producer is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * Media kind.\n\t */\n\tget kind(): MediaKind;\n\n\t/**\n\t * RTP parameters.\n\t */\n\tget rtpParameters(): RtpParameters;\n\n\t/**\n\t * Producer type.\n\t */\n\tget type(): ProducerType;\n\n\t/**\n\t * Consumable RTP parameters.\n\t *\n\t * @private\n\t */\n\tget consumableRtpParameters(): RtpParameters;\n\n\t/**\n\t * Whether the Producer is paused.\n\t */\n\tget paused(): boolean;\n\n\t/**\n\t * Producer score list.\n\t */\n\tget score(): ProducerScore[];\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): ProducerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: ProducerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): ProducerObserver;\n\n\t/**\n\t * Close the Producer.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Transport was closed.\n\t *\n\t * @private\n\t */\n\ttransportClosed(): void;\n\n\t/**\n\t * Dump Producer.\n\t */\n\tdump(): Promise<ProducerDump>;\n\n\t/**\n\t * Get Producer stats.\n\t */\n\tgetStats(): Promise<ProducerStat[]>;\n\n\t/**\n\t * Pause the Producer.\n\t */\n\tpause(): Promise<void>;\n\n\t/**\n\t * Resume the Producer.\n\t */\n\tresume(): Promise<void>;\n\n\t/**\n\t * Enable 'trace' event.\n\t */\n\tenableTraceEvent(types?: ProducerTraceEventType[]): Promise<void>;\n\n\t/**\n\t * Send RTP packet (just valid for Producers created on a DirectTransport).\n\t */\n\tsend(rtpPacket: Buffer): void;\n}\n"
  },
  {
    "path": "node/src/Router.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport * as ortc from './ortc';\nimport { InvalidStateError } from './errors';\nimport type { Channel } from './Channel';\nimport type {\n\tRouter,\n\tPipeToRouterOptions,\n\tPipeToRouterResult,\n\tPipeTransportPair,\n\tRouterDump,\n\tRouterEvents,\n\tRouterObserver,\n\tRouterObserverEvents,\n} from './RouterTypes';\nimport type {\n\tTransport,\n\tTransportListenIp,\n\tTransportProtocol,\n} from './TransportTypes';\nimport { portRangeToFbs, socketFlagsToFbs } from './Transport';\nimport type {\n\tWebRtcTransport,\n\tWebRtcTransportOptions,\n} from './WebRtcTransportTypes';\nimport {\n\tWebRtcTransportImpl,\n\tparseWebRtcTransportDumpResponse,\n} from './WebRtcTransport';\nimport type {\n\tPlainTransport,\n\tPlainTransportOptions,\n} from './PlainTransportTypes';\nimport {\n\tPlainTransportImpl,\n\tparsePlainTransportDumpResponse,\n} from './PlainTransport';\nimport type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes';\nimport {\n\tPipeTransportImpl,\n\tparsePipeTransportDumpResponse,\n} from './PipeTransport';\nimport type {\n\tDirectTransport,\n\tDirectTransportOptions,\n} from './DirectTransportTypes';\nimport {\n\tDirectTransportImpl,\n\tparseDirectTransportDumpResponse,\n} from './DirectTransport';\nimport type { Producer } from './ProducerTypes';\nimport type { Consumer } from './ConsumerTypes';\nimport type { DataProducer } from './DataProducerTypes';\nimport type { DataConsumer } from './DataConsumerTypes';\nimport type { RtpObserver } from './RtpObserverTypes';\nimport type {\n\tActiveSpeakerObserver,\n\tActiveSpeakerObserverOptions,\n} from './ActiveSpeakerObserverTypes';\nimport { ActiveSpeakerObserverImpl } from './ActiveSpeakerObserver';\nimport type {\n\tAudioLevelObserver,\n\tAudioLevelObserverOptions,\n} from './AudioLevelObserverTypes';\nimport { AudioLevelObserverImpl } from './AudioLevelObserver';\nimport type {\n\tRtpCapabilities,\n\tRouterRtpCodecCapability,\n} from './rtpParametersTypes';\nimport { cryptoSuiteToFbs } from './srtpParametersFbsUtils';\nimport type { AppData } from './types';\nimport * as utils from './utils';\nimport * as fbsUtils from './fbsUtils';\nimport * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer';\nimport * as FbsAudioLevelObserver from './fbs/audio-level-observer';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsWorker from './fbs/worker';\nimport * as FbsRouter from './fbs/router';\nimport * as FbsTransport from './fbs/transport';\nimport { Protocol as FbsTransportProtocol } from './fbs/transport/protocol';\nimport * as FbsWebRtcTransport from './fbs/web-rtc-transport';\nimport * as FbsPlainTransport from './fbs/plain-transport';\nimport * as FbsPipeTransport from './fbs/pipe-transport';\nimport * as FbsDirectTransport from './fbs/direct-transport';\nimport * as FbsSctpParameters from './fbs/sctp-parameters';\n\nexport type RouterInternal = {\n\trouterId: string;\n};\n\ntype RouterData = {\n\trtpCapabilities: RtpCapabilities;\n};\n\nconst logger = new Logger('Router');\n\nexport class RouterImpl<RouterAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<RouterEvents>\n\timplements Router\n{\n\t// Internal data.\n\treadonly #internal: RouterInternal;\n\n\t// Router data.\n\treadonly #data: RouterData;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Custom app data.\n\t#appData: RouterAppData;\n\n\t// Transports map.\n\treadonly #transports: Map<string, Transport> = new Map();\n\n\t// Producers map.\n\treadonly #producers: Map<string, Producer> = new Map();\n\n\t// RtpObservers map.\n\treadonly #rtpObservers: Map<string, RtpObserver> = new Map();\n\n\t// DataProducers map.\n\treadonly #dataProducers: Map<string, DataProducer> = new Map();\n\n\t// Map of PipeTransport pair Promises indexed by the id of the Router in\n\t// which pipeToRouter() was called.\n\treadonly #mapRouterPairPipeTransportPairPromise: Map<\n\t\tstring,\n\t\tPromise<PipeTransportPair>\n\t> = new Map();\n\n\t// Observer instance.\n\treadonly #observer: RouterObserver =\n\t\tnew EnhancedEventEmitter<RouterObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tdata,\n\t\tchannel,\n\t\tappData,\n\t}: {\n\t\tinternal: RouterInternal;\n\t\tdata: RouterData;\n\t\tchannel: Channel;\n\t\tappData?: RouterAppData;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#data = data;\n\t\tthis.#channel = channel;\n\t\tthis.#appData = appData ?? ({} as RouterAppData);\n\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.routerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget rtpCapabilities(): RtpCapabilities {\n\t\treturn this.#data.rtpCapabilities;\n\t}\n\n\tget appData(): RouterAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: RouterAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): RouterObserver {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t *\n\t * @private\n\t */\n\tget transportsForTesting(): Map<string, Transport> {\n\t\treturn this.#transports;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\tconst requestOffset = new FbsWorker.CloseRouterRequestT(\n\t\t\tthis.#internal.routerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.WORKER_CLOSE_ROUTER,\n\t\t\t\tFbsRequest.Body.Worker_CloseRouterRequest,\n\t\t\t\trequestOffset\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\t// Close every Transport.\n\t\tfor (const transport of this.#transports.values()) {\n\t\t\ttransport.routerClosed();\n\t\t}\n\t\tthis.#transports.clear();\n\n\t\t// Clear the Producers map.\n\t\tthis.#producers.clear();\n\n\t\t// Close every RtpObserver.\n\t\tfor (const rtpObserver of this.#rtpObservers.values()) {\n\t\t\trtpObserver.routerClosed();\n\t\t}\n\t\tthis.#rtpObservers.clear();\n\n\t\t// Clear the DataProducers map.\n\t\tthis.#dataProducers.clear();\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tworkerClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('workerClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Close every Transport.\n\t\tfor (const transport of this.#transports.values()) {\n\t\t\ttransport.routerClosed();\n\t\t}\n\t\tthis.#transports.clear();\n\n\t\t// Clear the Producers map.\n\t\tthis.#producers.clear();\n\n\t\t// Close every RtpObserver.\n\t\tfor (const rtpObserver of this.#rtpObservers.values()) {\n\t\t\trtpObserver.routerClosed();\n\t\t}\n\t\tthis.#rtpObservers.clear();\n\n\t\t// Clear the DataProducers map.\n\t\tthis.#dataProducers.clear();\n\n\t\tthis.safeEmit('workerclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<RouterDump> {\n\t\tlogger.debug('dump()');\n\n\t\t// Send the request and wait for the response.\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst dump = new FbsRouter.DumpResponse();\n\n\t\tresponse.body(dump);\n\n\t\treturn parseRouterDumpResponse(dump);\n\t}\n\n\tasync createWebRtcTransport<\n\t\tWebRtcTransportAppData extends AppData = AppData,\n\t>({\n\t\twebRtcServer,\n\t\tlistenInfos,\n\t\tlistenIps,\n\t\tport,\n\t\tenableUdp,\n\t\tenableTcp,\n\t\tpreferUdp = false,\n\t\tpreferTcp = false,\n\t\tinitialAvailableOutgoingBitrate = 600000,\n\t\tenableSctp = false,\n\t\tnumSctpStreams = { OS: 1024, MIS: 1024 },\n\t\tmaxSctpMessageSize = 262144,\n\t\tsctpSendBufferSize = 262144,\n\t\ticeConsentTimeout = 30,\n\t\tappData,\n\t}: WebRtcTransportOptions<WebRtcTransportAppData>): Promise<\n\t\tWebRtcTransport<WebRtcTransportAppData>\n\t> {\n\t\tlogger.debug('createWebRtcTransport()');\n\n\t\tif (\n\t\t\t!webRtcServer &&\n\t\t\t!Array.isArray(listenInfos) &&\n\t\t\t!Array.isArray(listenIps)\n\t\t) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'missing webRtcServer, listenInfos and listenIps (one of them is mandatory)'\n\t\t\t);\n\t\t} else if (webRtcServer && listenInfos && listenIps) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'only one of webRtcServer, listenInfos and listenIps must be given'\n\t\t\t);\n\t\t} else if (\n\t\t\tnumSctpStreams &&\n\t\t\t(typeof numSctpStreams.OS !== 'number' ||\n\t\t\t\ttypeof numSctpStreams.MIS !== 'number')\n\t\t) {\n\t\t\tthrow new TypeError('if given, numSctpStreams must contain OS and MIS');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// If webRtcServer is given, then do not force default values for enableUdp\n\t\t// and enableTcp. Otherwise set them if unset.\n\t\tif (webRtcServer) {\n\t\t\tenableUdp ??= true;\n\t\t\tenableTcp ??= true;\n\t\t} else {\n\t\t\tenableUdp ??= true;\n\t\t\tenableTcp ??= false;\n\t\t}\n\n\t\t// Convert deprecated TransportListenIps to TransportListenInfos.\n\t\tif (listenIps) {\n\t\t\t// Normalize IP strings to TransportListenIp objects.\n\t\t\tlistenIps = listenIps.map(listenIp => {\n\t\t\t\tif (typeof listenIp === 'string') {\n\t\t\t\t\treturn { ip: listenIp };\n\t\t\t\t} else {\n\t\t\t\t\treturn listenIp;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tlistenInfos = [];\n\n\t\t\tconst orderedProtocols: TransportProtocol[] = [];\n\n\t\t\tif (enableUdp && (preferUdp || !enableTcp || !preferTcp)) {\n\t\t\t\torderedProtocols.push('udp');\n\n\t\t\t\tif (enableTcp) {\n\t\t\t\t\torderedProtocols.push('tcp');\n\t\t\t\t}\n\t\t\t} else if (enableTcp && ((preferTcp && !preferUdp) || !enableUdp)) {\n\t\t\t\torderedProtocols.push('tcp');\n\n\t\t\t\tif (enableUdp) {\n\t\t\t\t\torderedProtocols.push('udp');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const listenIp of listenIps as TransportListenIp[]) {\n\t\t\t\tfor (const protocol of orderedProtocols) {\n\t\t\t\t\tlistenInfos.push({\n\t\t\t\t\t\tprotocol: protocol,\n\t\t\t\t\t\tip: listenIp.ip,\n\t\t\t\t\t\tannouncedAddress: listenIp.announcedIp,\n\t\t\t\t\t\tport: port,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst transportId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tlet webRtcTransportListenServer:\n\t\t\t| FbsWebRtcTransport.ListenServerT\n\t\t\t| undefined;\n\t\tlet webRtcTransportListenIndividual:\n\t\t\t| FbsWebRtcTransport.ListenIndividualT\n\t\t\t| undefined;\n\n\t\tif (webRtcServer) {\n\t\t\twebRtcTransportListenServer = new FbsWebRtcTransport.ListenServerT(\n\t\t\t\twebRtcServer.id\n\t\t\t);\n\t\t} else {\n\t\t\tconst fbsListenInfos: FbsTransport.ListenInfoT[] = [];\n\n\t\t\tfor (const listenInfo of listenInfos!) {\n\t\t\t\tfbsListenInfos.push(\n\t\t\t\t\tnew FbsTransport.ListenInfoT(\n\t\t\t\t\t\tlistenInfo.protocol === 'udp'\n\t\t\t\t\t\t\t? FbsTransportProtocol.UDP\n\t\t\t\t\t\t\t: FbsTransportProtocol.TCP,\n\t\t\t\t\t\tlistenInfo.ip,\n\t\t\t\t\t\tlistenInfo.announcedAddress ?? listenInfo.announcedIp,\n\t\t\t\t\t\tBoolean(listenInfo.exposeInternalIp),\n\t\t\t\t\t\tlistenInfo.port,\n\t\t\t\t\t\tportRangeToFbs(listenInfo.portRange),\n\t\t\t\t\t\tsocketFlagsToFbs(listenInfo.flags),\n\t\t\t\t\t\tlistenInfo.sendBufferSize,\n\t\t\t\t\t\tlistenInfo.recvBufferSize\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\twebRtcTransportListenIndividual =\n\t\t\t\tnew FbsWebRtcTransport.ListenIndividualT(fbsListenInfos);\n\t\t}\n\n\t\tconst baseTransportOptions = new FbsTransport.OptionsT(\n\t\t\tundefined /* direct */,\n\t\t\tundefined /* maxMessageSize */,\n\t\t\tinitialAvailableOutgoingBitrate,\n\t\t\tenableSctp,\n\t\t\tnew FbsSctpParameters.NumSctpStreamsT(\n\t\t\t\tnumSctpStreams.OS,\n\t\t\t\tnumSctpStreams.MIS\n\t\t\t),\n\t\t\tmaxSctpMessageSize,\n\t\t\tsctpSendBufferSize,\n\t\t\ttrue /* isDataChannel */\n\t\t);\n\n\t\tconst webRtcTransportOptions =\n\t\t\tnew FbsWebRtcTransport.WebRtcTransportOptionsT(\n\t\t\t\tbaseTransportOptions,\n\t\t\t\twebRtcServer\n\t\t\t\t\t? FbsWebRtcTransport.Listen.ListenServer\n\t\t\t\t\t: FbsWebRtcTransport.Listen.ListenIndividual,\n\t\t\t\twebRtcServer\n\t\t\t\t\t? webRtcTransportListenServer\n\t\t\t\t\t: webRtcTransportListenIndividual,\n\t\t\t\tenableUdp,\n\t\t\t\tenableTcp,\n\t\t\t\tpreferUdp,\n\t\t\t\tpreferTcp,\n\t\t\t\ticeConsentTimeout\n\t\t\t);\n\n\t\tconst requestOffset = new FbsRouter.CreateWebRtcTransportRequestT(\n\t\t\ttransportId,\n\t\t\twebRtcTransportOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tconst response = await this.#channel.request(\n\t\t\twebRtcServer\n\t\t\t\t? FbsRequest.Method.ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER\n\t\t\t\t: FbsRequest.Method.ROUTER_CREATE_WEBRTCTRANSPORT,\n\t\t\tFbsRequest.Body.Router_CreateWebRtcTransportRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsWebRtcTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\tconst webRtcTransportData = parseWebRtcTransportDumpResponse(data);\n\n\t\tconst transport: WebRtcTransport<WebRtcTransportAppData> =\n\t\t\tnew WebRtcTransportImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\ttransportId: transportId,\n\t\t\t\t},\n\t\t\t\tdata: webRtcTransportData,\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetRouterRtpCapabilities: (): RtpCapabilities =>\n\t\t\t\t\tthis.#data.rtpCapabilities,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t\tgetDataProducerById: (\n\t\t\t\t\tdataProducerId: string\n\t\t\t\t): DataProducer | undefined => this.#dataProducers.get(dataProducerId),\n\t\t\t});\n\n\t\tthis.#transports.set(transport.id, transport);\n\t\ttransport.on('@close', () => this.#transports.delete(transport.id));\n\t\ttransport.on('@listenserverclose', () =>\n\t\t\tthis.#transports.delete(transport.id)\n\t\t);\n\t\ttransport.on('@newproducer', (producer: Producer) =>\n\t\t\tthis.#producers.set(producer.id, producer)\n\t\t);\n\t\ttransport.on('@producerclose', (producer: Producer) =>\n\t\t\tthis.#producers.delete(producer.id)\n\t\t);\n\t\ttransport.on('@newdataproducer', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.set(dataProducer.id, dataProducer)\n\t\t);\n\t\ttransport.on('@dataproducerclose', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.delete(dataProducer.id)\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newtransport', transport);\n\n\t\tif (webRtcServer) {\n\t\t\twebRtcServer.handleWebRtcTransport(transport);\n\t\t}\n\n\t\treturn transport;\n\t}\n\n\tasync createPlainTransport<PlainTransportAppData extends AppData = AppData>({\n\t\tlistenInfo,\n\t\trtcpListenInfo,\n\t\tlistenIp,\n\t\tport,\n\t\trtcpMux = true,\n\t\tcomedia = false,\n\t\tenableSctp = false,\n\t\tnumSctpStreams = { OS: 1024, MIS: 1024 },\n\t\tmaxSctpMessageSize = 262144,\n\t\tsctpSendBufferSize = 262144,\n\t\tenableSrtp = false,\n\t\tsrtpCryptoSuite = 'AES_CM_128_HMAC_SHA1_80',\n\t\tappData,\n\t}: PlainTransportOptions<PlainTransportAppData>): Promise<\n\t\tPlainTransport<PlainTransportAppData>\n\t> {\n\t\tlogger.debug('createPlainTransport()');\n\n\t\tif (!listenInfo && !listenIp) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'missing listenInfo and listenIp (one of them is mandatory)'\n\t\t\t);\n\t\t} else if (listenInfo && listenIp) {\n\t\t\tthrow new TypeError('only one of listenInfo and listenIp must be given');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// If rtcpMux is enabled, ignore rtcpListenInfo.\n\t\tif (rtcpMux && rtcpListenInfo) {\n\t\t\tlogger.warn(\n\t\t\t\t'createPlainTransport() | ignoring rtcpMux since rtcpListenInfo is given'\n\t\t\t);\n\n\t\t\trtcpMux = false;\n\t\t}\n\n\t\t// Convert deprecated TransportListenIps to TransportListenInfos.\n\t\tif (listenIp) {\n\t\t\t// Normalize IP string to TransportListenIp object.\n\t\t\tif (typeof listenIp === 'string') {\n\t\t\t\tlistenIp = { ip: listenIp };\n\t\t\t}\n\n\t\t\tlistenInfo = {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: listenIp.ip,\n\t\t\t\tannouncedAddress: listenIp.announcedIp,\n\t\t\t\tport: port,\n\t\t\t};\n\t\t}\n\n\t\tconst transportId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tconst baseTransportOptions = new FbsTransport.OptionsT(\n\t\t\tundefined /* direct */,\n\t\t\tundefined /* maxMessageSize */,\n\t\t\tundefined /* initialAvailableOutgoingBitrate */,\n\t\t\tenableSctp,\n\t\t\tnew FbsSctpParameters.NumSctpStreamsT(\n\t\t\t\tnumSctpStreams.OS,\n\t\t\t\tnumSctpStreams.MIS\n\t\t\t),\n\t\t\tmaxSctpMessageSize,\n\t\t\tsctpSendBufferSize,\n\t\t\tfalse /* isDataChannel */\n\t\t);\n\n\t\tconst plainTransportOptions = new FbsPlainTransport.PlainTransportOptionsT(\n\t\t\tbaseTransportOptions,\n\t\t\tnew FbsTransport.ListenInfoT(\n\t\t\t\tlistenInfo!.protocol === 'udp'\n\t\t\t\t\t? FbsTransportProtocol.UDP\n\t\t\t\t\t: FbsTransportProtocol.TCP,\n\t\t\t\tlistenInfo!.ip,\n\t\t\t\tlistenInfo!.announcedAddress ?? listenInfo!.announcedIp,\n\t\t\t\tBoolean(listenInfo!.exposeInternalIp),\n\t\t\t\tlistenInfo!.port,\n\t\t\t\tportRangeToFbs(listenInfo!.portRange),\n\t\t\t\tsocketFlagsToFbs(listenInfo!.flags),\n\t\t\t\tlistenInfo!.sendBufferSize,\n\t\t\t\tlistenInfo!.recvBufferSize\n\t\t\t),\n\t\t\trtcpListenInfo\n\t\t\t\t? new FbsTransport.ListenInfoT(\n\t\t\t\t\t\trtcpListenInfo.protocol === 'udp'\n\t\t\t\t\t\t\t? FbsTransportProtocol.UDP\n\t\t\t\t\t\t\t: FbsTransportProtocol.TCP,\n\t\t\t\t\t\trtcpListenInfo.ip,\n\t\t\t\t\t\trtcpListenInfo.announcedAddress ?? rtcpListenInfo.announcedIp,\n\t\t\t\t\t\tBoolean(rtcpListenInfo.exposeInternalIp),\n\t\t\t\t\t\trtcpListenInfo.port,\n\t\t\t\t\t\tportRangeToFbs(rtcpListenInfo.portRange),\n\t\t\t\t\t\tsocketFlagsToFbs(rtcpListenInfo.flags),\n\t\t\t\t\t\trtcpListenInfo.sendBufferSize,\n\t\t\t\t\t\trtcpListenInfo.recvBufferSize\n\t\t\t\t\t)\n\t\t\t\t: undefined,\n\t\t\trtcpMux,\n\t\t\tcomedia,\n\t\t\tenableSrtp,\n\t\t\tcryptoSuiteToFbs(srtpCryptoSuite)\n\t\t);\n\n\t\tconst requestOffset = new FbsRouter.CreatePlainTransportRequestT(\n\t\t\ttransportId,\n\t\t\tplainTransportOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_CREATE_PLAINTRANSPORT,\n\t\t\tFbsRequest.Body.Router_CreatePlainTransportRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPlainTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\tconst plainTransportData = parsePlainTransportDumpResponse(data);\n\n\t\tconst transport: PlainTransport<PlainTransportAppData> =\n\t\t\tnew PlainTransportImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\ttransportId: transportId,\n\t\t\t\t},\n\t\t\t\tdata: plainTransportData,\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetRouterRtpCapabilities: (): RtpCapabilities =>\n\t\t\t\t\tthis.#data.rtpCapabilities,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t\tgetDataProducerById: (\n\t\t\t\t\tdataProducerId: string\n\t\t\t\t): DataProducer | undefined => this.#dataProducers.get(dataProducerId),\n\t\t\t});\n\n\t\tthis.#transports.set(transport.id, transport);\n\t\ttransport.on('@close', () => this.#transports.delete(transport.id));\n\t\ttransport.on('@listenserverclose', () =>\n\t\t\tthis.#transports.delete(transport.id)\n\t\t);\n\t\ttransport.on('@newproducer', (producer: Producer) =>\n\t\t\tthis.#producers.set(producer.id, producer)\n\t\t);\n\t\ttransport.on('@producerclose', (producer: Producer) =>\n\t\t\tthis.#producers.delete(producer.id)\n\t\t);\n\t\ttransport.on('@newdataproducer', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.set(dataProducer.id, dataProducer)\n\t\t);\n\t\ttransport.on('@dataproducerclose', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.delete(dataProducer.id)\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newtransport', transport);\n\n\t\treturn transport;\n\t}\n\n\tasync createPipeTransport<PipeTransportAppData extends AppData = AppData>({\n\t\tlistenInfo,\n\t\tlistenIp,\n\t\tport,\n\t\tenableSctp = false,\n\t\tnumSctpStreams = { OS: 1024, MIS: 1024 },\n\t\tmaxSctpMessageSize = 268435456,\n\t\tsctpSendBufferSize = 268435456,\n\t\tenableRtx = false,\n\t\tenableSrtp = false,\n\t\tappData,\n\t}: PipeTransportOptions<PipeTransportAppData>): Promise<\n\t\tPipeTransport<PipeTransportAppData>\n\t> {\n\t\tlogger.debug('createPipeTransport()');\n\n\t\tif (!listenInfo && !listenIp) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'missing listenInfo and listenIp (one of them is mandatory)'\n\t\t\t);\n\t\t} else if (listenInfo && listenIp) {\n\t\t\tthrow new TypeError('only one of listenInfo and listenIp must be given');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// Convert deprecated TransportListenIps to TransportListenInfos.\n\t\tif (listenIp) {\n\t\t\t// Normalize IP string to TransportListenIp object.\n\t\t\tif (typeof listenIp === 'string') {\n\t\t\t\tlistenIp = { ip: listenIp };\n\t\t\t}\n\n\t\t\tlistenInfo = {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: listenIp.ip,\n\t\t\t\tannouncedAddress: listenIp.announcedIp,\n\t\t\t\tport: port,\n\t\t\t};\n\t\t}\n\n\t\tconst transportId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tconst baseTransportOptions = new FbsTransport.OptionsT(\n\t\t\tundefined /* direct */,\n\t\t\tundefined /* maxMessageSize */,\n\t\t\tundefined /* initialAvailableOutgoingBitrate */,\n\t\t\tenableSctp,\n\t\t\tnew FbsSctpParameters.NumSctpStreamsT(\n\t\t\t\tnumSctpStreams.OS,\n\t\t\t\tnumSctpStreams.MIS\n\t\t\t),\n\t\t\tmaxSctpMessageSize,\n\t\t\tsctpSendBufferSize,\n\t\t\tfalse /* isDataChannel */\n\t\t);\n\n\t\tconst pipeTransportOptions = new FbsPipeTransport.PipeTransportOptionsT(\n\t\t\tbaseTransportOptions,\n\t\t\tnew FbsTransport.ListenInfoT(\n\t\t\t\tlistenInfo!.protocol === 'udp'\n\t\t\t\t\t? FbsTransportProtocol.UDP\n\t\t\t\t\t: FbsTransportProtocol.TCP,\n\t\t\t\tlistenInfo!.ip,\n\t\t\t\tlistenInfo!.announcedAddress ?? listenInfo!.announcedIp,\n\t\t\t\tBoolean(listenInfo!.exposeInternalIp),\n\t\t\t\tlistenInfo!.port,\n\t\t\t\tportRangeToFbs(listenInfo!.portRange),\n\t\t\t\tsocketFlagsToFbs(listenInfo!.flags),\n\t\t\t\tlistenInfo!.sendBufferSize,\n\t\t\t\tlistenInfo!.recvBufferSize\n\t\t\t),\n\t\t\tenableRtx,\n\t\t\tenableSrtp\n\t\t);\n\n\t\tconst requestOffset = new FbsRouter.CreatePipeTransportRequestT(\n\t\t\ttransportId,\n\t\t\tpipeTransportOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_CREATE_PIPETRANSPORT,\n\t\t\tFbsRequest.Body.Router_CreatePipeTransportRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsPipeTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\tconst pipeTransportData = parsePipeTransportDumpResponse(data);\n\n\t\tconst transport: PipeTransport<PipeTransportAppData> =\n\t\t\tnew PipeTransportImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\ttransportId,\n\t\t\t\t},\n\t\t\t\tdata: pipeTransportData,\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetRouterRtpCapabilities: (): RtpCapabilities =>\n\t\t\t\t\tthis.#data.rtpCapabilities,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t\tgetDataProducerById: (\n\t\t\t\t\tdataProducerId: string\n\t\t\t\t): DataProducer | undefined => this.#dataProducers.get(dataProducerId),\n\t\t\t});\n\n\t\tthis.#transports.set(transport.id, transport);\n\t\ttransport.on('@close', () => this.#transports.delete(transport.id));\n\t\ttransport.on('@listenserverclose', () =>\n\t\t\tthis.#transports.delete(transport.id)\n\t\t);\n\t\ttransport.on('@newproducer', (producer: Producer) =>\n\t\t\tthis.#producers.set(producer.id, producer)\n\t\t);\n\t\ttransport.on('@producerclose', (producer: Producer) =>\n\t\t\tthis.#producers.delete(producer.id)\n\t\t);\n\t\ttransport.on('@newdataproducer', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.set(dataProducer.id, dataProducer)\n\t\t);\n\t\ttransport.on('@dataproducerclose', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.delete(dataProducer.id)\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newtransport', transport);\n\n\t\treturn transport;\n\t}\n\n\tasync createDirectTransport<DirectTransportAppData extends AppData = AppData>(\n\t\t{\n\t\t\tmaxMessageSize = 262144,\n\t\t\tappData,\n\t\t}: DirectTransportOptions<DirectTransportAppData> = {\n\t\t\tmaxMessageSize: 262144,\n\t\t}\n\t): Promise<DirectTransport<DirectTransportAppData>> {\n\t\tlogger.debug('createDirectTransport()');\n\n\t\tif (typeof maxMessageSize !== 'number' || maxMessageSize < 0) {\n\t\t\tthrow new TypeError('if given, maxMessageSize must be a positive number');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tconst transportId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tconst baseTransportOptions = new FbsTransport.OptionsT(\n\t\t\ttrue /* direct */,\n\t\t\tmaxMessageSize,\n\t\t\tundefined /* initialAvailableOutgoingBitrate */,\n\t\t\tundefined /* enableSctp */,\n\t\t\tundefined /* numSctpStreams */,\n\t\t\tundefined /* maxSctpMessageSize */,\n\t\t\tundefined /* sctpSendBufferSize */,\n\t\t\tundefined /* isDataChannel */\n\t\t);\n\n\t\tconst directTransportOptions =\n\t\t\tnew FbsDirectTransport.DirectTransportOptionsT(baseTransportOptions);\n\n\t\tconst requestOffset = new FbsRouter.CreateDirectTransportRequestT(\n\t\t\ttransportId,\n\t\t\tdirectTransportOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_CREATE_DIRECTTRANSPORT,\n\t\t\tFbsRequest.Body.Router_CreateDirectTransportRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsDirectTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\tconst directTransportData = parseDirectTransportDumpResponse(data);\n\n\t\tconst transport: DirectTransport<DirectTransportAppData> =\n\t\t\tnew DirectTransportImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\ttransportId: transportId,\n\t\t\t\t},\n\t\t\t\tdata: directTransportData,\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetRouterRtpCapabilities: (): RtpCapabilities =>\n\t\t\t\t\tthis.#data.rtpCapabilities,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t\tgetDataProducerById: (\n\t\t\t\t\tdataProducerId: string\n\t\t\t\t): DataProducer | undefined => this.#dataProducers.get(dataProducerId),\n\t\t\t});\n\n\t\tthis.#transports.set(transport.id, transport);\n\t\ttransport.on('@close', () => this.#transports.delete(transport.id));\n\t\ttransport.on('@listenserverclose', () =>\n\t\t\tthis.#transports.delete(transport.id)\n\t\t);\n\t\ttransport.on('@newproducer', (producer: Producer) =>\n\t\t\tthis.#producers.set(producer.id, producer)\n\t\t);\n\t\ttransport.on('@producerclose', (producer: Producer) =>\n\t\t\tthis.#producers.delete(producer.id)\n\t\t);\n\t\ttransport.on('@newdataproducer', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.set(dataProducer.id, dataProducer)\n\t\t);\n\t\ttransport.on('@dataproducerclose', (dataProducer: DataProducer) =>\n\t\t\tthis.#dataProducers.delete(dataProducer.id)\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newtransport', transport);\n\n\t\treturn transport;\n\t}\n\n\tasync pipeToRouter({\n\t\tproducerId,\n\t\tdataProducerId,\n\t\trouter,\n\t\tkeepId = true,\n\t\tlistenInfo,\n\t\tlistenIp,\n\t\tenableSctp = true,\n\t\tnumSctpStreams = { OS: 1024, MIS: 1024 },\n\t\tenableRtx = false,\n\t\tenableSrtp = false,\n\t}: PipeToRouterOptions): Promise<PipeToRouterResult> {\n\t\tlogger.debug('pipeToRouter()');\n\n\t\tif (!listenInfo && !listenIp) {\n\t\t\tlistenInfo = {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t};\n\t\t}\n\n\t\tif (listenInfo && listenIp) {\n\t\t\tthrow new TypeError('only one of listenInfo and listenIp must be given');\n\t\t} else if (!producerId && !dataProducerId) {\n\t\t\tthrow new TypeError('missing producerId or dataProducerId');\n\t\t} else if (producerId && dataProducerId) {\n\t\t\tthrow new TypeError('just producerId or dataProducerId can be given');\n\t\t} else if (!router) {\n\t\t\tthrow new TypeError('Router not found');\n\t\t} else if (router === this) {\n\t\t\tthrow new TypeError('cannot use this Router as destination');\n\t\t}\n\n\t\t// Convert deprecated TransportListenIps to TransportListenInfos.\n\t\tif (listenIp) {\n\t\t\t// Normalize IP string to TransportListenIp object.\n\t\t\tif (typeof listenIp === 'string') {\n\t\t\t\tlistenIp = { ip: listenIp };\n\t\t\t}\n\n\t\t\tlistenInfo = {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: listenIp.ip,\n\t\t\t\tannouncedAddress: listenIp.announcedIp,\n\t\t\t};\n\t\t}\n\n\t\tlet producer: Producer | undefined;\n\t\tlet dataProducer: DataProducer | undefined;\n\n\t\tif (producerId) {\n\t\t\tproducer = this.#producers.get(producerId);\n\n\t\t\tif (!producer) {\n\t\t\t\tthrow new TypeError('Producer not found');\n\t\t\t}\n\t\t} else if (dataProducerId) {\n\t\t\tdataProducer = this.#dataProducers.get(dataProducerId);\n\n\t\t\tif (!dataProducer) {\n\t\t\t\tthrow new TypeError('DataProducer not found');\n\t\t\t}\n\t\t}\n\n\t\tconst pipeTransportPairKey = router.id;\n\t\tlet pipeTransportPairPromise =\n\t\t\tthis.#mapRouterPairPipeTransportPairPromise.get(pipeTransportPairKey);\n\t\tlet pipeTransportPair: PipeTransportPair;\n\t\tlet localPipeTransport: PipeTransport;\n\t\tlet remotePipeTransport: PipeTransport;\n\n\t\tif (pipeTransportPairPromise) {\n\t\t\tpipeTransportPair = await pipeTransportPairPromise;\n\t\t\tlocalPipeTransport = pipeTransportPair[this.id]!;\n\t\t\tremotePipeTransport = pipeTransportPair[router.id]!;\n\t\t} else {\n\t\t\tpipeTransportPairPromise = new Promise((resolve, reject) => {\n\t\t\t\tPromise.all([\n\t\t\t\t\tthis.createPipeTransport({\n\t\t\t\t\t\tlistenInfo: listenInfo!,\n\t\t\t\t\t\tenableSctp,\n\t\t\t\t\t\tnumSctpStreams,\n\t\t\t\t\t\tenableRtx,\n\t\t\t\t\t\tenableSrtp,\n\t\t\t\t\t}),\n\t\t\t\t\trouter.createPipeTransport({\n\t\t\t\t\t\tlistenInfo: listenInfo!,\n\t\t\t\t\t\tenableSctp,\n\t\t\t\t\t\tnumSctpStreams,\n\t\t\t\t\t\tenableRtx,\n\t\t\t\t\t\tenableSrtp,\n\t\t\t\t\t}),\n\t\t\t\t])\n\t\t\t\t\t.then(pipeTransports => {\n\t\t\t\t\t\tlocalPipeTransport = pipeTransports[0];\n\t\t\t\t\t\tremotePipeTransport = pipeTransports[1];\n\t\t\t\t\t})\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\treturn Promise.all([\n\t\t\t\t\t\t\tlocalPipeTransport.connect({\n\t\t\t\t\t\t\t\tip: remotePipeTransport.tuple.localAddress,\n\t\t\t\t\t\t\t\tport: remotePipeTransport.tuple.localPort,\n\t\t\t\t\t\t\t\tsrtpParameters: remotePipeTransport.srtpParameters,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tremotePipeTransport.connect({\n\t\t\t\t\t\t\t\tip: localPipeTransport.tuple.localAddress,\n\t\t\t\t\t\t\t\tport: localPipeTransport.tuple.localPort,\n\t\t\t\t\t\t\t\tsrtpParameters: localPipeTransport.srtpParameters,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t]);\n\t\t\t\t\t})\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tlocalPipeTransport.observer.on('close', () => {\n\t\t\t\t\t\t\tremotePipeTransport.close();\n\t\t\t\t\t\t\tthis.#mapRouterPairPipeTransportPairPromise.delete(\n\t\t\t\t\t\t\t\tpipeTransportPairKey\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tremotePipeTransport.observer.on('close', () => {\n\t\t\t\t\t\t\tlocalPipeTransport.close();\n\t\t\t\t\t\t\tthis.#mapRouterPairPipeTransportPairPromise.delete(\n\t\t\t\t\t\t\t\tpipeTransportPairKey\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t[this.id]: localPipeTransport,\n\t\t\t\t\t\t\t[router.id]: remotePipeTransport,\n\t\t\t\t\t\t});\n\t\t\t\t\t})\n\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t\t'pipeToRouter() | error creating PipeTransport pair:',\n\t\t\t\t\t\t\terror\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tif (localPipeTransport) {\n\t\t\t\t\t\t\tlocalPipeTransport.close();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (remotePipeTransport) {\n\t\t\t\t\t\t\tremotePipeTransport.close();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\t\t});\n\t\t\t});\n\n\t\t\tthis.#mapRouterPairPipeTransportPairPromise.set(\n\t\t\t\tpipeTransportPairKey,\n\t\t\t\tpipeTransportPairPromise\n\t\t\t);\n\n\t\t\trouter.addPipeTransportPair(this.id, pipeTransportPairPromise);\n\n\t\t\tawait pipeTransportPairPromise;\n\t\t}\n\n\t\tif (producer) {\n\t\t\tlet pipeConsumer: Consumer | undefined;\n\t\t\tlet pipeProducer: Producer | undefined;\n\n\t\t\ttry {\n\t\t\t\tpipeConsumer = await localPipeTransport!.consume({\n\t\t\t\t\tproducerId: producerId!,\n\t\t\t\t});\n\n\t\t\t\tpipeProducer = await remotePipeTransport!.produce({\n\t\t\t\t\t// If requested, generate a new id for the pipeProducer.\n\t\t\t\t\tid: keepId ? producer.id : utils.generateUUIDv4(),\n\t\t\t\t\tkind: pipeConsumer.kind,\n\t\t\t\t\trtpParameters: pipeConsumer.rtpParameters,\n\t\t\t\t\tpaused: pipeConsumer.producerPaused,\n\t\t\t\t\tappData: producer.appData,\n\t\t\t\t});\n\n\t\t\t\t// Ensure that the producer has not been closed in the meanwhile.\n\t\t\t\tif (producer.closed) {\n\t\t\t\t\tthrow new InvalidStateError('original Producer closed');\n\t\t\t\t}\n\n\t\t\t\t// Ensure that producer.paused has not changed in the meanwhile and, if\n\t\t\t\t// so, sync the pipeProducer.\n\t\t\t\tif (pipeProducer.paused !== producer.paused) {\n\t\t\t\t\tif (producer.paused) {\n\t\t\t\t\t\tawait pipeProducer.pause();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait pipeProducer.resume();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Pipe events from the pipe Consumer to the pipe Producer.\n\t\t\t\tpipeConsumer.observer.on('close', () => pipeProducer!.close());\n\t\t\t\tpipeConsumer.observer.on('pause', () => void pipeProducer!.pause());\n\t\t\t\tpipeConsumer.observer.on('resume', () => void pipeProducer!.resume());\n\n\t\t\t\t// Pipe events from the pipe Producer to the pipe Consumer.\n\t\t\t\tpipeProducer.observer.on('close', () => pipeConsumer!.close());\n\n\t\t\t\treturn { pipeConsumer, pipeProducer };\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t'pipeToRouter() | error creating pipe Consumer/Producer pair:',\n\t\t\t\t\terror as Error\n\t\t\t\t);\n\n\t\t\t\tif (pipeConsumer) {\n\t\t\t\t\tpipeConsumer.close();\n\t\t\t\t}\n\n\t\t\t\tif (pipeProducer) {\n\t\t\t\t\tpipeProducer.close();\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t} else if (dataProducer) {\n\t\t\tlet pipeDataConsumer: DataConsumer | undefined;\n\t\t\tlet pipeDataProducer: DataProducer | undefined;\n\n\t\t\ttry {\n\t\t\t\tpipeDataConsumer = await localPipeTransport!.consumeData({\n\t\t\t\t\tdataProducerId: dataProducerId!,\n\t\t\t\t});\n\n\t\t\t\tpipeDataProducer = await remotePipeTransport!.produceData({\n\t\t\t\t\t// If requested, generate a new id for the pipeDataProducer.\n\t\t\t\t\tid: keepId ? dataProducer.id : utils.generateUUIDv4(),\n\t\t\t\t\tsctpStreamParameters: pipeDataConsumer.sctpStreamParameters,\n\t\t\t\t\tlabel: pipeDataConsumer.label,\n\t\t\t\t\tprotocol: pipeDataConsumer.protocol,\n\t\t\t\t\tappData: dataProducer.appData,\n\t\t\t\t});\n\n\t\t\t\t// Ensure that the dataProducer has not been closed in the meanwhile.\n\t\t\t\tif (dataProducer.closed) {\n\t\t\t\t\tthrow new InvalidStateError('original DataProducer closed');\n\t\t\t\t}\n\n\t\t\t\t// Pipe events from the pipe DataConsumer to the pipe DataProducer.\n\t\t\t\tpipeDataConsumer.observer.on('close', () => pipeDataProducer!.close());\n\n\t\t\t\t// Pipe events from the pipe DataProducer to the pipe DataConsumer.\n\t\t\t\tpipeDataProducer.observer.on('close', () => pipeDataConsumer!.close());\n\n\t\t\t\treturn { pipeDataConsumer, pipeDataProducer };\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t'pipeToRouter() | error creating pipe DataConsumer/DataProducer pair:',\n\t\t\t\t\terror as Error\n\t\t\t\t);\n\n\t\t\t\tpipeDataConsumer?.close();\n\t\t\t\tpipeDataProducer?.close();\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t} else {\n\t\t\t// NOTE: This cannot happen since it's guaranteed that producer or\n\t\t\t// dataProducer exists, but TypeScript is not that smart.\n\t\t\tthrow new Error('internal error');\n\t\t}\n\t}\n\n\taddPipeTransportPair(\n\t\tpipeTransportPairKey: string,\n\t\tpipeTransportPairPromise: Promise<PipeTransportPair>\n\t): void {\n\t\tif (this.#mapRouterPairPipeTransportPairPromise.has(pipeTransportPairKey)) {\n\t\t\tthrow new Error(\n\t\t\t\t'given pipeTransportPairKey already exists in this Router'\n\t\t\t);\n\t\t}\n\n\t\tthis.#mapRouterPairPipeTransportPairPromise.set(\n\t\t\tpipeTransportPairKey,\n\t\t\tpipeTransportPairPromise\n\t\t);\n\n\t\tpipeTransportPairPromise\n\t\t\t.then(pipeTransportPair => {\n\t\t\t\tconst localPipeTransport = pipeTransportPair[this.id]!;\n\n\t\t\t\t// NOTE: No need to do any other cleanup here since that is done by the\n\t\t\t\t// Router calling this method on us.\n\t\t\t\tlocalPipeTransport.observer.on('close', () => {\n\t\t\t\t\tthis.#mapRouterPairPipeTransportPairPromise.delete(\n\t\t\t\t\t\tpipeTransportPairKey\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t})\n\t\t\t.catch(() => {\n\t\t\t\tthis.#mapRouterPairPipeTransportPairPromise.delete(\n\t\t\t\t\tpipeTransportPairKey\n\t\t\t\t);\n\t\t\t});\n\t}\n\n\tasync createActiveSpeakerObserver<\n\t\tActiveSpeakerObserverAppData extends AppData = AppData,\n\t>({\n\t\tinterval = 300,\n\t\tappData,\n\t}: ActiveSpeakerObserverOptions<ActiveSpeakerObserverAppData> = {}): Promise<\n\t\tActiveSpeakerObserver<ActiveSpeakerObserverAppData>\n\t> {\n\t\tlogger.debug('createActiveSpeakerObserver()');\n\n\t\tif (typeof interval !== 'number') {\n\t\t\tthrow new TypeError('if given, interval must be an number');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tconst rtpObserverId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tconst activeRtpObserverOptions =\n\t\t\tnew FbsActiveSpeakerObserver.ActiveSpeakerObserverOptionsT(interval);\n\n\t\tconst requestOffset = new FbsRouter.CreateActiveSpeakerObserverRequestT(\n\t\t\trtpObserverId,\n\t\t\tactiveRtpObserverOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_CREATE_ACTIVESPEAKEROBSERVER,\n\t\t\tFbsRequest.Body.Router_CreateActiveSpeakerObserverRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\tconst activeSpeakerObserver: ActiveSpeakerObserver<ActiveSpeakerObserverAppData> =\n\t\t\tnew ActiveSpeakerObserverImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\trtpObserverId: rtpObserverId,\n\t\t\t\t},\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t});\n\n\t\tthis.#rtpObservers.set(activeSpeakerObserver.id, activeSpeakerObserver);\n\t\tactiveSpeakerObserver.on('@close', () => {\n\t\t\tthis.#rtpObservers.delete(activeSpeakerObserver.id);\n\t\t});\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newrtpobserver', activeSpeakerObserver);\n\n\t\treturn activeSpeakerObserver;\n\t}\n\n\tasync createAudioLevelObserver<\n\t\tAudioLevelObserverAppData extends AppData = AppData,\n\t>({\n\t\tmaxEntries = 1,\n\t\tthreshold = -80,\n\t\tinterval = 1000,\n\t\tappData,\n\t}: AudioLevelObserverOptions<AudioLevelObserverAppData> = {}): Promise<\n\t\tAudioLevelObserver<AudioLevelObserverAppData>\n\t> {\n\t\tlogger.debug('createAudioLevelObserver()');\n\n\t\tif (typeof maxEntries !== 'number' || maxEntries <= 0) {\n\t\t\tthrow new TypeError('if given, maxEntries must be a positive number');\n\t\t} else if (\n\t\t\ttypeof threshold !== 'number' ||\n\t\t\tthreshold < -127 ||\n\t\t\tthreshold > 0\n\t\t) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'if given, threshole must be a negative number greater than -127'\n\t\t\t);\n\t\t} else if (typeof interval !== 'number') {\n\t\t\tthrow new TypeError('if given, interval must be an number');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tconst rtpObserverId = utils.generateUUIDv4();\n\n\t\t/* Build Request. */\n\t\tconst audioLevelObserverOptions =\n\t\t\tnew FbsAudioLevelObserver.AudioLevelObserverOptionsT(\n\t\t\t\tmaxEntries,\n\t\t\t\tthreshold,\n\t\t\t\tinterval\n\t\t\t);\n\n\t\tconst requestOffset = new FbsRouter.CreateAudioLevelObserverRequestT(\n\t\t\trtpObserverId,\n\t\t\taudioLevelObserverOptions\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.ROUTER_CREATE_AUDIOLEVELOBSERVER,\n\t\t\tFbsRequest.Body.Router_CreateAudioLevelObserverRequest,\n\t\t\trequestOffset,\n\t\t\tthis.#internal.routerId\n\t\t);\n\n\t\tconst audioLevelObserver: AudioLevelObserver<AudioLevelObserverAppData> =\n\t\t\tnew AudioLevelObserverImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.#internal,\n\t\t\t\t\trtpObserverId: rtpObserverId,\n\t\t\t\t},\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t\tgetProducerById: (producerId: string): Producer | undefined =>\n\t\t\t\t\tthis.#producers.get(producerId),\n\t\t\t});\n\n\t\tthis.#rtpObservers.set(audioLevelObserver.id, audioLevelObserver);\n\t\taudioLevelObserver.on('@close', () => {\n\t\t\tthis.#rtpObservers.delete(audioLevelObserver.id);\n\t\t});\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newrtpobserver', audioLevelObserver);\n\n\t\treturn audioLevelObserver;\n\t}\n\n\tcanConsume({\n\t\tproducerId,\n\t\trtpCapabilities,\n\t}: {\n\t\tproducerId: string;\n\t\trtpCapabilities: RtpCapabilities;\n\t}): boolean {\n\t\tconst producer = this.#producers.get(producerId);\n\n\t\tif (!producer) {\n\t\t\tlogger.error(`canConsume() | Producer with id \"${producerId}\" not found`);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Clone given RTP capabilities to not modify input data.\n\t\tconst clonedRtpCapabilities = utils.clone<RtpCapabilities>(rtpCapabilities);\n\n\t\ttry {\n\t\t\treturn ortc.canConsume(\n\t\t\t\tproducer.consumableRtpParameters,\n\t\t\t\tclonedRtpCapabilities\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tlogger.error(`canConsume() | unexpected error: ${error}`);\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tupdateMediaCodecs(mediaCodecs: RouterRtpCodecCapability[]): void {\n\t\tlogger.debug('updateMediaCodecs()');\n\n\t\t// Clone given media codecs to not modify input data.\n\t\tconst clonedMediaCodecs = utils.clone<\n\t\t\tRouterRtpCodecCapability[] | undefined\n\t\t>(mediaCodecs);\n\n\t\t// This may throw.\n\t\tconst rtpCapabilities =\n\t\t\tortc.generateRouterRtpCapabilities(clonedMediaCodecs);\n\n\t\tthis.#data.rtpCapabilities = rtpCapabilities;\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction parseRouterDumpResponse(binary: FbsRouter.DumpResponse): RouterDump {\n\treturn {\n\t\tid: binary.id()!,\n\t\ttransportIds: fbsUtils.parseVector(binary, 'transportIds'),\n\t\trtpObserverIds: fbsUtils.parseVector(binary, 'rtpObserverIds'),\n\t\tmapProducerIdConsumerIds: fbsUtils.parseStringStringArrayVector(\n\t\t\tbinary,\n\t\t\t'mapProducerIdConsumerIds'\n\t\t),\n\t\tmapConsumerIdProducerId: fbsUtils.parseStringStringVector(\n\t\t\tbinary,\n\t\t\t'mapConsumerIdProducerId'\n\t\t),\n\t\tmapProducerIdObserverIds: fbsUtils.parseStringStringArrayVector(\n\t\t\tbinary,\n\t\t\t'mapProducerIdObserverIds'\n\t\t),\n\t\tmapDataProducerIdDataConsumerIds: fbsUtils.parseStringStringArrayVector(\n\t\t\tbinary,\n\t\t\t'mapDataProducerIdDataConsumerIds'\n\t\t),\n\t\tmapDataConsumerIdDataProducerId: fbsUtils.parseStringStringVector(\n\t\t\tbinary,\n\t\t\t'mapDataConsumerIdDataProducerId'\n\t\t),\n\t};\n}\n"
  },
  {
    "path": "node/src/RouterTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tTransport,\n\tTransportListenInfo,\n\tTransportListenIp,\n} from './TransportTypes';\nimport type {\n\tWebRtcTransport,\n\tWebRtcTransportOptions,\n} from './WebRtcTransportTypes';\nimport type {\n\tPlainTransport,\n\tPlainTransportOptions,\n} from './PlainTransportTypes';\nimport type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes';\nimport type {\n\tDirectTransport,\n\tDirectTransportOptions,\n} from './DirectTransportTypes';\nimport type { Producer } from './ProducerTypes';\nimport type { Consumer } from './ConsumerTypes';\nimport type { DataProducer } from './DataProducerTypes';\nimport type { DataConsumer } from './DataConsumerTypes';\nimport type { RtpObserver } from './RtpObserverTypes';\nimport type {\n\tActiveSpeakerObserver,\n\tActiveSpeakerObserverOptions,\n} from './ActiveSpeakerObserverTypes';\nimport type {\n\tAudioLevelObserver,\n\tAudioLevelObserverOptions,\n} from './AudioLevelObserverTypes';\nimport type {\n\tRtpCapabilities,\n\tRouterRtpCodecCapability,\n} from './rtpParametersTypes';\nimport type { NumSctpStreams } from './sctpParametersTypes';\nimport type { Either, AppData } from './types';\n\nexport type RouterOptions<RouterAppData extends AppData = AppData> = {\n\t/**\n\t * Router media codecs.\n\t */\n\tmediaCodecs?: RouterRtpCodecCapability[];\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: RouterAppData;\n};\n\nexport type PipeToRouterOptions = {\n\t/**\n\t * The id of the Producer to consume.\n\t */\n\tproducerId?: string;\n\n\t/**\n\t * The id of the DataProducer to consume.\n\t */\n\tdataProducerId?: string;\n\n\t/**\n\t * Target Router instance.\n\t */\n\trouter: Router;\n\n\t/**\n\t * Whether the `id` of the returned Producer or DataProducer should be the\n\t * same than the `id` of the original Producer or DataProducer. Default true.\n\t *\n\t * @remarks\n\t * - If set to true, then the origin router and target router cannot be in the\n\t *   same worker (if so, `pipeToRouter()` with throw).\n\t */\n\tkeepId?: boolean;\n\n\t/**\n\t * Create a SCTP association. Default true.\n\t */\n\tenableSctp?: boolean;\n\n\t/**\n\t * SCTP streams number.\n\t */\n\tnumSctpStreams?: NumSctpStreams;\n\n\t/**\n\t * Enable RTX and NACK for RTP retransmission.\n\t */\n\tenableRtx?: boolean;\n\n\t/**\n\t * Enable SRTP.\n\t */\n\tenableSrtp?: boolean;\n} & PipeToRouterListen;\n\ntype PipeToRouterListen = Either<PipeToRouterListenInfo, PipeToRouterListenIp>;\n\ntype PipeToRouterListenInfo = {\n\tlistenInfo: TransportListenInfo;\n};\n\ntype PipeToRouterListenIp = {\n\t/**\n\t * IP used in the PipeTransport pair. Default '127.0.0.1'.\n\t */\n\tlistenIp?: TransportListenIp | string;\n};\n\nexport type PipeToRouterResult = {\n\t/**\n\t * The Consumer created in the current Router.\n\t */\n\tpipeConsumer?: Consumer;\n\n\t/**\n\t * The Producer created in the target Router.\n\t */\n\tpipeProducer?: Producer;\n\n\t/**\n\t * The DataConsumer created in the current Router.\n\t */\n\tpipeDataConsumer?: DataConsumer;\n\n\t/**\n\t * The DataProducer created in the target Router.\n\t */\n\tpipeDataProducer?: DataProducer;\n};\n\nexport type PipeTransportPair = {\n\t[key: string]: PipeTransport;\n};\n\nexport type RouterDump = {\n\t/**\n\t * The Router id.\n\t */\n\tid: string;\n\t/**\n\t * Id of Transports.\n\t */\n\ttransportIds: string[];\n\t/**\n\t * Id of RtpObservers.\n\t */\n\trtpObserverIds: string[];\n\t/**\n\t * Array of Producer id and its respective Consumer ids.\n\t */\n\tmapProducerIdConsumerIds: { key: string; values: string[] }[];\n\t/**\n\t * Array of Consumer id and its Producer id.\n\t */\n\tmapConsumerIdProducerId: { key: string; value: string }[];\n\t/**\n\t * Array of Producer id and its respective Observer ids.\n\t */\n\tmapProducerIdObserverIds: { key: string; values: string[] }[];\n\t/**\n\t * Array of Producer id and its respective DataConsumer ids.\n\t */\n\tmapDataProducerIdDataConsumerIds: { key: string; values: string[] }[];\n\t/**\n\t * Array of DataConsumer id and its DataProducer id.\n\t */\n\tmapDataConsumerIdDataProducerId: { key: string; value: string }[];\n};\n\nexport type RouterEvents = {\n\tworkerclose: [];\n\t// Private events.\n\t'@close': [];\n};\n\nexport type RouterObserver = EnhancedEventEmitter<RouterObserverEvents>;\n\nexport type RouterObserverEvents = {\n\tclose: [];\n\tnewtransport: [Transport];\n\tnewrtpobserver: [RtpObserver];\n};\n\nexport interface Router<\n\tRouterAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<RouterEvents> {\n\t/**\n\t * Router id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the Router is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * RTP capabilities of the Router.\n\t */\n\tget rtpCapabilities(): RtpCapabilities;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): RouterAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: RouterAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): RouterObserver;\n\n\t/**\n\t * Close the Router.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Worker was closed.\n\t *\n\t * @private\n\t */\n\tworkerClosed(): void;\n\n\t/**\n\t * Dump Router.\n\t */\n\tdump(): Promise<RouterDump>;\n\n\t/**\n\t * Create a WebRtcTransport.\n\t */\n\tcreateWebRtcTransport<WebRtcTransportAppData extends AppData = AppData>(\n\t\toptions: WebRtcTransportOptions<WebRtcTransportAppData>\n\t): Promise<WebRtcTransport<WebRtcTransportAppData>>;\n\n\t/**\n\t * Create a PlainTransport.\n\t */\n\tcreatePlainTransport<PlainTransportAppData extends AppData = AppData>(\n\t\toptions: PlainTransportOptions<PlainTransportAppData>\n\t): Promise<PlainTransport<PlainTransportAppData>>;\n\n\t/**\n\t * Create a PipeTransport.\n\t */\n\tcreatePipeTransport<PipeTransportAppData extends AppData = AppData>(\n\t\toptions: PipeTransportOptions<PipeTransportAppData>\n\t): Promise<PipeTransport<PipeTransportAppData>>;\n\n\t/**\n\t * Create a DirectTransport.\n\t */\n\tcreateDirectTransport<DirectTransportAppData extends AppData = AppData>(\n\t\toptions?: DirectTransportOptions<DirectTransportAppData>\n\t): Promise<DirectTransport<DirectTransportAppData>>;\n\n\t/**\n\t * Pipes the given Producer or DataProducer into another Router in same host.\n\t */\n\tpipeToRouter(options: PipeToRouterOptions): Promise<PipeToRouterResult>;\n\n\t/**\n\t * @private\n\t */\n\taddPipeTransportPair(\n\t\tpipeTransportPairKey: string,\n\t\tpipeTransportPairPromise: Promise<PipeTransportPair>\n\t): void;\n\n\t/**\n\t * Create an ActiveSpeakerObserver\n\t */\n\tcreateActiveSpeakerObserver<\n\t\tActiveSpeakerObserverAppData extends AppData = AppData,\n\t>(\n\t\toptions?: ActiveSpeakerObserverOptions<ActiveSpeakerObserverAppData>\n\t): Promise<ActiveSpeakerObserver<ActiveSpeakerObserverAppData>>;\n\n\t/**\n\t * Create an AudioLevelObserver.\n\t */\n\tcreateAudioLevelObserver<AudioLevelObserverAppData extends AppData = AppData>(\n\t\toptions?: AudioLevelObserverOptions<AudioLevelObserverAppData>\n\t): Promise<AudioLevelObserver<AudioLevelObserverAppData>>;\n\n\t/**\n\t * Check whether the given RTP capabilities can consume the given Producer.\n\t */\n\tcanConsume({\n\t\tproducerId,\n\t\trtpCapabilities,\n\t}: {\n\t\tproducerId: string;\n\t\trtpCapabilities: RtpCapabilities;\n\t}): boolean;\n\n\t/**\n\t * Update the Router media codecs. Once called, the return value of the\n\t * router.rtpCapabilities getter changes.\n\t */\n\tupdateMediaCodecs(mediaCodecs: RouterRtpCodecCapability[]): void;\n}\n"
  },
  {
    "path": "node/src/RtpObserver.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tRtpObserverEvents,\n\tRtpObserverObserver,\n} from './RtpObserverTypes';\nimport type { Channel } from './Channel';\nimport type { RouterInternal } from './Router';\nimport type { Producer } from './ProducerTypes';\nimport type { AppData } from './types';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsRouter from './fbs/router';\nimport * as FbsRtpObserver from './fbs/rtp-observer';\n\nexport type RtpObserverConstructorOptions<RtpObserverAppData> = {\n\tinternal: RtpObserverObserverInternal;\n\tchannel: Channel;\n\tappData?: RtpObserverAppData;\n\tgetProducerById: (producerId: string) => Producer | undefined;\n};\n\ntype RtpObserverObserverInternal = RouterInternal & {\n\trtpObserverId: string;\n};\n\nconst logger = new Logger('RtpObserver');\n\nexport abstract class RtpObserverImpl<\n\tRtpObserverAppData extends AppData = AppData,\n\tEvents extends RtpObserverEvents = RtpObserverEvents,\n\tObserver extends RtpObserverObserver = RtpObserverObserver,\n> extends EnhancedEventEmitter<Events> {\n\t// Internal data.\n\tprotected readonly internal: RtpObserverObserverInternal;\n\n\t// Channel instance.\n\tprotected readonly channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Paused flag.\n\t#paused = false;\n\n\t// Custom app data.\n\t#appData: RtpObserverAppData;\n\n\t// Method to retrieve a Producer.\n\tprotected readonly getProducerById: (\n\t\tproducerId: string\n\t) => Producer | undefined;\n\n\t// Observer instance.\n\treadonly #observer: Observer;\n\n\tprotected constructor(\n\t\t{\n\t\t\tinternal,\n\t\t\tchannel,\n\t\t\tappData,\n\t\t\tgetProducerById,\n\t\t}: RtpObserverConstructorOptions<RtpObserverAppData>,\n\t\tobserver: Observer\n\t) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.internal = internal;\n\t\tthis.channel = channel;\n\t\tthis.#appData = appData ?? ({} as RtpObserverAppData);\n\t\tthis.getProducerById = getProducerById;\n\t\tthis.#observer = observer;\n\t}\n\n\tget id(): string {\n\t\treturn this.internal.rtpObserverId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget paused(): boolean {\n\t\treturn this.#paused;\n\t}\n\n\tget appData(): RtpObserverAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: RtpObserverAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): Observer {\n\t\treturn this.#observer;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.channel.removeAllListeners(this.internal.rtpObserverId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsRouter.CloseRtpObserverRequestT(\n\t\t\tthis.internal.rtpObserverId\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tthis.channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.ROUTER_CLOSE_RTPOBSERVER,\n\t\t\t\tFbsRequest.Body.Router_CloseRtpObserverRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.internal.routerId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\trouterClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('routerClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.channel.removeAllListeners(this.internal.rtpObserverId);\n\n\t\tthis.safeEmit('routerclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync pause(): Promise<void> {\n\t\tlogger.debug('pause()');\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.RTPOBSERVER_PAUSE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.rtpObserverId\n\t\t);\n\n\t\tthis.#paused = true;\n\n\t\t// Emit observer event.\n\t\tif (!wasPaused) {\n\t\t\tthis.#observer.safeEmit('pause');\n\t\t}\n\t}\n\n\tasync resume(): Promise<void> {\n\t\tlogger.debug('resume()');\n\n\t\tconst wasPaused = this.#paused;\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.RTPOBSERVER_RESUME,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.rtpObserverId\n\t\t);\n\n\t\tthis.#paused = false;\n\n\t\t// Emit observer event.\n\t\tif (wasPaused) {\n\t\t\tthis.#observer.safeEmit('resume');\n\t\t}\n\t}\n\n\tasync addProducer({ producerId }: { producerId: string }): Promise<void> {\n\t\tlogger.debug('addProducer()');\n\n\t\tconst producer = this.getProducerById(producerId);\n\n\t\tif (!producer) {\n\t\t\tthrow Error(`Producer with id \"${producerId}\" not found`);\n\t\t}\n\n\t\tconst requestOffset = new FbsRtpObserver.AddProducerRequestT(\n\t\t\tproducerId\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.RTPOBSERVER_ADD_PRODUCER,\n\t\t\tFbsRequest.Body.RtpObserver_AddProducerRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.rtpObserverId\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('addproducer', producer);\n\t}\n\n\tasync removeProducer({ producerId }: { producerId: string }): Promise<void> {\n\t\tlogger.debug('removeProducer()');\n\n\t\tconst producer = this.getProducerById(producerId);\n\n\t\tif (!producer) {\n\t\t\tthrow Error(`Producer with id \"${producerId}\" not found`);\n\t\t}\n\n\t\tconst requestOffset = new FbsRtpObserver.RemoveProducerRequestT(\n\t\t\tproducerId\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.RTPOBSERVER_REMOVE_PRODUCER,\n\t\t\tFbsRequest.Body.RtpObserver_RemoveProducerRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.rtpObserverId\n\t\t);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('removeproducer', producer);\n\t}\n}\n"
  },
  {
    "path": "node/src/RtpObserverTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { Producer } from './ProducerTypes';\nimport type { AppData } from './types';\n\n/**\n * RtpObserver type.\n */\nexport type RtpObserverType = 'audiolevel' | 'activespeaker';\n\nexport type RtpObserverEvents = {\n\trouterclose: [];\n\t// Private events.\n\t'@close': [];\n};\n\nexport type RtpObserverObserver =\n\tEnhancedEventEmitter<RtpObserverObserverEvents>;\n\nexport type RtpObserverObserverEvents = {\n\tclose: [];\n\tpause: [];\n\tresume: [];\n\taddproducer: [Producer];\n\tremoveproducer: [Producer];\n};\n\nexport interface RtpObserver<\n\tRtpObserverAppData extends AppData = AppData,\n\tEvents extends RtpObserverEvents = RtpObserverEvents,\n\tObserver extends RtpObserverObserver = RtpObserverObserver,\n> extends EnhancedEventEmitter<Events> {\n\t/**\n\t * RtpObserver id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the RtpObserver is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * RtpObserver type.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual getter since each RtpObserver class overrides it.\n\t */\n\tget type(): RtpObserverType;\n\n\t/**\n\t * Whether the RtpObserver is paused.\n\t */\n\tget paused(): boolean;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): RtpObserverAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: RtpObserverAppData);\n\n\t/**\n\t * Observer.\n\t *\n\t * @virtual\n\t */\n\tget observer(): Observer;\n\n\t/**\n\t * Close the RtpObserver.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Router was closed.\n\t *\n\t * @private\n\t */\n\trouterClosed(): void;\n\n\t/**\n\t * Pause the RtpObserver.\n\t */\n\tpause(): Promise<void>;\n\n\t/**\n\t * Resume the RtpObserver.\n\t */\n\tresume(): Promise<void>;\n\n\t/**\n\t * Add a Producer to the RtpObserver.\n\t */\n\taddProducer({ producerId }: { producerId: string }): Promise<void>;\n\n\t/**\n\t * Remove a Producer from the RtpObserver.\n\t */\n\tremoveProducer({ producerId }: { producerId: string }): Promise<void>;\n}\n"
  },
  {
    "path": "node/src/Transport.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport * as ortc from './ortc';\nimport type {\n\tTransport,\n\tTransportType,\n\tTransportProtocol,\n\tTransportPortRange,\n\tTransportSocketFlags,\n\tTransportTuple,\n\tSctpState,\n\tRtpListenerDump,\n\tSctpListenerDump,\n\tRecvRtpHeaderExtensions,\n\tBaseTransportDump,\n\tBaseTransportStats,\n\tTransportTraceEventType,\n\tTransportTraceEventData,\n\tTransportEvents,\n\tTransportObserver,\n} from './TransportTypes';\nimport type { Channel } from './Channel';\nimport type { RouterInternal } from './Router';\nimport type { WebRtcTransportData } from './WebRtcTransport';\nimport type { PlainTransportData } from './PlainTransport';\nimport type { PipeTransportData } from './PipeTransport';\nimport type { DirectTransportData } from './DirectTransport';\nimport type { Producer, ProducerOptions } from './ProducerTypes';\nimport {\n\tProducerImpl,\n\tproducerTypeFromFbs,\n\tproducerTypeToFbs,\n} from './Producer';\nimport type {\n\tConsumer,\n\tConsumerOptions,\n\tConsumerType,\n\tConsumerLayers,\n} from './ConsumerTypes';\nimport { ConsumerImpl } from './Consumer';\nimport type {\n\tDataProducer,\n\tDataProducerOptions,\n\tDataProducerType,\n} from './DataProducerTypes';\nimport {\n\tDataProducerImpl,\n\tdataProducerTypeToFbs,\n\tparseDataProducerDumpResponse,\n} from './DataProducer';\nimport type {\n\tDataConsumer,\n\tDataConsumerOptions,\n\tDataConsumerType,\n} from './DataConsumerTypes';\nimport {\n\tDataConsumerImpl,\n\tdataConsumerTypeToFbs,\n\tparseDataConsumerDumpResponse,\n} from './DataConsumer';\nimport type {\n\tMediaKind,\n\tRtpCapabilities,\n\tRtpParameters,\n} from './rtpParametersTypes';\nimport {\n\tserializeRtpEncodingParameters,\n\tserializeRtpParameters,\n} from './rtpParametersFbsUtils';\nimport type {\n\tSctpParameters,\n\tSctpStreamParameters,\n} from './sctpParametersTypes';\nimport {\n\tparseSctpParametersDump,\n\tserializeSctpStreamParameters,\n} from './sctpParametersFbsUtils';\nimport type { AppData } from './types';\nimport * as utils from './utils';\nimport * as fbsUtils from './fbsUtils';\nimport { TraceDirection as FbsTraceDirection } from './fbs/common';\nimport * as FbsRequest from './fbs/request';\nimport { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind';\nimport * as FbsConsumer from './fbs/consumer';\nimport * as FbsDataConsumer from './fbs/data-consumer';\nimport * as FbsDataProducer from './fbs/data-producer';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsRouter from './fbs/router';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\nimport { SctpState as FbsSctpState } from './fbs/sctp-association/sctp-state';\n\nexport type TransportConstructorOptions<TransportAppData> = {\n\tinternal: TransportInternal;\n\tdata: TransportData;\n\tchannel: Channel;\n\tappData?: TransportAppData;\n\tgetRouterRtpCapabilities: () => RtpCapabilities;\n\tgetProducerById: (producerId: string) => Producer | undefined;\n\tgetDataProducerById: (dataProducerId: string) => DataProducer | undefined;\n};\n\nexport type TransportInternal = RouterInternal & {\n\ttransportId: string;\n};\n\ntype TransportData =\n\t| WebRtcTransportData\n\t| PlainTransportData\n\t| PipeTransportData\n\t| DirectTransportData;\n\nconst logger = new Logger('Transport');\n\nexport abstract class TransportImpl<\n\tTransportAppData extends AppData = AppData,\n\tEvents extends TransportEvents = TransportEvents,\n\tObserver extends TransportObserver = TransportObserver,\n>\n\textends EnhancedEventEmitter<Events>\n\timplements Transport\n{\n\t// Internal data.\n\tprotected readonly internal: TransportInternal;\n\n\t// Transport data. This is set by the subclass.\n\treadonly #data: TransportData;\n\n\t// Channel instance.\n\tprotected readonly channel: Channel;\n\n\t// Close flag.\n\t#closed = false;\n\n\t// Custom app data.\n\t#appData: TransportAppData;\n\n\t// Method to retrieve Router RTP capabilities.\n\treadonly #getRouterRtpCapabilities: () => RtpCapabilities;\n\n\t// Method to retrieve a Producer.\n\tprotected readonly getProducerById: (\n\t\tproducerId: string\n\t) => Producer | undefined;\n\n\t// Method to retrieve a DataProducer.\n\tprotected readonly getDataProducerById: (\n\t\tdataProducerId: string\n\t) => DataProducer | undefined;\n\n\t// Producers map.\n\treadonly #producers: Map<string, Producer> = new Map();\n\n\t// Consumers map.\n\tprotected readonly consumers: Map<string, Consumer> = new Map();\n\n\t// DataProducers map.\n\tprotected readonly dataProducers: Map<string, DataProducer> = new Map();\n\n\t// DataConsumers map.\n\tprotected readonly dataConsumers: Map<string, DataConsumer> = new Map();\n\n\t// RTCP CNAME for Producers.\n\t#cnameForProducers?: string;\n\n\t// Next MID for Consumers. It's converted into string when used.\n\t#nextMidForConsumers = 0;\n\n\t// Buffer with available SCTP stream ids.\n\t#sctpStreamIds?: Buffer;\n\n\t// Next SCTP stream id.\n\t#nextSctpStreamId = 0;\n\n\t// Observer instance.\n\treadonly #observer: Observer;\n\n\tprotected constructor(\n\t\t{\n\t\t\tinternal,\n\t\t\tdata,\n\t\t\tchannel,\n\t\t\tappData,\n\t\t\tgetRouterRtpCapabilities,\n\t\t\tgetProducerById,\n\t\t\tgetDataProducerById,\n\t\t}: TransportConstructorOptions<TransportAppData>,\n\t\tobserver: Observer\n\t) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.internal = internal;\n\t\tthis.#data = data;\n\t\tthis.channel = channel;\n\t\tthis.#appData = appData ?? ({} as TransportAppData);\n\t\tthis.#getRouterRtpCapabilities = getRouterRtpCapabilities;\n\t\tthis.getProducerById = getProducerById;\n\t\tthis.getDataProducerById = getDataProducerById;\n\t\tthis.#observer = observer;\n\t}\n\n\tget id(): string {\n\t\treturn this.internal.transportId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tabstract get type(): TransportType;\n\n\tget appData(): TransportAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: TransportAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): Observer {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t */\n\tget channelForTesting(): Channel {\n\t\treturn this.channel;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.channel.removeAllListeners(this.internal.transportId);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsRouter.CloseTransportRequestT(\n\t\t\tthis.internal.transportId\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tthis.channel\n\t\t\t.request(\n\t\t\t\tFbsRequest.Method.ROUTER_CLOSE_TRANSPORT,\n\t\t\t\tFbsRequest.Body.Router_CloseTransportRequest,\n\t\t\t\trequestOffset,\n\t\t\t\tthis.internal.routerId\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\t// Close every Producer.\n\t\tfor (const producer of this.#producers.values()) {\n\t\t\tproducer.transportClosed();\n\n\t\t\t// Must tell the Router.\n\t\t\tthis.emit('@producerclose', producer);\n\t\t}\n\t\tthis.#producers.clear();\n\n\t\t// Close every Consumer.\n\t\tfor (const consumer of this.consumers.values()) {\n\t\t\tconsumer.transportClosed();\n\t\t}\n\t\tthis.consumers.clear();\n\n\t\t// Close every DataProducer.\n\t\tfor (const dataProducer of this.dataProducers.values()) {\n\t\t\tdataProducer.transportClosed();\n\n\t\t\t// Must tell the Router.\n\t\t\tthis.emit('@dataproducerclose', dataProducer);\n\t\t}\n\t\tthis.dataProducers.clear();\n\n\t\t// Close every DataConsumer.\n\t\tfor (const dataConsumer of this.dataConsumers.values()) {\n\t\t\tdataConsumer.transportClosed();\n\t\t}\n\t\tthis.dataConsumers.clear();\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\trouterClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('routerClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.channel.removeAllListeners(this.internal.transportId);\n\n\t\t// Close every Producer.\n\t\tfor (const producer of this.#producers.values()) {\n\t\t\tproducer.transportClosed();\n\n\t\t\t// NOTE: No need to tell the Router since it already knows (it has\n\t\t\t// been closed in fact).\n\t\t}\n\t\tthis.#producers.clear();\n\n\t\t// Close every Consumer.\n\t\tfor (const consumer of this.consumers.values()) {\n\t\t\tconsumer.transportClosed();\n\t\t}\n\t\tthis.consumers.clear();\n\n\t\t// Close every DataProducer.\n\t\tfor (const dataProducer of this.dataProducers.values()) {\n\t\t\tdataProducer.transportClosed();\n\n\t\t\t// NOTE: No need to tell the Router since it already knows (it has\n\t\t\t// been closed in fact).\n\t\t}\n\t\tthis.dataProducers.clear();\n\n\t\t// Close every DataConsumer.\n\t\tfor (const dataConsumer of this.dataConsumers.values()) {\n\t\t\tdataConsumer.transportClosed();\n\t\t}\n\t\tthis.dataConsumers.clear();\n\n\t\tthis.safeEmit('routerclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tlistenServerClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('listenServerClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// Remove notification subscriptions.\n\t\tthis.channel.removeAllListeners(this.internal.transportId);\n\n\t\t// Close every Producer.\n\t\tfor (const producer of this.#producers.values()) {\n\t\t\tproducer.transportClosed();\n\n\t\t\t// NOTE: No need to tell the Router since it already knows (it has\n\t\t\t// been closed in fact).\n\t\t}\n\t\tthis.#producers.clear();\n\n\t\t// Close every Consumer.\n\t\tfor (const consumer of this.consumers.values()) {\n\t\t\tconsumer.transportClosed();\n\t\t}\n\t\tthis.consumers.clear();\n\n\t\t// Close every DataProducer.\n\t\tfor (const dataProducer of this.dataProducers.values()) {\n\t\t\tdataProducer.transportClosed();\n\n\t\t\t// NOTE: No need to tell the Router since it already knows (it has\n\t\t\t// been closed in fact).\n\t\t}\n\t\tthis.dataProducers.clear();\n\n\t\t// Close every DataConsumer.\n\t\tfor (const dataConsumer of this.dataConsumers.values()) {\n\t\t\tdataConsumer.transportClosed();\n\t\t}\n\t\tthis.dataConsumers.clear();\n\n\t\t// Need to emit this event to let the parent Router know since\n\t\t// transport.listenServerClosed() is called by the listen server.\n\t\t// NOTE: Currently there is just WebRtcServer for WebRtcTransports.\n\t\tthis.emit('@listenserverclose');\n\n\t\tthis.safeEmit('listenserverclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tabstract dump(): Promise<BaseTransportDump>;\n\n\tabstract getStats(): Promise<BaseTransportStats[]>;\n\n\tabstract connect(params: unknown): Promise<void>;\n\n\tasync setMaxIncomingBitrate(bitrate: number): Promise<void> {\n\t\tlogger.debug(`setMaxIncomingBitrate() [bitrate:${bitrate}]`);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset =\n\t\t\tFbsTransport.SetMaxIncomingBitrateRequest.createSetMaxIncomingBitrateRequest(\n\t\t\t\tthis.channel.bufferBuilder,\n\t\t\t\tbitrate\n\t\t\t);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_SET_MAX_INCOMING_BITRATE,\n\t\t\tFbsRequest.Body.Transport_SetMaxIncomingBitrateRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\t}\n\n\tasync setMaxOutgoingBitrate(bitrate: number): Promise<void> {\n\t\tlogger.debug(`setMaxOutgoingBitrate() [bitrate:${bitrate}]`);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.SetMaxOutgoingBitrateRequestT(\n\t\t\tbitrate\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_SET_MAX_OUTGOING_BITRATE,\n\t\t\tFbsRequest.Body.Transport_SetMaxOutgoingBitrateRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\t}\n\n\tasync setMinOutgoingBitrate(bitrate: number): Promise<void> {\n\t\tlogger.debug(`setMinOutgoingBitrate() [bitrate:${bitrate}]`);\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.SetMinOutgoingBitrateRequestT(\n\t\t\tbitrate\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_SET_MIN_OUTGOING_BITRATE,\n\t\t\tFbsRequest.Body.Transport_SetMinOutgoingBitrateRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\t}\n\n\tasync produce<ProducerAppData extends AppData = AppData>({\n\t\tid = undefined,\n\t\tkind,\n\t\trtpParameters,\n\t\tpaused = false,\n\t\tkeyFrameRequestDelay,\n\t\tenableMediasoupPacketIdHeaderExtension,\n\t\tappData,\n\t}: ProducerOptions<ProducerAppData>): Promise<Producer<ProducerAppData>> {\n\t\tlogger.debug('produce()');\n\n\t\tif (id && this.#producers.has(id)) {\n\t\t\tthrow new TypeError(`a Producer with same id \"${id}\" already exists`);\n\t\t} else if (!['audio', 'video'].includes(kind)) {\n\t\t\tthrow new TypeError(`invalid kind \"${kind}\"`);\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// Clone given RTP parameters to not modify input data.\n\t\tconst clonedRtpParameters = utils.clone<RtpParameters>(rtpParameters);\n\n\t\t// This may throw.\n\t\tortc.validateAndNormalizeRtpParameters(clonedRtpParameters);\n\n\t\t// If missing or empty encodings, add one.\n\t\tif (\n\t\t\t!clonedRtpParameters.encodings ||\n\t\t\t!Array.isArray(clonedRtpParameters.encodings) ||\n\t\t\tclonedRtpParameters.encodings.length === 0\n\t\t) {\n\t\t\tclonedRtpParameters.encodings = [{}];\n\t\t}\n\n\t\t// Don't do this in PipeTransports since there we must keep CNAME value in\n\t\t// each Producer.\n\t\tif (this.type !== 'pipe') {\n\t\t\t// If CNAME is given and we don't have yet a CNAME for Producers in this\n\t\t\t// Transport, take it.\n\t\t\tif (!this.#cnameForProducers && clonedRtpParameters.rtcp?.cname) {\n\t\t\t\tthis.#cnameForProducers = clonedRtpParameters.rtcp.cname;\n\t\t\t}\n\t\t\t// Otherwise if we don't have yet a CNAME for Producers and the RTP\n\t\t\t// parameters do not include CNAME, create a random one.\n\t\t\telse if (!this.#cnameForProducers) {\n\t\t\t\tthis.#cnameForProducers = utils.generateUUIDv4().substr(0, 8);\n\t\t\t}\n\n\t\t\t// Override Producer's CNAME.\n\t\t\tclonedRtpParameters.rtcp = clonedRtpParameters.rtcp ?? {};\n\t\t\tclonedRtpParameters.rtcp.cname = this.#cnameForProducers;\n\t\t}\n\n\t\tconst routerRtpCapabilities = this.#getRouterRtpCapabilities();\n\n\t\t// This may throw.\n\t\tconst rtpMapping = ortc.getProducerRtpParametersMapping(\n\t\t\tclonedRtpParameters,\n\t\t\trouterRtpCapabilities\n\t\t);\n\n\t\t// This may throw.\n\t\tconst consumableRtpParameters = ortc.getConsumableRtpParameters(\n\t\t\tkind,\n\t\t\tclonedRtpParameters,\n\t\t\trouterRtpCapabilities,\n\t\t\trtpMapping\n\t\t);\n\n\t\tconst producerId = id ?? utils.generateUUIDv4();\n\t\tconst requestOffset = createProduceRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tproducerId,\n\t\t\tkind,\n\t\t\trtpParameters: clonedRtpParameters,\n\t\t\trtpMapping,\n\t\t\tpaused,\n\t\t\tkeyFrameRequestDelay,\n\t\t\tenableMediasoupPacketIdHeaderExtension,\n\t\t});\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_PRODUCE,\n\t\t\tFbsRequest.Body.Transport_ProduceRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst produceResponse = new FbsTransport.ProduceResponse();\n\n\t\tresponse.body(produceResponse);\n\n\t\tconst status = produceResponse.unpack();\n\n\t\tconst data = {\n\t\t\tkind,\n\t\t\trtpParameters: clonedRtpParameters,\n\t\t\ttype: producerTypeFromFbs(status.type),\n\t\t\tconsumableRtpParameters,\n\t\t};\n\n\t\tconst producer: Producer<ProducerAppData> = new ProducerImpl({\n\t\t\tinternal: {\n\t\t\t\t...this.internal,\n\t\t\t\tproducerId,\n\t\t\t},\n\t\t\tdata,\n\t\t\tchannel: this.channel,\n\t\t\tappData,\n\t\t\tpaused,\n\t\t});\n\n\t\tthis.#producers.set(producer.id, producer);\n\t\tproducer.on('@close', () => {\n\t\t\tthis.#producers.delete(producer.id);\n\t\t\tthis.emit('@producerclose', producer);\n\t\t});\n\n\t\tthis.emit('@newproducer', producer);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newproducer', producer);\n\n\t\treturn producer;\n\t}\n\n\tasync consume<ConsumerAppData extends AppData = AppData>({\n\t\tproducerId,\n\t\trtpCapabilities,\n\t\tpaused = false,\n\t\tmid,\n\t\tpreferredLayers,\n\t\tignoreDtx = false,\n\t\tenableRtx,\n\t\tpipe = false,\n\t\tappData,\n\t}: ConsumerOptions<ConsumerAppData>): Promise<Consumer<ConsumerAppData>> {\n\t\tlogger.debug('consume()');\n\n\t\tif (!producerId || typeof producerId !== 'string') {\n\t\t\tthrow new TypeError('missing producerId');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t} else if (mid && (typeof mid !== 'string' || mid.length === 0)) {\n\t\t\tthrow new TypeError('if given, mid must be non empty string');\n\t\t}\n\n\t\t// Clone given RTP capabilities to not modify input data.\n\t\tconst clonedRtpCapabilities = utils.clone<RtpCapabilities>(rtpCapabilities);\n\n\t\t// This may throw.\n\t\tortc.validateAndNormalizeRtpCapabilities(clonedRtpCapabilities);\n\n\t\tconst producer = this.getProducerById(producerId);\n\n\t\tif (!producer) {\n\t\t\tthrow Error(`Producer with id \"${producerId}\" not found`);\n\t\t}\n\n\t\t// If enableRtx is not given, set it to true if video and false if audio.\n\t\tif (enableRtx === undefined) {\n\t\t\tenableRtx = producer.kind === 'video';\n\t\t}\n\n\t\t// This may throw.\n\t\tconst rtpParameters = ortc.getConsumerRtpParameters({\n\t\t\tconsumableRtpParameters: producer.consumableRtpParameters,\n\t\t\tremoteRtpCapabilities: clonedRtpCapabilities,\n\t\t\tpipe,\n\t\t\tenableRtx,\n\t\t});\n\n\t\t// Set MID.\n\t\tif (!pipe) {\n\t\t\tif (mid) {\n\t\t\t\trtpParameters.mid = mid;\n\t\t\t} else {\n\t\t\t\trtpParameters.mid = `${this.#nextMidForConsumers++}`;\n\n\t\t\t\t// We use up to 8 bytes for MID (string).\n\t\t\t\tif (this.#nextMidForConsumers === 100000000) {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`consume() | reaching max MID value \"${this.#nextMidForConsumers}\"`\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.#nextMidForConsumers = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst consumerId = utils.generateUUIDv4();\n\t\tconst requestOffset = createConsumeRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tproducer,\n\t\t\tconsumerId,\n\t\t\trtpParameters,\n\t\t\tpaused,\n\t\t\tpreferredLayers,\n\t\t\tignoreDtx,\n\t\t\tpipe,\n\t\t});\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_CONSUME,\n\t\t\tFbsRequest.Body.Transport_ConsumeRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst consumeResponse = new FbsTransport.ConsumeResponse();\n\n\t\tresponse.body(consumeResponse);\n\n\t\tconst status = consumeResponse.unpack();\n\n\t\tconst data = {\n\t\t\tproducerId,\n\t\t\tkind: producer.kind,\n\t\t\trtpParameters,\n\t\t\ttype: pipe ? 'pipe' : (producer.type as ConsumerType),\n\t\t};\n\n\t\tconst consumer: Consumer<ConsumerAppData> = new ConsumerImpl({\n\t\t\tinternal: {\n\t\t\t\t...this.internal,\n\t\t\t\tconsumerId,\n\t\t\t},\n\t\t\tdata,\n\t\t\tchannel: this.channel,\n\t\t\tappData,\n\t\t\tpaused: status.paused,\n\t\t\tproducerPaused: status.producerPaused,\n\t\t\tscore: status.score ?? undefined,\n\t\t\tpreferredLayers: status.preferredLayers\n\t\t\t\t? {\n\t\t\t\t\t\tspatialLayer: status.preferredLayers.spatialLayer,\n\t\t\t\t\t\ttemporalLayer: status.preferredLayers.temporalLayer ?? undefined,\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t});\n\n\t\tthis.consumers.set(consumer.id, consumer);\n\t\tconsumer.on('@close', () => this.consumers.delete(consumer.id));\n\t\tconsumer.on('@producerclose', () => this.consumers.delete(consumer.id));\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newconsumer', consumer);\n\n\t\treturn consumer;\n\t}\n\n\tasync produceData<DataProducerAppData extends AppData = AppData>({\n\t\tid = undefined,\n\t\tsctpStreamParameters,\n\t\tlabel = '',\n\t\tprotocol = '',\n\t\tpaused = false,\n\t\tappData,\n\t}: DataProducerOptions<DataProducerAppData> = {}): Promise<\n\t\tDataProducer<DataProducerAppData>\n\t> {\n\t\tlogger.debug('produceData()');\n\n\t\tif (id && this.dataProducers.has(id)) {\n\t\t\tthrow new TypeError(`a DataProducer with same id \"${id}\" already exists`);\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tlet type: DataProducerType;\n\n\t\t// Clone given SCTP stream parameters to not modify input data.\n\t\tlet clonedSctpStreamParameters = utils.clone<\n\t\t\tSctpStreamParameters | undefined\n\t\t>(sctpStreamParameters);\n\n\t\t// If this is not a DirectTransport, sctpStreamParameters are required.\n\t\tif (this.type !== 'direct') {\n\t\t\ttype = 'sctp';\n\n\t\t\t// This may throw.\n\t\t\tortc.validateAndNormalizeSctpStreamParameters(\n\t\t\t\tclonedSctpStreamParameters!\n\t\t\t);\n\t\t}\n\t\t// If this is a DirectTransport, sctpStreamParameters must not be given.\n\t\telse {\n\t\t\ttype = 'direct';\n\n\t\t\tif (sctpStreamParameters) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'produceData() | sctpStreamParameters are ignored when producing data on a DirectTransport'\n\t\t\t\t);\n\n\t\t\t\tclonedSctpStreamParameters = undefined;\n\t\t\t}\n\t\t}\n\n\t\tconst dataProducerId = id ?? utils.generateUUIDv4();\n\t\tconst requestOffset = createProduceDataRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tdataProducerId,\n\t\t\ttype,\n\t\t\tsctpStreamParameters: clonedSctpStreamParameters,\n\t\t\tlabel,\n\t\t\tprotocol,\n\t\t\tpaused,\n\t\t});\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_PRODUCE_DATA,\n\t\t\tFbsRequest.Body.Transport_ProduceDataRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst produceDataResponse = new FbsDataProducer.DumpResponse();\n\n\t\tresponse.body(produceDataResponse);\n\n\t\tconst dump = parseDataProducerDumpResponse(produceDataResponse);\n\n\t\tconst dataProducer: DataProducer<DataProducerAppData> =\n\t\t\tnew DataProducerImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.internal,\n\t\t\t\t\tdataProducerId,\n\t\t\t\t},\n\t\t\t\tdata: {\n\t\t\t\t\ttype: dump.type,\n\t\t\t\t\tsctpStreamParameters: dump.sctpStreamParameters,\n\t\t\t\t\tlabel: dump.label,\n\t\t\t\t\tprotocol: dump.protocol,\n\t\t\t\t},\n\t\t\t\tchannel: this.channel,\n\t\t\t\tpaused,\n\t\t\t\tappData,\n\t\t\t});\n\n\t\tthis.dataProducers.set(dataProducer.id, dataProducer);\n\t\tdataProducer.on('@close', () => {\n\t\t\tthis.dataProducers.delete(dataProducer.id);\n\t\t\tthis.emit('@dataproducerclose', dataProducer);\n\t\t});\n\n\t\tthis.emit('@newdataproducer', dataProducer);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newdataproducer', dataProducer);\n\n\t\treturn dataProducer;\n\t}\n\n\tasync consumeData<DataConsumerAppData extends AppData = AppData>({\n\t\tdataProducerId,\n\t\tordered,\n\t\tmaxPacketLifeTime,\n\t\tmaxRetransmits,\n\t\tpaused = false,\n\t\tsubchannels,\n\t\tappData,\n\t}: DataConsumerOptions<DataConsumerAppData>): Promise<\n\t\tDataConsumer<DataConsumerAppData>\n\t> {\n\t\tlogger.debug('consumeData()');\n\n\t\tif (!dataProducerId || typeof dataProducerId !== 'string') {\n\t\t\tthrow new TypeError('missing dataProducerId');\n\t\t} else if (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\tconst dataProducer = this.getDataProducerById(dataProducerId);\n\n\t\tif (!dataProducer) {\n\t\t\tthrow Error(`DataProducer with id \"${dataProducerId}\" not found`);\n\t\t}\n\n\t\tlet type: DataConsumerType;\n\t\tlet sctpStreamParameters: SctpStreamParameters | undefined;\n\t\tlet sctpStreamId: number;\n\n\t\t// If this is a DirectTransport, sctpStreamParameters must not be used.\n\t\tif (this.type === 'direct') {\n\t\t\ttype = 'direct';\n\n\t\t\tif (\n\t\t\t\tordered !== undefined ||\n\t\t\t\tmaxPacketLifeTime !== undefined ||\n\t\t\t\tmaxRetransmits !== undefined\n\t\t\t) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'consumeData() | ordered, maxPacketLifeTime and maxRetransmits are ignored when consuming data on a DirectTransport'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\t// If this is not a DirectTransport, use sctpStreamParameters from the\n\t\t// DataProducer (if type 'sctp') unless they are given in method parameters.\n\t\t// If the DataProducer is type 'sctp' and no sctpStreamParameters are given,\n\t\t// generate proper ones.\n\t\telse {\n\t\t\ttype = 'sctp';\n\n\t\t\t// This may throw.\n\t\t\tsctpStreamId = this.getNextSctpStreamId();\n\n\t\t\tsctpStreamParameters = dataProducer.sctpStreamParameters\n\t\t\t\t? utils.clone<SctpStreamParameters>(dataProducer.sctpStreamParameters)\n\t\t\t\t: {\n\t\t\t\t\t\tstreamId: sctpStreamId,\n\t\t\t\t\t\tordered: true,\n\t\t\t\t\t};\n\n\t\t\tthis.#sctpStreamIds![sctpStreamId] = 1;\n\t\t\tsctpStreamParameters.streamId = sctpStreamId;\n\n\t\t\tif (ordered !== undefined) {\n\t\t\t\tsctpStreamParameters.ordered = ordered;\n\n\t\t\t\tif (ordered) {\n\t\t\t\t\tsctpStreamParameters.maxPacketLifeTime = undefined;\n\t\t\t\t\tsctpStreamParameters.maxRetransmits = undefined;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!ordered) {\n\t\t\t\tif (maxPacketLifeTime !== undefined) {\n\t\t\t\t\tsctpStreamParameters.ordered = false;\n\t\t\t\t\tsctpStreamParameters.maxPacketLifeTime = maxPacketLifeTime;\n\t\t\t\t}\n\n\t\t\t\tif (maxRetransmits !== undefined) {\n\t\t\t\t\tsctpStreamParameters.ordered = false;\n\t\t\t\t\tsctpStreamParameters.maxRetransmits = maxRetransmits;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst { label, protocol } = dataProducer;\n\t\tconst dataConsumerId = utils.generateUUIDv4();\n\n\t\tconst requestOffset = createConsumeDataRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tdataConsumerId,\n\t\t\tdataProducerId,\n\t\t\ttype,\n\t\t\tsctpStreamParameters,\n\t\t\tlabel,\n\t\t\tprotocol,\n\t\t\tpaused,\n\t\t\tsubchannels,\n\t\t});\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_CONSUME_DATA,\n\t\t\tFbsRequest.Body.Transport_ConsumeDataRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst consumeDataResponse = new FbsDataConsumer.DumpResponse();\n\n\t\tresponse.body(consumeDataResponse);\n\n\t\tconst dump = parseDataConsumerDumpResponse(consumeDataResponse);\n\n\t\tconst dataConsumer: DataConsumer<DataConsumerAppData> =\n\t\t\tnew DataConsumerImpl({\n\t\t\t\tinternal: {\n\t\t\t\t\t...this.internal,\n\t\t\t\t\tdataConsumerId,\n\t\t\t\t},\n\t\t\t\tdata: {\n\t\t\t\t\tdataProducerId: dump.dataProducerId,\n\t\t\t\t\ttype: dump.type,\n\t\t\t\t\tsctpStreamParameters: dump.sctpStreamParameters,\n\t\t\t\t\tlabel: dump.label,\n\t\t\t\t\tprotocol: dump.protocol,\n\t\t\t\t\tbufferedAmountLowThreshold: dump.bufferedAmountLowThreshold,\n\t\t\t\t},\n\t\t\t\tchannel: this.channel,\n\t\t\t\tpaused: dump.paused,\n\t\t\t\tsubchannels: dump.subchannels,\n\t\t\t\tdataProducerPaused: dump.dataProducerPaused,\n\t\t\t\tappData,\n\t\t\t});\n\n\t\tthis.dataConsumers.set(dataConsumer.id, dataConsumer);\n\t\tdataConsumer.on('@close', () => {\n\t\t\tthis.dataConsumers.delete(dataConsumer.id);\n\n\t\t\tif (this.#sctpStreamIds) {\n\t\t\t\tthis.#sctpStreamIds[sctpStreamId] = 0;\n\t\t\t}\n\t\t});\n\t\tdataConsumer.on('@dataproducerclose', () => {\n\t\t\tthis.dataConsumers.delete(dataConsumer.id);\n\n\t\t\tif (this.#sctpStreamIds) {\n\t\t\t\tthis.#sctpStreamIds[sctpStreamId] = 0;\n\t\t\t}\n\t\t});\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newdataconsumer', dataConsumer);\n\n\t\treturn dataConsumer;\n\t}\n\n\tasync enableTraceEvent(types: TransportTraceEventType[] = []): Promise<void> {\n\t\tlogger.debug('enableTraceEvent()');\n\n\t\tif (!Array.isArray(types)) {\n\t\t\tthrow new TypeError('types must be an array');\n\t\t} else if (types.find(type => typeof type !== 'string')) {\n\t\t\tthrow new TypeError('every type must be a string');\n\t\t}\n\n\t\t// Convert event types.\n\t\tconst fbsEventTypes: FbsTransport.TraceEventType[] = [];\n\n\t\tfor (const eventType of types) {\n\t\t\ttry {\n\t\t\t\tfbsEventTypes.push(transportTraceEventTypeToFbs(eventType));\n\t\t\t} catch (error) {\n\t\t\t\t// Ignore invalid event types.\n\t\t\t}\n\t\t}\n\n\t\t/* Build Request. */\n\t\tconst requestOffset = new FbsTransport.EnableTraceEventRequestT(\n\t\t\tfbsEventTypes\n\t\t).pack(this.channel.bufferBuilder);\n\n\t\tawait this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_ENABLE_TRACE_EVENT,\n\t\t\tFbsRequest.Body.Transport_EnableTraceEventRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\t}\n\n\tprivate getNextSctpStreamId(): number {\n\t\tif (\n\t\t\t!this.#data.sctpParameters ||\n\t\t\ttypeof this.#data.sctpParameters.MIS !== 'number'\n\t\t) {\n\t\t\tthrow new TypeError('missing sctpParameters.MIS');\n\t\t}\n\n\t\tconst numStreams = this.#data.sctpParameters.MIS;\n\n\t\tif (!this.#sctpStreamIds) {\n\t\t\tthis.#sctpStreamIds = Buffer.alloc(numStreams, 0);\n\t\t}\n\n\t\tlet sctpStreamId;\n\n\t\tfor (let idx = 0; idx < this.#sctpStreamIds.length; ++idx) {\n\t\t\tsctpStreamId =\n\t\t\t\t(this.#nextSctpStreamId + idx) % this.#sctpStreamIds.length;\n\n\t\t\tif (!this.#sctpStreamIds[sctpStreamId]) {\n\t\t\t\tthis.#nextSctpStreamId = sctpStreamId + 1;\n\n\t\t\t\treturn sctpStreamId;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error('no sctpStreamId available');\n\t}\n}\n\nexport function portRangeToFbs(\n\tportRange: TransportPortRange = { min: 0, max: 0 }\n): FbsTransport.PortRangeT {\n\treturn new FbsTransport.PortRangeT(portRange.min, portRange.max);\n}\n\nexport function socketFlagsToFbs(\n\tflags: TransportSocketFlags = {}\n): FbsTransport.SocketFlagsT {\n\treturn new FbsTransport.SocketFlagsT(\n\t\tBoolean(flags.ipv6Only),\n\t\tBoolean(flags.udpReusePort)\n\t);\n}\n\nexport function parseSctpState(fbsSctpState: FbsSctpState): SctpState {\n\tswitch (fbsSctpState) {\n\t\tcase FbsSctpState.NEW: {\n\t\t\treturn 'new';\n\t\t}\n\n\t\tcase FbsSctpState.CONNECTING: {\n\t\t\treturn 'connecting';\n\t\t}\n\n\t\tcase FbsSctpState.CONNECTED: {\n\t\t\treturn 'connected';\n\t\t}\n\n\t\tcase FbsSctpState.FAILED: {\n\t\t\treturn 'failed';\n\t\t}\n\n\t\tcase FbsSctpState.CLOSED: {\n\t\t\treturn 'closed';\n\t\t}\n\t}\n}\n\nexport function parseProtocol(\n\tprotocol: FbsTransport.Protocol\n): TransportProtocol {\n\tswitch (protocol) {\n\t\tcase FbsTransport.Protocol.UDP: {\n\t\t\treturn 'udp';\n\t\t}\n\n\t\tcase FbsTransport.Protocol.TCP: {\n\t\t\treturn 'tcp';\n\t\t}\n\t}\n}\n\nexport function serializeProtocol(\n\tprotocol: TransportProtocol\n): FbsTransport.Protocol {\n\tswitch (protocol) {\n\t\tcase 'udp': {\n\t\t\treturn FbsTransport.Protocol.UDP;\n\t\t}\n\n\t\tcase 'tcp': {\n\t\t\treturn FbsTransport.Protocol.TCP;\n\t\t}\n\t}\n}\n\nexport function parseTuple(binary: FbsTransport.Tuple): TransportTuple {\n\treturn {\n\t\t// @deprecated Use localAddress instead.\n\t\tlocalIp: binary.localAddress()!,\n\t\tlocalAddress: binary.localAddress()!,\n\t\tlocalPort: binary.localPort(),\n\t\tremoteIp: binary.remoteIp() ?? undefined,\n\t\tremotePort: binary.remotePort(),\n\t\tprotocol: parseProtocol(binary.protocol()),\n\t};\n}\n\nexport function parseBaseTransportDump(\n\tbinary: FbsTransport.Dump\n): BaseTransportDump {\n\t// Retrieve producerIds.\n\tconst producerIds = fbsUtils.parseVector<string>(binary, 'producerIds');\n\t// Retrieve consumerIds.\n\tconst consumerIds = fbsUtils.parseVector<string>(binary, 'consumerIds');\n\t// Retrieve map SSRC consumerId.\n\tconst mapSsrcConsumerId = fbsUtils.parseUint32StringVector(\n\t\tbinary,\n\t\t'mapSsrcConsumerId'\n\t);\n\t// Retrieve map RTX SSRC consumerId.\n\tconst mapRtxSsrcConsumerId = fbsUtils.parseUint32StringVector(\n\t\tbinary,\n\t\t'mapRtxSsrcConsumerId'\n\t);\n\t// Retrieve dataProducerIds.\n\tconst dataProducerIds = fbsUtils.parseVector<string>(\n\t\tbinary,\n\t\t'dataProducerIds'\n\t);\n\t// Retrieve dataConsumerIds.\n\tconst dataConsumerIds = fbsUtils.parseVector<string>(\n\t\tbinary,\n\t\t'dataConsumerIds'\n\t);\n\t// Retrieve recvRtpHeaderExtesions.\n\tconst recvRtpHeaderExtensions = parseRecvRtpHeaderExtensions(\n\t\tbinary.recvRtpHeaderExtensions()!\n\t);\n\t// Retrieve RtpListener.\n\tconst rtpListener = parseRtpListenerDump(binary.rtpListener()!);\n\n\t// Retrieve SctpParameters.\n\tconst fbsSctpParameters = binary.sctpParameters();\n\tlet sctpParameters: SctpParameters | undefined;\n\n\tif (fbsSctpParameters) {\n\t\tsctpParameters = parseSctpParametersDump(fbsSctpParameters);\n\t}\n\n\t// Retrieve sctpState.\n\tconst sctpState =\n\t\tbinary.sctpState() === null\n\t\t\t? undefined\n\t\t\t: parseSctpState(binary.sctpState()!);\n\n\t// Retrive sctpListener.\n\tconst sctpListener = binary.sctpListener()\n\t\t? parseSctpListenerDump(binary.sctpListener()!)\n\t\t: undefined;\n\n\t// Retrieve traceEventTypes.\n\tconst traceEventTypes = fbsUtils.parseVector<TransportTraceEventType>(\n\t\tbinary,\n\t\t'traceEventTypes',\n\t\ttransportTraceEventTypeFromFbs\n\t);\n\n\treturn {\n\t\tid: binary.id()!,\n\t\tproducerIds: producerIds,\n\t\tconsumerIds: consumerIds,\n\t\tmapSsrcConsumerId: mapSsrcConsumerId,\n\t\tmapRtxSsrcConsumerId: mapRtxSsrcConsumerId,\n\t\tdataProducerIds: dataProducerIds,\n\t\tdataConsumerIds: dataConsumerIds,\n\t\trecvRtpHeaderExtensions: recvRtpHeaderExtensions,\n\t\trtpListener: rtpListener,\n\t\tmaxMessageSize: binary.maxMessageSize(),\n\t\tsctpParameters: sctpParameters,\n\t\tsctpState: sctpState,\n\t\tsctpListener: sctpListener,\n\t\ttraceEventTypes: traceEventTypes,\n\t};\n}\n\nexport function parseBaseTransportStats(\n\tbinary: FbsTransport.Stats\n): BaseTransportStats {\n\tconst sctpState =\n\t\tbinary.sctpState() === null\n\t\t\t? undefined\n\t\t\t: parseSctpState(binary.sctpState()!);\n\n\treturn {\n\t\ttransportId: binary.transportId()!,\n\t\ttimestamp: Number(binary.timestamp()),\n\t\tsctpState,\n\t\tbytesReceived: Number(binary.bytesReceived()),\n\t\trecvBitrate: Number(binary.recvBitrate()),\n\t\tbytesSent: Number(binary.bytesSent()),\n\t\tsendBitrate: Number(binary.sendBitrate()),\n\t\trtpBytesReceived: Number(binary.rtpBytesReceived()),\n\t\trtpRecvBitrate: Number(binary.rtpRecvBitrate()),\n\t\trtpBytesSent: Number(binary.rtpBytesSent()),\n\t\trtpSendBitrate: Number(binary.rtpSendBitrate()),\n\t\trtxBytesReceived: Number(binary.rtxBytesReceived()),\n\t\trtxRecvBitrate: Number(binary.rtxRecvBitrate()),\n\t\trtxBytesSent: Number(binary.rtxBytesSent()),\n\t\trtxSendBitrate: Number(binary.rtxSendBitrate()),\n\t\tprobationBytesSent: Number(binary.probationBytesSent()),\n\t\tprobationSendBitrate: Number(binary.probationSendBitrate()),\n\t\tavailableOutgoingBitrate:\n\t\t\ttypeof binary.availableOutgoingBitrate() === 'number'\n\t\t\t\t? Number(binary.availableOutgoingBitrate())\n\t\t\t\t: undefined,\n\t\tavailableIncomingBitrate:\n\t\t\ttypeof binary.availableIncomingBitrate() === 'number'\n\t\t\t\t? Number(binary.availableIncomingBitrate())\n\t\t\t\t: undefined,\n\t\tmaxIncomingBitrate:\n\t\t\ttypeof binary.maxIncomingBitrate() === 'number'\n\t\t\t\t? Number(binary.maxIncomingBitrate())\n\t\t\t\t: undefined,\n\t\tmaxOutgoingBitrate:\n\t\t\ttypeof binary.maxOutgoingBitrate() === 'number'\n\t\t\t\t? Number(binary.maxOutgoingBitrate())\n\t\t\t\t: undefined,\n\t\tminOutgoingBitrate:\n\t\t\ttypeof binary.minOutgoingBitrate() === 'number'\n\t\t\t\t? Number(binary.minOutgoingBitrate())\n\t\t\t\t: undefined,\n\t\trtpPacketLossReceived:\n\t\t\ttypeof binary.rtpPacketLossReceived() === 'number'\n\t\t\t\t? Number(binary.rtpPacketLossReceived())\n\t\t\t\t: undefined,\n\t\trtpPacketLossSent:\n\t\t\ttypeof binary.rtpPacketLossSent() === 'number'\n\t\t\t\t? Number(binary.rtpPacketLossSent())\n\t\t\t\t: undefined,\n\t};\n}\n\nexport function parseTransportTraceEventData(\n\ttrace: FbsTransport.TraceNotification\n): TransportTraceEventData {\n\tswitch (trace.type()) {\n\t\tcase FbsTransport.TraceEventType.BWE: {\n\t\t\tconst info = new FbsTransport.BweTraceInfo();\n\n\t\t\ttrace.info(info);\n\n\t\t\treturn {\n\t\t\t\ttype: 'bwe',\n\t\t\t\ttimestamp: Number(trace.timestamp()),\n\t\t\t\tdirection:\n\t\t\t\t\ttrace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out',\n\t\t\t\tinfo: parseBweTraceInfo(info),\n\t\t\t};\n\t\t}\n\n\t\tcase FbsTransport.TraceEventType.PROBATION: {\n\t\t\treturn {\n\t\t\t\ttype: 'probation',\n\t\t\t\ttimestamp: Number(trace.timestamp()),\n\t\t\t\tdirection:\n\t\t\t\t\ttrace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out',\n\t\t\t\tinfo: {},\n\t\t\t};\n\t\t}\n\t}\n}\n\nfunction parseRecvRtpHeaderExtensions(\n\tbinary: FbsTransport.RecvRtpHeaderExtensions\n): RecvRtpHeaderExtensions {\n\treturn {\n\t\tmid: binary.mid() !== null ? binary.mid()! : undefined,\n\t\trid: binary.rid() !== null ? binary.rid()! : undefined,\n\t\trrid: binary.rrid() !== null ? binary.rrid()! : undefined,\n\t\tabsSendTime:\n\t\t\tbinary.absSendTime() !== null ? binary.absSendTime()! : undefined,\n\t\ttransportWideCc01:\n\t\t\tbinary.transportWideCc01() !== null\n\t\t\t\t? binary.transportWideCc01()!\n\t\t\t\t: undefined,\n\t};\n}\n\nfunction transportTraceEventTypeToFbs(\n\teventType: TransportTraceEventType\n): FbsTransport.TraceEventType {\n\tswitch (eventType) {\n\t\tcase 'probation': {\n\t\t\treturn FbsTransport.TraceEventType.PROBATION;\n\t\t}\n\n\t\tcase 'bwe': {\n\t\t\treturn FbsTransport.TraceEventType.BWE;\n\t\t}\n\t}\n}\n\nfunction transportTraceEventTypeFromFbs(\n\teventType: FbsTransport.TraceEventType\n): TransportTraceEventType {\n\tswitch (eventType) {\n\t\tcase FbsTransport.TraceEventType.PROBATION: {\n\t\t\treturn 'probation';\n\t\t}\n\n\t\tcase FbsTransport.TraceEventType.BWE: {\n\t\t\treturn 'bwe';\n\t\t}\n\t}\n}\n\nfunction parseBweTraceInfo(binary: FbsTransport.BweTraceInfo): {\n\tdesiredBitrate: number;\n\teffectiveDesiredBitrate: number;\n\tminBitrate: number;\n\tmaxBitrate: number;\n\tstartBitrate: number;\n\tmaxPaddingBitrate: number;\n\tavailableBitrate: number;\n\tbweType: 'transport-cc' | 'remb';\n} {\n\treturn {\n\t\tdesiredBitrate: binary.desiredBitrate(),\n\t\teffectiveDesiredBitrate: binary.effectiveDesiredBitrate(),\n\t\tminBitrate: binary.minBitrate(),\n\t\tmaxBitrate: binary.maxBitrate(),\n\t\tstartBitrate: binary.startBitrate(),\n\t\tmaxPaddingBitrate: binary.maxPaddingBitrate(),\n\t\tavailableBitrate: binary.availableBitrate(),\n\t\tbweType:\n\t\t\tbinary.bweType() === FbsTransport.BweType.TRANSPORT_CC\n\t\t\t\t? 'transport-cc'\n\t\t\t\t: 'remb',\n\t};\n}\n\nfunction createConsumeRequest({\n\tbuilder,\n\tproducer,\n\tconsumerId,\n\trtpParameters,\n\tpaused,\n\tpreferredLayers,\n\tignoreDtx,\n\tpipe,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tproducer: Producer;\n\tconsumerId: string;\n\trtpParameters: RtpParameters;\n\tpaused: boolean;\n\tpreferredLayers?: ConsumerLayers;\n\tignoreDtx?: boolean;\n\tpipe: boolean;\n}): number {\n\tconst rtpParametersOffset = serializeRtpParameters(builder, rtpParameters);\n\tconst consumerIdOffset = builder.createString(consumerId);\n\tconst producerIdOffset = builder.createString(producer.id);\n\tlet consumableRtpEncodingsOffset: number | undefined;\n\tlet preferredLayersOffset: number | undefined;\n\n\tif (producer.consumableRtpParameters.encodings) {\n\t\tconsumableRtpEncodingsOffset = serializeRtpEncodingParameters(\n\t\t\tbuilder,\n\t\t\tproducer.consumableRtpParameters.encodings\n\t\t);\n\t}\n\n\tif (preferredLayers) {\n\t\tFbsConsumer.ConsumerLayers.startConsumerLayers(builder);\n\t\tFbsConsumer.ConsumerLayers.addSpatialLayer(\n\t\t\tbuilder,\n\t\t\tpreferredLayers.spatialLayer\n\t\t);\n\n\t\tif (preferredLayers.temporalLayer !== undefined) {\n\t\t\tFbsConsumer.ConsumerLayers.addTemporalLayer(\n\t\t\t\tbuilder,\n\t\t\t\tpreferredLayers.temporalLayer\n\t\t\t);\n\t\t}\n\n\t\tpreferredLayersOffset =\n\t\t\tFbsConsumer.ConsumerLayers.endConsumerLayers(builder);\n\t}\n\n\tconst ConsumeRequest = FbsTransport.ConsumeRequest;\n\n\t// Create Consume Request.\n\tConsumeRequest.startConsumeRequest(builder);\n\tConsumeRequest.addConsumerId(builder, consumerIdOffset);\n\tConsumeRequest.addProducerId(builder, producerIdOffset);\n\tConsumeRequest.addKind(\n\t\tbuilder,\n\t\tproducer.kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO\n\t);\n\tConsumeRequest.addRtpParameters(builder, rtpParametersOffset);\n\tConsumeRequest.addType(\n\t\tbuilder,\n\t\tpipe ? FbsRtpParameters.Type.PIPE : producerTypeToFbs(producer.type)\n\t);\n\n\tif (consumableRtpEncodingsOffset) {\n\t\tConsumeRequest.addConsumableRtpEncodings(\n\t\t\tbuilder,\n\t\t\tconsumableRtpEncodingsOffset\n\t\t);\n\t}\n\n\tConsumeRequest.addPaused(builder, paused);\n\n\tif (preferredLayersOffset) {\n\t\tConsumeRequest.addPreferredLayers(builder, preferredLayersOffset);\n\t}\n\n\tConsumeRequest.addIgnoreDtx(builder, Boolean(ignoreDtx));\n\n\treturn ConsumeRequest.endConsumeRequest(builder);\n}\n\nfunction createProduceRequest({\n\tbuilder,\n\tproducerId,\n\tkind,\n\trtpParameters,\n\trtpMapping,\n\tpaused,\n\tkeyFrameRequestDelay,\n\tenableMediasoupPacketIdHeaderExtension,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tproducerId: string;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n\trtpMapping: ortc.RtpCodecsEncodingsMapping;\n\tpaused: boolean;\n\tkeyFrameRequestDelay?: number;\n\tenableMediasoupPacketIdHeaderExtension?: boolean;\n}): number {\n\tconst producerIdOffset = builder.createString(producerId);\n\tconst rtpParametersOffset = serializeRtpParameters(builder, rtpParameters);\n\tconst rtpMappingOffset = ortc.serializeRtpMapping(builder, rtpMapping);\n\n\tFbsTransport.ProduceRequest.startProduceRequest(builder);\n\tFbsTransport.ProduceRequest.addProducerId(builder, producerIdOffset);\n\tFbsTransport.ProduceRequest.addKind(\n\t\tbuilder,\n\t\tkind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO\n\t);\n\tFbsTransport.ProduceRequest.addRtpParameters(builder, rtpParametersOffset);\n\tFbsTransport.ProduceRequest.addRtpMapping(builder, rtpMappingOffset);\n\tFbsTransport.ProduceRequest.addPaused(builder, paused);\n\tFbsTransport.ProduceRequest.addKeyFrameRequestDelay(\n\t\tbuilder,\n\t\tkeyFrameRequestDelay ?? 0\n\t);\n\tFbsTransport.ProduceRequest.addEnableMediasoupPacketIdHeaderExtension(\n\t\tbuilder,\n\t\tenableMediasoupPacketIdHeaderExtension ?? false\n\t);\n\n\treturn FbsTransport.ProduceRequest.endProduceRequest(builder);\n}\n\nfunction createProduceDataRequest({\n\tbuilder,\n\tdataProducerId,\n\ttype,\n\tsctpStreamParameters,\n\tlabel,\n\tprotocol,\n\tpaused,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tdataProducerId: string;\n\ttype: DataProducerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n\tpaused: boolean;\n}): number {\n\tconst dataProducerIdOffset = builder.createString(dataProducerId);\n\tconst labelOffset = builder.createString(label);\n\tconst protocolOffset = builder.createString(protocol);\n\n\tlet sctpStreamParametersOffset = 0;\n\n\tif (sctpStreamParameters) {\n\t\tsctpStreamParametersOffset = serializeSctpStreamParameters(\n\t\t\tbuilder,\n\t\t\tsctpStreamParameters\n\t\t);\n\t}\n\n\tFbsTransport.ProduceDataRequest.startProduceDataRequest(builder);\n\tFbsTransport.ProduceDataRequest.addDataProducerId(\n\t\tbuilder,\n\t\tdataProducerIdOffset\n\t);\n\tFbsTransport.ProduceDataRequest.addType(builder, dataProducerTypeToFbs(type));\n\n\tif (sctpStreamParametersOffset) {\n\t\tFbsTransport.ProduceDataRequest.addSctpStreamParameters(\n\t\t\tbuilder,\n\t\t\tsctpStreamParametersOffset\n\t\t);\n\t}\n\n\tFbsTransport.ProduceDataRequest.addLabel(builder, labelOffset);\n\tFbsTransport.ProduceDataRequest.addProtocol(builder, protocolOffset);\n\tFbsTransport.ProduceDataRequest.addPaused(builder, paused);\n\n\treturn FbsTransport.ProduceDataRequest.endProduceDataRequest(builder);\n}\n\nfunction createConsumeDataRequest({\n\tbuilder,\n\tdataConsumerId,\n\tdataProducerId,\n\ttype,\n\tsctpStreamParameters,\n\tlabel,\n\tprotocol,\n\tpaused,\n\tsubchannels = [],\n}: {\n\tbuilder: flatbuffers.Builder;\n\tdataConsumerId: string;\n\tdataProducerId: string;\n\ttype: DataConsumerType;\n\tsctpStreamParameters?: SctpStreamParameters;\n\tlabel: string;\n\tprotocol: string;\n\tpaused: boolean;\n\tsubchannels?: number[];\n}): number {\n\tconst dataConsumerIdOffset = builder.createString(dataConsumerId);\n\tconst dataProducerIdOffset = builder.createString(dataProducerId);\n\tconst labelOffset = builder.createString(label);\n\tconst protocolOffset = builder.createString(protocol);\n\n\tlet sctpStreamParametersOffset = 0;\n\n\tif (sctpStreamParameters) {\n\t\tsctpStreamParametersOffset = serializeSctpStreamParameters(\n\t\t\tbuilder,\n\t\t\tsctpStreamParameters\n\t\t);\n\t}\n\n\tconst subchannelsOffset =\n\t\tFbsTransport.ConsumeDataRequest.createSubchannelsVector(\n\t\t\tbuilder,\n\t\t\tsubchannels\n\t\t);\n\n\tFbsTransport.ConsumeDataRequest.startConsumeDataRequest(builder);\n\tFbsTransport.ConsumeDataRequest.addDataConsumerId(\n\t\tbuilder,\n\t\tdataConsumerIdOffset\n\t);\n\tFbsTransport.ConsumeDataRequest.addDataProducerId(\n\t\tbuilder,\n\t\tdataProducerIdOffset\n\t);\n\tFbsTransport.ConsumeDataRequest.addType(builder, dataConsumerTypeToFbs(type));\n\n\tif (sctpStreamParametersOffset) {\n\t\tFbsTransport.ConsumeDataRequest.addSctpStreamParameters(\n\t\t\tbuilder,\n\t\t\tsctpStreamParametersOffset\n\t\t);\n\t}\n\n\tFbsTransport.ConsumeDataRequest.addLabel(builder, labelOffset);\n\tFbsTransport.ConsumeDataRequest.addProtocol(builder, protocolOffset);\n\tFbsTransport.ConsumeDataRequest.addPaused(builder, paused);\n\tFbsTransport.ConsumeDataRequest.addSubchannels(builder, subchannelsOffset);\n\n\treturn FbsTransport.ConsumeDataRequest.endConsumeDataRequest(builder);\n}\n\nfunction parseRtpListenerDump(\n\tbinary: FbsTransport.RtpListener\n): RtpListenerDump {\n\t// Retrieve ssrcTable.\n\tconst ssrcTable = fbsUtils.parseUint32StringVector(binary, 'ssrcTable');\n\t// Retrieve midTable.\n\tconst midTable = fbsUtils.parseUint32StringVector(binary, 'midTable');\n\t// Retrieve ridTable.\n\tconst ridTable = fbsUtils.parseUint32StringVector(binary, 'ridTable');\n\n\treturn {\n\t\tssrcTable,\n\t\tmidTable,\n\t\tridTable,\n\t};\n}\n\nfunction parseSctpListenerDump(\n\tbinary: FbsTransport.SctpListener\n): SctpListenerDump {\n\t// Retrieve streamIdTable.\n\tconst streamIdTable = fbsUtils.parseUint32StringVector(\n\t\tbinary,\n\t\t'streamIdTable'\n\t);\n\n\treturn { streamIdTable };\n}\n"
  },
  {
    "path": "node/src/TransportTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { Producer, ProducerOptions } from './ProducerTypes';\nimport type { Consumer, ConsumerOptions } from './ConsumerTypes';\nimport type { DataProducer, DataProducerOptions } from './DataProducerTypes';\nimport type { DataConsumer, DataConsumerOptions } from './DataConsumerTypes';\nimport type { SctpParameters } from './sctpParametersTypes';\nimport type { AppData } from './types';\n\n/**\n * Transport type.\n */\nexport type TransportType = 'webrtc' | 'plain' | 'pipe' | 'direct';\n\nexport type TransportListenInfo = {\n\t/**\n\t * Network protocol.\n\t */\n\tprotocol: TransportProtocol;\n\n\t/**\n\t * Listening IPv4 or IPv6.\n\t */\n\tip: string;\n\n\t/**\n\t * @deprecated Use |announcedAddress| instead.\n\t *\n\t * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT\n\t * with private IP).\n\t */\n\tannouncedIp?: string;\n\n\t/**\n\t * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT\n\t * with private IP).\n\t */\n\tannouncedAddress?: string;\n\n\t/**\n\t * In transports with ICE candidates, this field determines whether to also\n\t * expose an ICE candidate with the IP of the |ip| field when |announcedAddress|\n\t * is given.\n\t */\n\texposeInternalIp?: boolean;\n\n\t/**\n\t * Listening port.\n\t */\n\tport?: number;\n\n\t/**\n\t * Listening port range. If given then |port| will be ignored.\n\t */\n\tportRange?: TransportPortRange;\n\n\t/**\n\t * Socket flags.\n\t */\n\tflags?: TransportSocketFlags;\n\n\t/**\n\t * Send buffer size (bytes).\n\t */\n\tsendBufferSize?: number;\n\n\t/**\n\t * Recv buffer size (bytes).\n\t */\n\trecvBufferSize?: number;\n};\n\n/**\n * Use TransportListenInfo instead.\n * @deprecated\n */\nexport type TransportListenIp = {\n\t/**\n\t * Listening IPv4 or IPv6.\n\t */\n\tip: string;\n\n\t/**\n\t * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT\n\t * with private IP).\n\t */\n\tannouncedIp?: string;\n};\n\n/**\n * Transport protocol.\n */\nexport type TransportProtocol = 'udp' | 'tcp';\n\n/**\n * Port range..\n */\nexport type TransportPortRange = {\n\t/**\n\t * Lowest port in the range.\n\t */\n\tmin: number;\n\t/**\n\t * Highest port in the range.\n\t */\n\tmax: number;\n};\n\n/**\n * UDP/TCP socket flags.\n */\nexport type TransportSocketFlags = {\n\t/**\n\t * Disable dual-stack support so only IPv6 is used (only if |ip| is IPv6).\n\t */\n\tipv6Only?: boolean;\n\t/**\n\t * Make different transports bind to the same IP and port (only for UDP).\n\t * Useful for multicast scenarios with plain transport. Use with caution.\n\t */\n\tudpReusePort?: boolean;\n};\n\nexport type TransportTuple = {\n\t// @deprecated Use localAddress instead.\n\tlocalIp: string;\n\tlocalAddress: string;\n\tlocalPort: number;\n\tremoteIp?: string;\n\tremotePort?: number;\n\tprotocol: TransportProtocol;\n};\n\nexport type SctpState =\n\t| 'new'\n\t| 'connecting'\n\t| 'connected'\n\t| 'failed'\n\t| 'closed';\n\nexport type RtpListenerDump = {\n\tssrcTable: { key: number; value: string }[];\n\tmidTable: { key: number; value: string }[];\n\tridTable: { key: number; value: string }[];\n};\n\nexport type SctpListenerDump = {\n\tstreamIdTable: { key: number; value: string }[];\n};\n\nexport type RecvRtpHeaderExtensions = {\n\tmid?: number;\n\trid?: number;\n\trrid?: number;\n\tabsSendTime?: number;\n\ttransportWideCc01?: number;\n};\n\nexport type BaseTransportDump = {\n\tid: string;\n\tproducerIds: string[];\n\tconsumerIds: string[];\n\tmapSsrcConsumerId: { key: number; value: string }[];\n\tmapRtxSsrcConsumerId: { key: number; value: string }[];\n\trecvRtpHeaderExtensions: RecvRtpHeaderExtensions;\n\trtpListener: RtpListenerDump;\n\tmaxMessageSize: number;\n\tdataProducerIds: string[];\n\tdataConsumerIds: string[];\n\tsctpParameters?: SctpParameters;\n\tsctpState?: SctpState;\n\tsctpListener?: SctpListenerDump;\n\ttraceEventTypes?: string[];\n};\n\nexport type BaseTransportStats = {\n\ttransportId: string;\n\ttimestamp: number;\n\tsctpState?: SctpState;\n\tbytesReceived: number;\n\trecvBitrate: number;\n\tbytesSent: number;\n\tsendBitrate: number;\n\trtpBytesReceived: number;\n\trtpRecvBitrate: number;\n\trtpBytesSent: number;\n\trtpSendBitrate: number;\n\trtxBytesReceived: number;\n\trtxRecvBitrate: number;\n\trtxBytesSent: number;\n\trtxSendBitrate: number;\n\tprobationBytesSent: number;\n\tprobationSendBitrate: number;\n\tavailableOutgoingBitrate?: number;\n\tavailableIncomingBitrate?: number;\n\tmaxIncomingBitrate?: number;\n\tmaxOutgoingBitrate?: number;\n\tminOutgoingBitrate?: number;\n\trtpPacketLossReceived?: number;\n\trtpPacketLossSent?: number;\n};\n\n/**\n * Valid types for 'trace' event.\n */\nexport type TransportTraceEventType = 'probation' | 'bwe';\n\n/**\n * 'trace' event data.\n */\nexport type TransportTraceEventData = {\n\t/**\n\t * Trace type.\n\t */\n\ttype: TransportTraceEventType;\n\n\t/**\n\t * Event timestamp.\n\t */\n\ttimestamp: number;\n\n\t/**\n\t * Event direction.\n\t */\n\tdirection: 'in' | 'out';\n\n\t/**\n\t * Per type information.\n\t */\n\tinfo: unknown;\n};\n\nexport type TransportEvents = {\n\trouterclose: [];\n\tlistenserverclose: [];\n\ttrace: [TransportTraceEventData];\n\t// Private events.\n\t'@close': [];\n\t'@newproducer': [Producer];\n\t'@producerclose': [Producer];\n\t'@newdataproducer': [DataProducer];\n\t'@dataproducerclose': [DataProducer];\n\t'@listenserverclose': [];\n};\n\nexport type TransportObserver = EnhancedEventEmitter<TransportObserverEvents>;\n\nexport type TransportObserverEvents = {\n\tclose: [];\n\tnewproducer: [Producer];\n\tnewconsumer: [Consumer];\n\tnewdataproducer: [DataProducer];\n\tnewdataconsumer: [DataConsumer];\n\ttrace: [TransportTraceEventData];\n};\n\nexport interface Transport<\n\tTransportAppData extends AppData = AppData,\n\tEvents extends TransportEvents = TransportEvents,\n\tObserver extends TransportObserver = TransportObserver,\n> extends EnhancedEventEmitter<Events> {\n\t/**\n\t * Transport id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the Transport is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * Transport type.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual getter since each Transport class overrides it.\n\t */\n\tget type(): TransportType;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): TransportAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: TransportAppData);\n\n\t/**\n\t * Observer.\n\t *\n\t * @virtual\n\t */\n\tget observer(): Observer;\n\n\t/**\n\t * Close the Transport.\n\t *\n\t * @virtual\n\t */\n\tclose(): void;\n\n\t/**\n\t * Router was closed.\n\t *\n\t * @private\n\t * @virtual\n\t */\n\trouterClosed(): void;\n\n\t/**\n\t * Listen server was closed (this just happens in WebRtcTransports when their\n\t * associated WebRtcServer is closed).\n\t *\n\t * @private\n\t * @virtual\n\t */\n\tlistenServerClosed(): void;\n\n\t/**\n\t * Dump Transport.\n\t *\n\t * @abstract\n\t */\n\tdump(): Promise<unknown>;\n\n\t/**\n\t * Get Transport stats.\n\t *\n\t * @abstract\n\t */\n\tgetStats(): Promise<unknown[]>;\n\n\t/**\n\t * Provide the Transport remote parameters.\n\t *\n\t * @abstract\n\t */\n\tconnect(params: unknown): Promise<void>;\n\n\t/**\n\t * Set maximum incoming bitrate for receiving media.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual method because DirectTransport overrides it.\n\t */\n\tsetMaxIncomingBitrate(bitrate: number): Promise<void>;\n\n\t/**\n\t * Set maximum outgoing bitrate for sending media.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual method because DirectTransport overrides it.\n\t */\n\tsetMaxOutgoingBitrate(bitrate: number): Promise<void>;\n\n\t/**\n\t * Set minimum outgoing bitrate for sending media.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual method because DirectTransport overrides it.\n\t */\n\tsetMinOutgoingBitrate(bitrate: number): Promise<void>;\n\n\t/**\n\t * Create a Producer.\n\t */\n\tproduce<ProducerAppData extends AppData = AppData>(\n\t\toptions: ProducerOptions<ProducerAppData>\n\t): Promise<Producer<ProducerAppData>>;\n\n\t/**\n\t * Create a Consumer.\n\t *\n\t * @virtual\n\t * @privateRemarks\n\t * - It's marked as virtual method because PipeTransport overrides it.\n\t */\n\tconsume<ConsumerAppData extends AppData = AppData>(\n\t\toptions: ConsumerOptions<ConsumerAppData>\n\t): Promise<Consumer<ConsumerAppData>>;\n\n\t/**\n\t * Create a DataProducer.\n\t */\n\tproduceData<DataProducerAppData extends AppData = AppData>(\n\t\toptions?: DataProducerOptions<DataProducerAppData>\n\t): Promise<DataProducer<DataProducerAppData>>;\n\n\t/**\n\t * Create a DataConsumer.\n\t */\n\tconsumeData<DataConsumerAppData extends AppData = AppData>(\n\t\toptions: DataConsumerOptions<DataConsumerAppData>\n\t): Promise<DataConsumer<DataConsumerAppData>>;\n\n\t/**\n\t * Enable 'trace' event.\n\t */\n\tenableTraceEvent(types?: TransportTraceEventType[]): Promise<void>;\n}\n"
  },
  {
    "path": "node/src/WebRtcServer.ts",
    "content": "import { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type { Channel } from './Channel';\nimport type {\n\tWebRtcServer,\n\tIpPort,\n\tIceUserNameFragment,\n\tTupleHash,\n\tWebRtcServerDump,\n\tWebRtcServerEvents,\n\tWebRtcServerObserver,\n\tWebRtcServerObserverEvents,\n} from './WebRtcServerTypes';\nimport type { WebRtcTransport } from './WebRtcTransportTypes';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Body as RequestBody, Method } from './fbs/request';\nimport * as FbsWorker from './fbs/worker';\nimport * as FbsWebRtcServer from './fbs/web-rtc-server';\n\ntype WebRtcServerInternal = {\n\twebRtcServerId: string;\n};\n\nconst logger = new Logger('WebRtcServer');\n\nexport class WebRtcServerImpl<WebRtcServerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<WebRtcServerEvents>\n\timplements WebRtcServer\n{\n\t// Internal data.\n\treadonly #internal: WebRtcServerInternal;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Custom app data.\n\t#appData: WebRtcServerAppData;\n\n\t// Transports map.\n\treadonly #webRtcTransports: Map<string, WebRtcTransport> = new Map();\n\n\t// Observer instance.\n\treadonly #observer: WebRtcServerObserver =\n\t\tnew EnhancedEventEmitter<WebRtcServerObserverEvents>();\n\n\tconstructor({\n\t\tinternal,\n\t\tchannel,\n\t\tappData,\n\t}: {\n\t\tinternal: WebRtcServerInternal;\n\t\tchannel: Channel;\n\t\tappData?: WebRtcServerAppData;\n\t}) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tthis.#internal = internal;\n\t\tthis.#channel = channel;\n\t\tthis.#appData = appData ?? ({} as WebRtcServerAppData);\n\n\t\tthis.handleListenerError();\n\t}\n\n\tget id(): string {\n\t\treturn this.#internal.webRtcServerId;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget appData(): WebRtcServerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: WebRtcServerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): WebRtcServerObserver {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t */\n\tget webRtcTransportsForTesting(): Map<string, WebRtcTransport> {\n\t\treturn this.#webRtcTransports;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\t// Build the request.\n\t\tconst requestOffset = new FbsWorker.CloseWebRtcServerRequestT(\n\t\t\tthis.#internal.webRtcServerId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tthis.#channel\n\t\t\t.request(\n\t\t\t\tMethod.WORKER_WEBRTCSERVER_CLOSE,\n\t\t\t\tRequestBody.Worker_CloseWebRtcServerRequest,\n\t\t\t\trequestOffset\n\t\t\t)\n\t\t\t.catch(() => {});\n\n\t\t// Close every WebRtcTransport.\n\t\tfor (const webRtcTransport of this.#webRtcTransports.values()) {\n\t\t\twebRtcTransport.listenServerClosed();\n\n\t\t\t// Emit observer event.\n\t\t\tthis.#observer.safeEmit('webrtctransportunhandled', webRtcTransport);\n\t\t}\n\t\tthis.#webRtcTransports.clear();\n\n\t\tthis.emit('@close');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tworkerClosed(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('workerClosed()');\n\n\t\tthis.#closed = true;\n\n\t\t// NOTE: No need to close WebRtcTransports since they are closed by their\n\t\t// respective Router parents.\n\t\tthis.#webRtcTransports.clear();\n\n\t\tthis.safeEmit('workerclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<WebRtcServerDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tMethod.WEBRTCSERVER_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.#internal.webRtcServerId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst dump = new FbsWebRtcServer.DumpResponse();\n\n\t\tresponse.body(dump);\n\n\t\treturn parseWebRtcServerDump(dump);\n\t}\n\n\thandleWebRtcTransport(webRtcTransport: WebRtcTransport): void {\n\t\tthis.#webRtcTransports.set(webRtcTransport.id, webRtcTransport);\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('webrtctransporthandled', webRtcTransport);\n\n\t\twebRtcTransport.on('@close', () => {\n\t\t\tthis.#webRtcTransports.delete(webRtcTransport.id);\n\n\t\t\t// Emit observer event.\n\t\t\tthis.#observer.safeEmit('webrtctransportunhandled', webRtcTransport);\n\t\t});\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction parseIpPort(binary: FbsWebRtcServer.IpPort): IpPort {\n\treturn {\n\t\tip: binary.ip()!,\n\t\tport: binary.port(),\n\t};\n}\n\nfunction parseIceUserNameFragment(\n\tbinary: FbsWebRtcServer.IceUserNameFragment\n): IceUserNameFragment {\n\treturn {\n\t\tlocalIceUsernameFragment: binary.localIceUsernameFragment()!,\n\t\twebRtcTransportId: binary.webRtcTransportId()!,\n\t};\n}\n\nfunction parseTupleHash(binary: FbsWebRtcServer.TupleHash): TupleHash {\n\treturn {\n\t\ttupleHash: Number(binary.tupleHash()),\n\t\twebRtcTransportId: binary.webRtcTransportId()!,\n\t};\n}\n\nfunction parseWebRtcServerDump(\n\tdata: FbsWebRtcServer.DumpResponse\n): WebRtcServerDump {\n\treturn {\n\t\tid: data.id()!,\n\t\tudpSockets: fbsUtils.parseVector(data, 'udpSockets', parseIpPort),\n\t\ttcpServers: fbsUtils.parseVector(data, 'tcpServers', parseIpPort),\n\t\twebRtcTransportIds: fbsUtils.parseVector(data, 'webRtcTransportIds'),\n\t\tlocalIceUsernameFragments: fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'localIceUsernameFragments',\n\t\t\tparseIceUserNameFragment\n\t\t),\n\t\ttupleHashes: fbsUtils.parseVector(data, 'tupleHashes', parseTupleHash),\n\t};\n}\n"
  },
  {
    "path": "node/src/WebRtcServerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { TransportListenInfo } from './TransportTypes';\nimport type { WebRtcTransport } from './WebRtcTransportTypes';\nimport type { AppData } from './types';\n\nexport type WebRtcServerOptions<WebRtcServerAppData extends AppData = AppData> =\n\t{\n\t\t/**\n\t\t * Listen infos.\n\t\t */\n\t\tlistenInfos: TransportListenInfo[];\n\n\t\t/**\n\t\t * Custom application data.\n\t\t */\n\t\tappData?: WebRtcServerAppData;\n\t};\n\n/**\n * @deprecated Use TransportListenInfo instead.\n */\nexport type WebRtcServerListenInfo = TransportListenInfo;\n\nexport type IpPort = {\n\tip: string;\n\tport: number;\n};\n\nexport type IceUserNameFragment = {\n\tlocalIceUsernameFragment: string;\n\twebRtcTransportId: string;\n};\n\nexport type TupleHash = {\n\ttupleHash: number;\n\twebRtcTransportId: string;\n};\n\nexport type WebRtcServerDump = {\n\tid: string;\n\tudpSockets: IpPort[];\n\ttcpServers: IpPort[];\n\twebRtcTransportIds: string[];\n\tlocalIceUsernameFragments: IceUserNameFragment[];\n\ttupleHashes: TupleHash[];\n};\n\nexport type WebRtcServerEvents = {\n\tworkerclose: [];\n\t// Private events.\n\t'@close': [];\n};\n\nexport type WebRtcServerObserver =\n\tEnhancedEventEmitter<WebRtcServerObserverEvents>;\n\nexport type WebRtcServerObserverEvents = {\n\tclose: [];\n\twebrtctransporthandled: [WebRtcTransport];\n\twebrtctransportunhandled: [WebRtcTransport];\n};\n\nexport interface WebRtcServer<\n\tWebRtcServerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<WebRtcServerEvents> {\n\t/**\n\t * WebRtcServer id.\n\t */\n\tget id(): string;\n\n\t/**\n\t * Whether the WebRtcServer is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): WebRtcServerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: WebRtcServerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): WebRtcServerObserver;\n\n\t/**\n\t * Close the WebRtcServer.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Worker was closed.\n\t *\n\t * @private\n\t */\n\tworkerClosed(): void;\n\n\t/**\n\t * Dump WebRtcServer.\n\t */\n\tdump(): Promise<WebRtcServerDump>;\n\n\t/**\n\t * @private\n\t */\n\thandleWebRtcTransport(webRtcTransport: WebRtcTransport): void;\n}\n"
  },
  {
    "path": "node/src/WebRtcTransport.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tWebRtcTransport,\n\tIceParameters,\n\tIceCandidate,\n\tDtlsParameters,\n\tFingerprintAlgorithm,\n\tDtlsFingerprint,\n\tIceRole,\n\tIceState,\n\tIceCandidateType,\n\tIceCandidateTcpType,\n\tDtlsRole,\n\tDtlsState,\n\tWebRtcTransportDump,\n\tWebRtcTransportStat,\n\tWebRtcTransportEvents,\n\tWebRtcTransportObserver,\n\tWebRtcTransportObserverEvents,\n} from './WebRtcTransportTypes';\nimport type { Transport, TransportTuple, SctpState } from './TransportTypes';\nimport {\n\tTransportImpl,\n\tTransportConstructorOptions,\n\tparseSctpState,\n\tparseBaseTransportDump,\n\tparseBaseTransportStats,\n\tparseProtocol,\n\tparseTransportTraceEventData,\n\tparseTuple,\n} from './Transport';\nimport type { SctpParameters } from './sctpParametersTypes';\nimport type { AppData } from './types';\nimport * as fbsUtils from './fbsUtils';\nimport { Event, Notification } from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsTransport from './fbs/transport';\nimport * as FbsWebRtcTransport from './fbs/web-rtc-transport';\nimport { DtlsState as FbsDtlsState } from './fbs/web-rtc-transport/dtls-state';\nimport { DtlsRole as FbsDtlsRole } from './fbs/web-rtc-transport/dtls-role';\nimport { FingerprintAlgorithm as FbsFingerprintAlgorithm } from './fbs/web-rtc-transport/fingerprint-algorithm';\nimport { IceState as FbsIceState } from './fbs/web-rtc-transport/ice-state';\nimport { IceRole as FbsIceRole } from './fbs/web-rtc-transport/ice-role';\nimport { IceCandidateType as FbsIceCandidateType } from './fbs/web-rtc-transport/ice-candidate-type';\nimport { IceCandidateTcpType as FbsIceCandidateTcpType } from './fbs/web-rtc-transport/ice-candidate-tcp-type';\n\ntype WebRtcTransportConstructorOptions<WebRtcTransportAppData> =\n\tTransportConstructorOptions<WebRtcTransportAppData> & {\n\t\tdata: WebRtcTransportData;\n\t};\n\nexport type WebRtcTransportData = {\n\ticeRole: 'controlled';\n\ticeParameters: IceParameters;\n\ticeCandidates: IceCandidate[];\n\ticeState: IceState;\n\ticeSelectedTuple?: TransportTuple;\n\tdtlsParameters: DtlsParameters;\n\tdtlsState: DtlsState;\n\tdtlsRemoteCert?: string;\n\tsctpParameters?: SctpParameters;\n\tsctpState?: SctpState;\n};\n\nconst logger = new Logger('WebRtcTransport');\n\nexport class WebRtcTransportImpl<\n\tWebRtcTransportAppData extends AppData = AppData,\n>\n\textends TransportImpl<\n\t\tWebRtcTransportAppData,\n\t\tWebRtcTransportEvents,\n\t\tWebRtcTransportObserver\n\t>\n\timplements Transport, WebRtcTransport\n{\n\t// WebRtcTransport data.\n\treadonly #data: WebRtcTransportData;\n\n\tconstructor(\n\t\toptions: WebRtcTransportConstructorOptions<WebRtcTransportAppData>\n\t) {\n\t\tconst observer: WebRtcTransportObserver =\n\t\t\tnew EnhancedEventEmitter<WebRtcTransportObserverEvents>();\n\n\t\tsuper(options, observer);\n\n\t\tlogger.debug('constructor()');\n\n\t\tconst { data } = options;\n\n\t\tthis.#data = {\n\t\t\ticeRole: data.iceRole,\n\t\t\ticeParameters: data.iceParameters,\n\t\t\ticeCandidates: data.iceCandidates,\n\t\t\ticeState: data.iceState,\n\t\t\ticeSelectedTuple: data.iceSelectedTuple,\n\t\t\tdtlsParameters: data.dtlsParameters,\n\t\t\tdtlsState: data.dtlsState,\n\t\t\tdtlsRemoteCert: data.dtlsRemoteCert,\n\t\t\tsctpParameters: data.sctpParameters,\n\t\t\tsctpState: data.sctpState,\n\t\t};\n\n\t\tthis.handleWorkerNotifications();\n\t\tthis.handleListenerError();\n\t}\n\n\tget type(): 'webrtc' {\n\t\treturn 'webrtc';\n\t}\n\n\toverride get observer(): WebRtcTransportObserver {\n\t\treturn super.observer;\n\t}\n\n\tget iceRole(): 'controlled' {\n\t\treturn this.#data.iceRole;\n\t}\n\n\tget iceParameters(): IceParameters {\n\t\treturn this.#data.iceParameters;\n\t}\n\n\tget iceCandidates(): IceCandidate[] {\n\t\treturn this.#data.iceCandidates;\n\t}\n\n\tget iceState(): IceState {\n\t\treturn this.#data.iceState;\n\t}\n\n\tget iceSelectedTuple(): TransportTuple | undefined {\n\t\treturn this.#data.iceSelectedTuple;\n\t}\n\n\tget dtlsParameters(): DtlsParameters {\n\t\treturn this.#data.dtlsParameters;\n\t}\n\n\tget dtlsState(): DtlsState {\n\t\treturn this.#data.dtlsState;\n\t}\n\n\tget dtlsRemoteCert(): string | undefined {\n\t\treturn this.#data.dtlsRemoteCert;\n\t}\n\n\tget sctpParameters(): SctpParameters | undefined {\n\t\treturn this.#data.sctpParameters;\n\t}\n\n\tget sctpState(): SctpState | undefined {\n\t\treturn this.#data.sctpState;\n\t}\n\n\toverride close(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#data.iceState = 'closed';\n\t\tthis.#data.iceSelectedTuple = undefined;\n\t\tthis.#data.dtlsState = 'closed';\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.close();\n\t}\n\n\toverride routerClosed(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#data.iceState = 'closed';\n\t\tthis.#data.iceSelectedTuple = undefined;\n\t\tthis.#data.dtlsState = 'closed';\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.routerClosed();\n\t}\n\n\toverride listenServerClosed(): void {\n\t\tif (this.closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#data.iceState = 'closed';\n\t\tthis.#data.iceSelectedTuple = undefined;\n\t\tthis.#data.dtlsState = 'closed';\n\n\t\tif (this.#data.sctpState) {\n\t\t\tthis.#data.sctpState = 'closed';\n\t\t}\n\n\t\tsuper.listenServerClosed();\n\t}\n\n\tasync dump(): Promise<WebRtcTransportDump> {\n\t\tlogger.debug('dump()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_DUMP,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsWebRtcTransport.DumpResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn parseWebRtcTransportDumpResponse(data);\n\t}\n\n\tasync getStats(): Promise<WebRtcTransportStat[]> {\n\t\tlogger.debug('getStats()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_GET_STATS,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsWebRtcTransport.GetStatsResponse();\n\n\t\tresponse.body(data);\n\n\t\treturn [parseGetStatsResponse(data)];\n\t}\n\n\tasync connect({\n\t\tdtlsParameters,\n\t}: {\n\t\tdtlsParameters: DtlsParameters;\n\t}): Promise<void> {\n\t\tlogger.debug('connect()');\n\n\t\tconst requestOffset = createConnectRequest({\n\t\t\tbuilder: this.channel.bufferBuilder,\n\t\t\tdtlsParameters,\n\t\t});\n\n\t\t// Wait for response.\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.WEBRTCTRANSPORT_CONNECT,\n\t\t\tFbsRequest.Body.WebRtcTransport_ConnectRequest,\n\t\t\trequestOffset,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst data = new FbsWebRtcTransport.ConnectResponse();\n\n\t\tresponse.body(data);\n\n\t\t// Update data.\n\t\tthis.#data.dtlsParameters.role = dtlsRoleFromFbs(data.dtlsLocalRole());\n\t}\n\n\tasync restartIce(): Promise<IceParameters> {\n\t\tlogger.debug('restartIce()');\n\n\t\tconst response = await this.channel.request(\n\t\t\tFbsRequest.Method.TRANSPORT_RESTART_ICE,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tthis.internal.transportId\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst restartIceResponse = new FbsTransport.RestartIceResponse();\n\n\t\tresponse.body(restartIceResponse);\n\n\t\tconst iceParameters = {\n\t\t\tusernameFragment: restartIceResponse.usernameFragment()!,\n\t\t\tpassword: restartIceResponse.password()!,\n\t\t\ticeLite: restartIceResponse.iceLite(),\n\t\t};\n\n\t\tthis.#data.iceParameters = iceParameters;\n\n\t\treturn iceParameters;\n\t}\n\n\tprivate handleWorkerNotifications(): void {\n\t\tthis.channel.on(\n\t\t\tthis.internal.transportId,\n\t\t\t(event: Event, data?: Notification) => {\n\t\t\t\tswitch (event) {\n\t\t\t\t\tcase Event.WEBRTCTRANSPORT_ICE_STATE_CHANGE: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsWebRtcTransport.IceStateChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst iceState = iceStateFromFbs(notification.iceState());\n\n\t\t\t\t\t\tthis.#data.iceState = iceState;\n\n\t\t\t\t\t\tthis.safeEmit('icestatechange', iceState);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('icestatechange', iceState);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsWebRtcTransport.IceSelectedTupleChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst iceSelectedTuple = parseTuple(notification.tuple()!);\n\n\t\t\t\t\t\tthis.#data.iceSelectedTuple = iceSelectedTuple;\n\n\t\t\t\t\t\tthis.safeEmit('iceselectedtuplechange', iceSelectedTuple);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('iceselectedtuplechange', iceSelectedTuple);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.WEBRTCTRANSPORT_DTLS_STATE_CHANGE: {\n\t\t\t\t\t\tconst notification =\n\t\t\t\t\t\t\tnew FbsWebRtcTransport.DtlsStateChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst dtlsState = dtlsStateFromFbs(notification.dtlsState());\n\n\t\t\t\t\t\tthis.#data.dtlsState = dtlsState;\n\n\t\t\t\t\t\tif (dtlsState === 'connected') {\n\t\t\t\t\t\t\tthis.#data.dtlsRemoteCert = notification.remoteCert()!;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.safeEmit('dtlsstatechange', dtlsState);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('dtlsstatechange', dtlsState);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.TRANSPORT_SCTP_STATE_CHANGE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.SctpStateChangeNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst sctpState = parseSctpState(notification.sctpState());\n\n\t\t\t\t\t\tthis.#data.sctpState = sctpState;\n\n\t\t\t\t\t\tthis.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('sctpstatechange', sctpState);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Event.TRANSPORT_TRACE: {\n\t\t\t\t\t\tconst notification = new FbsTransport.TraceNotification();\n\n\t\t\t\t\t\tdata!.body(notification);\n\n\t\t\t\t\t\tconst trace = parseTransportTraceEventData(notification);\n\n\t\t\t\t\t\tthis.safeEmit('trace', trace);\n\n\t\t\t\t\t\t// Emit observer event.\n\t\t\t\t\t\tthis.observer.safeEmit('trace', trace);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tlogger.error(`ignoring unknown event \"${event}\"`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n}\n\nfunction iceStateFromFbs(fbsIceState: FbsIceState): IceState {\n\tswitch (fbsIceState) {\n\t\tcase FbsIceState.NEW: {\n\t\t\treturn 'new';\n\t\t}\n\n\t\tcase FbsIceState.CONNECTED: {\n\t\t\treturn 'connected';\n\t\t}\n\n\t\tcase FbsIceState.COMPLETED: {\n\t\t\treturn 'completed';\n\t\t}\n\n\t\tcase FbsIceState.DISCONNECTED: {\n\t\t\treturn 'disconnected';\n\t\t}\n\t}\n}\n\nfunction iceRoleFromFbs(role: FbsIceRole): IceRole {\n\tswitch (role) {\n\t\tcase FbsIceRole.CONTROLLED: {\n\t\t\treturn 'controlled';\n\t\t}\n\n\t\tcase FbsIceRole.CONTROLLING: {\n\t\t\treturn 'controlling';\n\t\t}\n\t}\n}\n\nfunction iceCandidateTypeFromFbs(type: FbsIceCandidateType): IceCandidateType {\n\tswitch (type) {\n\t\tcase FbsIceCandidateType.HOST: {\n\t\t\treturn 'host';\n\t\t}\n\t}\n}\n\nfunction iceCandidateTcpTypeFromFbs(\n\ttype: FbsIceCandidateTcpType\n): IceCandidateTcpType {\n\tswitch (type) {\n\t\tcase FbsIceCandidateTcpType.PASSIVE: {\n\t\t\treturn 'passive';\n\t\t}\n\t}\n}\n\nfunction dtlsStateFromFbs(fbsDtlsState: FbsDtlsState): DtlsState {\n\tswitch (fbsDtlsState) {\n\t\tcase FbsDtlsState.NEW: {\n\t\t\treturn 'new';\n\t\t}\n\n\t\tcase FbsDtlsState.CONNECTING: {\n\t\t\treturn 'connecting';\n\t\t}\n\n\t\tcase FbsDtlsState.CONNECTED: {\n\t\t\treturn 'connected';\n\t\t}\n\n\t\tcase FbsDtlsState.FAILED: {\n\t\t\treturn 'failed';\n\t\t}\n\n\t\tcase FbsDtlsState.CLOSED: {\n\t\t\treturn 'closed';\n\t\t}\n\t}\n}\n\nfunction dtlsRoleFromFbs(role: FbsDtlsRole): DtlsRole {\n\tswitch (role) {\n\t\tcase FbsDtlsRole.AUTO: {\n\t\t\treturn 'auto';\n\t\t}\n\n\t\tcase FbsDtlsRole.CLIENT: {\n\t\t\treturn 'client';\n\t\t}\n\n\t\tcase FbsDtlsRole.SERVER: {\n\t\t\treturn 'server';\n\t\t}\n\t}\n}\n\nfunction fingerprintAlgorithmsFromFbs(\n\talgorithm: FbsFingerprintAlgorithm\n): FingerprintAlgorithm {\n\tswitch (algorithm) {\n\t\tcase FbsFingerprintAlgorithm.SHA1: {\n\t\t\treturn 'sha-1';\n\t\t}\n\n\t\tcase FbsFingerprintAlgorithm.SHA224: {\n\t\t\treturn 'sha-224';\n\t\t}\n\n\t\tcase FbsFingerprintAlgorithm.SHA256: {\n\t\t\treturn 'sha-256';\n\t\t}\n\n\t\tcase FbsFingerprintAlgorithm.SHA384: {\n\t\t\treturn 'sha-384';\n\t\t}\n\n\t\tcase FbsFingerprintAlgorithm.SHA512: {\n\t\t\treturn 'sha-512';\n\t\t}\n\t}\n}\n\nfunction fingerprintAlgorithmToFbs(\n\talgorithm: FingerprintAlgorithm\n): FbsFingerprintAlgorithm {\n\tswitch (algorithm) {\n\t\tcase 'sha-1': {\n\t\t\treturn FbsFingerprintAlgorithm.SHA1;\n\t\t}\n\n\t\tcase 'sha-224': {\n\t\t\treturn FbsFingerprintAlgorithm.SHA224;\n\t\t}\n\n\t\tcase 'sha-256': {\n\t\t\treturn FbsFingerprintAlgorithm.SHA256;\n\t\t}\n\n\t\tcase 'sha-384': {\n\t\t\treturn FbsFingerprintAlgorithm.SHA384;\n\t\t}\n\n\t\tcase 'sha-512': {\n\t\t\treturn FbsFingerprintAlgorithm.SHA512;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid FingerprintAlgorithm: ${algorithm}`);\n\t\t}\n\t}\n}\n\nfunction dtlsRoleToFbs(role: DtlsRole): FbsDtlsRole {\n\tswitch (role) {\n\t\tcase 'auto': {\n\t\t\treturn FbsDtlsRole.AUTO;\n\t\t}\n\n\t\tcase 'client': {\n\t\t\treturn FbsDtlsRole.CLIENT;\n\t\t}\n\n\t\tcase 'server': {\n\t\t\treturn FbsDtlsRole.SERVER;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid DtlsRole: ${role}`);\n\t\t}\n\t}\n}\n\nexport function parseWebRtcTransportDumpResponse(\n\tbinary: FbsWebRtcTransport.DumpResponse\n): WebRtcTransportDump {\n\t// Retrieve BaseTransportDump.\n\tconst baseTransportDump = parseBaseTransportDump(binary.base()!);\n\t// Retrieve ICE candidates.\n\tconst iceCandidates = fbsUtils.parseVector<IceCandidate>(\n\t\tbinary,\n\t\t'iceCandidates',\n\t\tparseIceCandidate\n\t);\n\t// Retrieve ICE parameters.\n\tconst iceParameters = parseIceParameters(binary.iceParameters()!);\n\t// Retrieve DTLS parameters.\n\tconst dtlsParameters = parseDtlsParameters(binary.dtlsParameters()!);\n\n\treturn {\n\t\t...baseTransportDump,\n\t\tsctpParameters: baseTransportDump.sctpParameters,\n\t\tsctpState: baseTransportDump.sctpState,\n\t\ticeRole: 'controlled',\n\t\ticeParameters: iceParameters,\n\t\ticeCandidates: iceCandidates,\n\t\ticeState: iceStateFromFbs(binary.iceState()),\n\t\tdtlsParameters: dtlsParameters,\n\t\tdtlsState: dtlsStateFromFbs(binary.dtlsState()),\n\t};\n}\n\nfunction createConnectRequest({\n\tbuilder,\n\tdtlsParameters,\n}: {\n\tbuilder: flatbuffers.Builder;\n\tdtlsParameters: DtlsParameters;\n}): number {\n\t// Serialize DtlsParameters. This can throw.\n\tconst dtlsParametersOffset = serializeDtlsParameters(builder, dtlsParameters);\n\n\treturn FbsWebRtcTransport.ConnectRequest.createConnectRequest(\n\t\tbuilder,\n\t\tdtlsParametersOffset\n\t);\n}\n\nfunction parseGetStatsResponse(\n\tbinary: FbsWebRtcTransport.GetStatsResponse\n): WebRtcTransportStat {\n\tconst base = parseBaseTransportStats(binary.base()!);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'webrtc-transport',\n\t\ticeRole: iceRoleFromFbs(binary.iceRole()),\n\t\ticeState: iceStateFromFbs(binary.iceState()),\n\t\ticeSelectedTuple: binary.iceSelectedTuple()\n\t\t\t? parseTuple(binary.iceSelectedTuple()!)\n\t\t\t: undefined,\n\t\tdtlsState: dtlsStateFromFbs(binary.dtlsState()),\n\t};\n}\n\nfunction parseIceCandidate(\n\tbinary: FbsWebRtcTransport.IceCandidate\n): IceCandidate {\n\treturn {\n\t\tfoundation: binary.foundation()!,\n\t\tpriority: binary.priority(),\n\t\tip: binary.address()!,\n\t\taddress: binary.address()!,\n\t\tprotocol: parseProtocol(binary.protocol()),\n\t\tport: binary.port(),\n\t\ttype: iceCandidateTypeFromFbs(binary.type()),\n\t\ttcpType:\n\t\t\tbinary.tcpType() === null\n\t\t\t\t? undefined\n\t\t\t\t: iceCandidateTcpTypeFromFbs(binary.tcpType()!),\n\t};\n}\n\nfunction parseIceParameters(\n\tbinary: FbsWebRtcTransport.IceParameters\n): IceParameters {\n\treturn {\n\t\tusernameFragment: binary.usernameFragment()!,\n\t\tpassword: binary.password()!,\n\t\ticeLite: binary.iceLite(),\n\t};\n}\n\nfunction parseDtlsParameters(\n\tbinary: FbsWebRtcTransport.DtlsParameters\n): DtlsParameters {\n\tconst fingerprints: DtlsFingerprint[] = [];\n\n\tfor (let i = 0; i < binary.fingerprintsLength(); ++i) {\n\t\tconst fbsFingerprint = binary.fingerprints(i)!;\n\t\tconst fingerPrint: DtlsFingerprint = {\n\t\t\talgorithm: fingerprintAlgorithmsFromFbs(fbsFingerprint.algorithm()),\n\t\t\tvalue: fbsFingerprint.value()!,\n\t\t};\n\n\t\tfingerprints.push(fingerPrint);\n\t}\n\n\treturn {\n\t\tfingerprints: fingerprints,\n\t\trole: binary.role() === null ? undefined : dtlsRoleFromFbs(binary.role()),\n\t};\n}\n\nfunction serializeDtlsParameters(\n\tbuilder: flatbuffers.Builder,\n\tdtlsParameters: DtlsParameters\n): number {\n\tconst fingerprints: number[] = [];\n\n\tfor (const fingerprint of dtlsParameters.fingerprints) {\n\t\tconst algorithm = fingerprintAlgorithmToFbs(fingerprint.algorithm);\n\t\tconst valueOffset = builder.createString(fingerprint.value);\n\t\tconst fingerprintOffset = FbsWebRtcTransport.Fingerprint.createFingerprint(\n\t\t\tbuilder,\n\t\t\talgorithm,\n\t\t\tvalueOffset\n\t\t);\n\n\t\tfingerprints.push(fingerprintOffset);\n\t}\n\n\tconst fingerprintsOffset =\n\t\tFbsWebRtcTransport.DtlsParameters.createFingerprintsVector(\n\t\t\tbuilder,\n\t\t\tfingerprints\n\t\t);\n\n\tconst role =\n\t\tdtlsParameters.role !== undefined\n\t\t\t? dtlsRoleToFbs(dtlsParameters.role)\n\t\t\t: FbsWebRtcTransport.DtlsRole.AUTO;\n\n\treturn FbsWebRtcTransport.DtlsParameters.createDtlsParameters(\n\t\tbuilder,\n\t\tfingerprintsOffset,\n\t\trole\n\t);\n}\n"
  },
  {
    "path": "node/src/WebRtcTransportTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tTransport,\n\tTransportListenInfo,\n\tTransportListenIp,\n\tTransportProtocol,\n\tTransportTuple,\n\tSctpState,\n\tBaseTransportDump,\n\tBaseTransportStats,\n\tTransportEvents,\n\tTransportObserverEvents,\n} from './TransportTypes';\nimport type { WebRtcServer } from './WebRtcServerTypes';\nimport type { SctpParameters, NumSctpStreams } from './sctpParametersTypes';\nimport type { Either, AppData } from './types';\n\nexport type WebRtcTransportOptions<\n\tWebRtcTransportAppData extends AppData = AppData,\n> = WebRtcTransportOptionsBase<WebRtcTransportAppData> & WebRtcTransportListen;\n\ntype WebRtcTransportOptionsBase<WebRtcTransportAppData> = {\n\t/**\n\t * Listen in UDP. Default true.\n\t */\n\tenableUdp?: boolean;\n\n\t/**\n\t * Listen in TCP. Default true if webrtcServer is given, false otherwise.\n\t */\n\tenableTcp?: boolean;\n\n\t/**\n\t * Prefer UDP. Default false.\n\t */\n\tpreferUdp?: boolean;\n\n\t/**\n\t * Prefer TCP. Default false.\n\t */\n\tpreferTcp?: boolean;\n\n\t/**\n\t * ICE consent timeout (in seconds). If 0 it is disabled. Default 30.\n\t */\n\ticeConsentTimeout?: number;\n\n\t/**\n\t * Initial available outgoing bitrate (in bps). Default 600000.\n\t */\n\tinitialAvailableOutgoingBitrate?: number;\n\n\t/**\n\t * Create a SCTP association. Default false.\n\t */\n\tenableSctp?: boolean;\n\n\t/**\n\t * SCTP streams number.\n\t */\n\tnumSctpStreams?: NumSctpStreams;\n\n\t/**\n\t * Maximum allowed size for SCTP messages sent by DataProducers.\n\t * Default 262144.\n\t */\n\tmaxSctpMessageSize?: number;\n\n\t/**\n\t * Maximum SCTP send buffer used by DataConsumers.\n\t * Default 262144.\n\t */\n\tsctpSendBufferSize?: number;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: WebRtcTransportAppData;\n};\n\ntype WebRtcTransportListen = Either<\n\tEither<\n\t\tWebRtcTransportListenIndividualListenInfo,\n\t\tWebRtcTransportListenIndividualListenIp\n\t>,\n\tWebRtcTransportListenServer\n>;\n\ntype WebRtcTransportListenIndividualListenInfo = {\n\t/**\n\t * Listening info.\n\t */\n\tlistenInfos: TransportListenInfo[];\n};\n\ntype WebRtcTransportListenIndividualListenIp = {\n\t/**\n\t * Listening IP address or addresses in order of preference (first one is the\n\t * preferred one).\n\t */\n\tlistenIps: (TransportListenIp | string)[];\n\n\t/**\n\t * Fixed port to listen on instead of selecting automatically from Worker's port\n\t * range.\n\t */\n\tport?: number;\n};\n\ntype WebRtcTransportListenServer = {\n\t/**\n\t * Instance of WebRtcServer.\n\t */\n\twebRtcServer: WebRtcServer;\n};\n\nexport type IceParameters = {\n\tusernameFragment: string;\n\tpassword: string;\n\ticeLite?: boolean;\n};\n\nexport type IceCandidate = {\n\tfoundation: string;\n\tpriority: number;\n\t// @deprecated Use |address| instead.\n\tip: string;\n\taddress: string;\n\tprotocol: TransportProtocol;\n\tport: number;\n\ttype: IceCandidateType;\n\ttcpType?: IceCandidateTcpType;\n};\n\nexport type DtlsParameters = {\n\trole?: DtlsRole;\n\tfingerprints: DtlsFingerprint[];\n};\n\n/**\n * The hash function algorithm (as defined in the \"Hash function Textual Names\"\n * registry initially specified in RFC 4572 Section 8).\n */\nexport type FingerprintAlgorithm =\n\t| 'sha-1'\n\t| 'sha-224'\n\t| 'sha-256'\n\t| 'sha-384'\n\t| 'sha-512';\n\n/**\n * The hash function algorithm and its corresponding certificate fingerprint\n * value (in lowercase hex string as expressed utilizing the syntax of\n * \"fingerprint\" in RFC 4572 Section 5).\n */\nexport type DtlsFingerprint = {\n\talgorithm: FingerprintAlgorithm;\n\tvalue: string;\n};\n\nexport type IceRole = 'controlled' | 'controlling';\n\nexport type IceState =\n\t| 'new'\n\t| 'connected'\n\t| 'completed'\n\t| 'disconnected'\n\t| 'closed';\n\nexport type IceCandidateType = 'host';\n\nexport type IceCandidateTcpType = 'passive';\n\nexport type DtlsRole = 'auto' | 'client' | 'server';\n\nexport type DtlsState =\n\t| 'new'\n\t| 'connecting'\n\t| 'connected'\n\t| 'failed'\n\t| 'closed';\n\nexport type WebRtcTransportDump = BaseTransportDump & {\n\ticeRole: 'controlled';\n\ticeParameters: IceParameters;\n\ticeCandidates: IceCandidate[];\n\ticeState: IceState;\n\ticeSelectedTuple?: TransportTuple;\n\tdtlsParameters: DtlsParameters;\n\tdtlsState: DtlsState;\n\tdtlsRemoteCert?: string;\n};\n\nexport type WebRtcTransportStat = BaseTransportStats & {\n\ttype: string;\n\ticeRole: string;\n\ticeState: IceState;\n\ticeSelectedTuple?: TransportTuple;\n\tdtlsState: DtlsState;\n};\n\nexport type WebRtcTransportEvents = TransportEvents & {\n\ticestatechange: [IceState];\n\ticeselectedtuplechange: [TransportTuple];\n\tdtlsstatechange: [DtlsState];\n\tsctpstatechange: [SctpState];\n};\n\nexport type WebRtcTransportObserver =\n\tEnhancedEventEmitter<WebRtcTransportObserverEvents>;\n\nexport type WebRtcTransportObserverEvents = TransportObserverEvents & {\n\ticestatechange: [IceState];\n\ticeselectedtuplechange: [TransportTuple];\n\tdtlsstatechange: [DtlsState];\n\tsctpstatechange: [SctpState];\n};\n\nexport interface WebRtcTransport<\n\tWebRtcTransportAppData extends AppData = AppData,\n> extends Transport<\n\tWebRtcTransportAppData,\n\tWebRtcTransportEvents,\n\tWebRtcTransportObserver\n> {\n\t/**\n\t * Transport type.\n\t *\n\t * @override\n\t */\n\tget type(): 'webrtc';\n\n\t/**\n\t * Observer.\n\t *\n\t * @override\n\t */\n\tget observer(): WebRtcTransportObserver;\n\n\t/**\n\t * ICE role.\n\t */\n\tget iceRole(): 'controlled';\n\n\t/**\n\t * ICE parameters.\n\t */\n\tget iceParameters(): IceParameters;\n\n\t/**\n\t * ICE candidates.\n\t */\n\tget iceCandidates(): IceCandidate[];\n\n\t/**\n\t * ICE state.\n\t */\n\tget iceState(): IceState;\n\n\t/**\n\t * ICE selected tuple.\n\t */\n\tget iceSelectedTuple(): TransportTuple | undefined;\n\n\t/**\n\t * DTLS parameters.\n\t */\n\tget dtlsParameters(): DtlsParameters;\n\n\t/**\n\t * DTLS state.\n\t */\n\tget dtlsState(): DtlsState;\n\n\t/**\n\t * Remote certificate in PEM format.\n\t */\n\tget dtlsRemoteCert(): string | undefined;\n\n\t/**\n\t * SCTP parameters.\n\t */\n\tget sctpParameters(): SctpParameters | undefined;\n\n\t/**\n\t * SCTP state.\n\t */\n\tget sctpState(): SctpState | undefined;\n\n\t/**\n\t * Dump WebRtcTransport.\n\t *\n\t * @override\n\t */\n\tdump(): Promise<WebRtcTransportDump>;\n\n\t/**\n\t * Get WebRtcTransport stats.\n\t *\n\t * @override\n\t */\n\tgetStats(): Promise<WebRtcTransportStat[]>;\n\n\t/**\n\t * Provide the WebRtcTransport remote parameters.\n\t *\n\t * @override\n\t */\n\tconnect({\n\t\tdtlsParameters,\n\t}: {\n\t\tdtlsParameters: DtlsParameters;\n\t}): Promise<void>;\n\n\t/**\n\t * Restart ICE.\n\t */\n\trestartIce(): Promise<IceParameters>;\n}\n"
  },
  {
    "path": "node/src/Worker.ts",
    "content": "// NOTE: Must import `process` using default import otherwise we get the\n// `process` namespace and it doesn't have `removeListener()`.\nimport process from 'node:process';\nimport * as path from 'node:path';\nimport type { Duplex } from 'node:stream';\nimport { spawn, ChildProcess } from 'node:child_process';\nimport { version } from './';\nimport { Logger } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport * as ortc from './ortc';\nimport type {\n\tWorker,\n\tWorkerSettings,\n\tWorkerUpdateableSettings,\n\tWorkerResourceUsage,\n\tWorkerDump,\n\tWorkerEvents,\n\tWorkerObserver,\n\tWorkerObserverEvents,\n} from './WorkerTypes';\nimport { Channel } from './Channel';\nimport type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes';\nimport { WebRtcServerImpl } from './WebRtcServer';\nimport type { Router, RouterOptions } from './RouterTypes';\nimport { RouterImpl } from './Router';\nimport { portRangeToFbs, socketFlagsToFbs } from './Transport';\nimport type { RouterRtpCodecCapability } from './rtpParametersTypes';\nimport * as utils from './utils';\nimport * as fbsUtils from './fbsUtils';\nimport type { AppData } from './types';\nimport { Event } from './fbs/notification';\nimport * as FbsNotification from './fbs/notification';\nimport * as FbsRequest from './fbs/request';\nimport * as FbsWorker from './fbs/worker';\nimport * as FbsTransport from './fbs/transport';\nimport { Protocol as FbsTransportProtocol } from './fbs/transport/protocol';\n\nconst logger = new Logger('Worker');\nconst workerLogger = new Logger('Worker');\n\nexport const defaultWorkerBin: string = getDefaultWorkerBin();\n\nexport class WorkerImpl<WorkerAppData extends AppData = AppData>\n\textends EnhancedEventEmitter<WorkerEvents>\n\timplements Worker\n{\n\t// mediasoup-worker child process.\n\t#child: ChildProcess;\n\n\t// Worker process PID.\n\treadonly #pid: number;\n\n\t// Channel instance.\n\treadonly #channel: Channel;\n\n\t// Closed flag.\n\t#closed = false;\n\n\t// Died dlag.\n\t#died = false;\n\n\t// Worker process closed flag.\n\t#subprocessClosed = false;\n\n\t// Custom app data.\n\t#appData: WorkerAppData;\n\n\t// WebRtcServers set.\n\treadonly #webRtcServers: Set<WebRtcServer> = new Set();\n\n\t// Routers set.\n\treadonly #routers: Set<Router> = new Set();\n\n\t// Observer instance.\n\treadonly #observer: WorkerObserver =\n\t\tnew EnhancedEventEmitter<WorkerObserverEvents>();\n\n\tconstructor({\n\t\tlogLevel,\n\t\tlogTags,\n\t\trtcMinPort,\n\t\trtcMaxPort,\n\t\tdtlsCertificateFile,\n\t\tdtlsPrivateKeyFile,\n\t\tworkerBin,\n\t\tlibwebrtcFieldTrials,\n\t\tdisableLiburing,\n\t\tuseBuiltInSctpStack,\n\t\tappData,\n\t}: WorkerSettings<WorkerAppData>) {\n\t\tsuper();\n\n\t\tlogger.debug('constructor()');\n\n\t\tworkerBin = workerBin ?? defaultWorkerBin;\n\n\t\tlet spawnBin = workerBin;\n\t\tlet spawnArgs: string[] = [];\n\n\t\tif (process.env['MEDIASOUP_USE_VALGRIND'] === 'true') {\n\t\t\tspawnBin = process.env['MEDIASOUP_VALGRIND_BIN'] ?? 'valgrind';\n\n\t\t\tif (process.env['MEDIASOUP_VALGRIND_OPTIONS']) {\n\t\t\t\tspawnArgs = spawnArgs.concat(\n\t\t\t\t\tprocess.env['MEDIASOUP_VALGRIND_OPTIONS'].split(/\\s+/)\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tspawnArgs.push(workerBin);\n\t\t}\n\n\t\tif (typeof logLevel === 'string' && logLevel) {\n\t\t\tspawnArgs.push(`--logLevel=${logLevel}`);\n\t\t}\n\n\t\tfor (const logTag of Array.isArray(logTags) ? logTags : []) {\n\t\t\tif (typeof logTag === 'string' && logTag) {\n\t\t\t\tspawnArgs.push(`--logTag=${logTag}`);\n\t\t\t}\n\t\t}\n\n\t\tif (typeof rtcMinPort === 'number' && !Number.isNaN(rtcMinPort)) {\n\t\t\tspawnArgs.push(`--rtcMinPort=${rtcMinPort}`);\n\t\t}\n\n\t\tif (typeof rtcMaxPort === 'number' && !Number.isNaN(rtcMaxPort)) {\n\t\t\tspawnArgs.push(`--rtcMaxPort=${rtcMaxPort}`);\n\t\t}\n\n\t\tif (typeof dtlsCertificateFile === 'string' && dtlsCertificateFile) {\n\t\t\tspawnArgs.push(`--dtlsCertificateFile=${dtlsCertificateFile}`);\n\t\t}\n\n\t\tif (typeof dtlsPrivateKeyFile === 'string' && dtlsPrivateKeyFile) {\n\t\t\tspawnArgs.push(`--dtlsPrivateKeyFile=${dtlsPrivateKeyFile}`);\n\t\t}\n\n\t\tif (typeof libwebrtcFieldTrials === 'string' && libwebrtcFieldTrials) {\n\t\t\tspawnArgs.push(`--libwebrtcFieldTrials=${libwebrtcFieldTrials}`);\n\t\t}\n\n\t\tif (disableLiburing) {\n\t\t\tspawnArgs.push('--disableLiburing=true');\n\t\t}\n\n\t\tif (useBuiltInSctpStack) {\n\t\t\tspawnArgs.push('--useBuiltInSctpStack=true');\n\t\t} else {\n\t\t\tspawnArgs.push('--useBuiltInSctpStack=false');\n\t\t}\n\n\t\tlogger.debug(`spawning worker process: ${spawnBin} ${spawnArgs.join(' ')}`);\n\n\t\tthis.#child = spawn(\n\t\t\t// command\n\t\t\tspawnBin,\n\t\t\t// args\n\t\t\tspawnArgs,\n\t\t\t// options\n\t\t\t{\n\t\t\t\tenv: {\n\t\t\t\t\tMEDIASOUP_VERSION: version,\n\t\t\t\t\t// Let the worker process inherit all environment variables, useful\n\t\t\t\t\t// if a custom and not in the path GCC is used so the user can set\n\t\t\t\t\t// LD_LIBRARY_PATH environment variable for runtime.\n\t\t\t\t\t...process.env,\n\t\t\t\t},\n\n\t\t\t\tdetached: false,\n\n\t\t\t\t// fd 0 (stdin)   : Just ignore it.\n\t\t\t\t// fd 1 (stdout)  : Pipe it for 3rd libraries that log their own stuff.\n\t\t\t\t// fd 2 (stderr)  : Same as stdout.\n\t\t\t\t// fd 3 (channel) : Producer Channel fd.\n\t\t\t\t// fd 4 (channel) : Consumer Channel fd.\n\t\t\t\tstdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],\n\t\t\t\twindowsHide: true,\n\t\t\t}\n\t\t);\n\n\t\tthis.#pid = this.#child.pid!;\n\n\t\tthis.#channel = new Channel({\n\t\t\tproducerSocket: this.#child.stdio[3] as Duplex,\n\t\t\tconsumerSocket: this.#child.stdio[4] as Duplex,\n\t\t\tpid: this.#pid,\n\t\t});\n\n\t\tthis.#appData = appData ?? ({} as WorkerAppData);\n\n\t\tlet spawnDone = false;\n\n\t\t// Listen for 'running' notification.\n\t\tthis.#channel.once(String(this.#pid), (event: Event) => {\n\t\t\tif (!spawnDone && event === Event.WORKER_RUNNING) {\n\t\t\t\tspawnDone = true;\n\n\t\t\t\tlogger.debug(`worker process running [pid:${this.#pid}]`);\n\n\t\t\t\tthis.emit('@success');\n\t\t\t}\n\t\t});\n\n\t\tthis.#child.on('exit', (code, signal) => {\n\t\t\t// If closed by ourselves, do nothing.\n\t\t\tif (this.#closed) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!spawnDone) {\n\t\t\t\tspawnDone = true;\n\n\t\t\t\tif (code === 42) {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`worker process failed due to wrong settings [pid:${this.#pid}]`\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.close();\n\t\t\t\t\tthis.emit('@failure', new TypeError('wrong settings'));\n\t\t\t\t} else {\n\t\t\t\t\tlogger.error(\n\t\t\t\t\t\t`worker process failed unexpectedly [pid:${this.#pid}, code:${code}, signal:${signal}]`\n\t\t\t\t\t);\n\n\t\t\t\t\tthis.close();\n\t\t\t\t\tthis.emit(\n\t\t\t\t\t\t'@failure',\n\t\t\t\t\t\tnew Error(`[pid:${this.#pid}, code:${code}, signal:${signal}]`)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`worker process died unexpectedly [pid:${this.#pid}, code:${code}, signal:${signal}]`\n\t\t\t\t);\n\n\t\t\t\tthis.workerDied(\n\t\t\t\t\tnew Error(`[pid:${this.#pid}, code:${code}, signal:${signal}]`)\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\tthis.#child.on('error', error => {\n\t\t\t// If closed by ourselves, do nothing.\n\t\t\tif (this.#closed) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!spawnDone) {\n\t\t\t\tspawnDone = true;\n\n\t\t\t\tlogger.error(\n\t\t\t\t\t`worker process failed [pid:${this.#pid}]: ${error.message}`\n\t\t\t\t);\n\n\t\t\t\tthis.close();\n\t\t\t\tthis.emit('@failure', new Error(error.message));\n\t\t\t} else {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`worker process error [pid:${this.#pid}]: ${error.message}`\n\t\t\t\t);\n\n\t\t\t\tthis.workerDied(error);\n\t\t\t}\n\t\t});\n\n\t\tthis.#child.on('close', (code, signal) => {\n\t\t\tlogger.debug(\n\t\t\t\t`worker process closed [pid:${this.#pid}, code:${code}, signal:${signal}]`\n\t\t\t);\n\n\t\t\tif (!this.#subprocessClosed) {\n\t\t\t\tthis.#subprocessClosed = true;\n\n\t\t\t\tlogger.debug(`emitting 'subprocessclose' event`);\n\n\t\t\t\tthis.safeEmit('subprocessclose');\n\t\t\t}\n\t\t});\n\n\t\t// Be ready for 3rd party worker libraries logging to stdout.\n\t\tthis.#child.stdout!.on('data', buffer => {\n\t\t\tfor (const line of buffer.toString('utf8').split('\\n')) {\n\t\t\t\tif (line) {\n\t\t\t\t\tworkerLogger.debug(`(stdout) ${line}`);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// In case of a worker bug, mediasoup will log to stderr.\n\t\tthis.#child.stderr!.on('data', buffer => {\n\t\t\tfor (const line of buffer.toString('utf8').split('\\n')) {\n\t\t\t\tif (line) {\n\t\t\t\t\tworkerLogger.error(`(stderr) ${line}`);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// NOTE: Avoid \"Possible EventEmitter memory leak detected\" Node warning.\n\t\tconst processListenerCount = process.getMaxListeners();\n\n\t\tif (processListenerCount >= 10) {\n\t\t\tprocess.setMaxListeners(processListenerCount + 2);\n\t\t}\n\n\t\tprocess.once('SIGINT', this.onSignal);\n\t\tprocess.once('SIGTERM', this.onSignal);\n\n\t\tthis.handleListenerError();\n\t}\n\n\tget pid(): number {\n\t\treturn this.#pid;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tget died(): boolean {\n\t\treturn this.#died;\n\t}\n\n\tget subprocessClosed(): boolean {\n\t\treturn this.#subprocessClosed;\n\t}\n\n\tget appData(): WorkerAppData {\n\t\treturn this.#appData;\n\t}\n\n\tset appData(appData: WorkerAppData) {\n\t\tthis.#appData = appData;\n\t}\n\n\tget observer(): WorkerObserver {\n\t\treturn this.#observer;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t */\n\tget webRtcServersForTesting(): Set<WebRtcServer> {\n\t\treturn this.#webRtcServers;\n\t}\n\n\t/**\n\t * Just for testing purposes.\n\t */\n\tget routersForTesting(): Set<Router> {\n\t\treturn this.#routers;\n\t}\n\n\tclose(): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug('close()');\n\n\t\tthis.#closed = true;\n\n\t\tprocess.removeListener('SIGINT', this.onSignal);\n\t\tprocess.removeListener('SIGTERM', this.onSignal);\n\n\t\t// Close every Router.\n\t\tfor (const router of this.#routers) {\n\t\t\trouter.workerClosed();\n\t\t}\n\t\tthis.#routers.clear();\n\n\t\t// Close every WebRtcServer.\n\t\tfor (const webRtcServer of this.#webRtcServers) {\n\t\t\twebRtcServer.workerClosed();\n\t\t}\n\t\tthis.#webRtcServers.clear();\n\n\t\t// Send notification to worker process.\n\t\tthis.#channel.notify(FbsNotification.Event.WORKER_CLOSE);\n\n\t\t// Close the Channel instance now.\n\t\tthis.#channel.close();\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tasync dump(): Promise<WorkerDump> {\n\t\tlogger.debug('dump()');\n\n\t\t// Send the request and wait for the response.\n\t\tconst response = await this.#channel.request(FbsRequest.Method.WORKER_DUMP);\n\n\t\t/* Decode Response. */\n\t\tconst dump = new FbsWorker.DumpResponse();\n\n\t\tresponse.body(dump);\n\n\t\treturn parseWorkerDumpResponse(dump);\n\t}\n\n\tasync getResourceUsage(): Promise<WorkerResourceUsage> {\n\t\tlogger.debug('getResourceUsage()');\n\n\t\tconst response = await this.#channel.request(\n\t\t\tFbsRequest.Method.WORKER_GET_RESOURCE_USAGE\n\t\t);\n\n\t\t/* Decode Response. */\n\t\tconst resourceUsage = new FbsWorker.ResourceUsageResponse();\n\n\t\tresponse.body(resourceUsage);\n\n\t\tconst ru = resourceUsage.unpack();\n\n\t\treturn {\n\t\t\tru_utime: Number(ru.ruUtime),\n\t\t\tru_stime: Number(ru.ruStime),\n\t\t\tru_maxrss: Number(ru.ruMaxrss),\n\t\t\tru_ixrss: Number(ru.ruIxrss),\n\t\t\tru_idrss: Number(ru.ruIdrss),\n\t\t\tru_isrss: Number(ru.ruIsrss),\n\t\t\tru_minflt: Number(ru.ruMinflt),\n\t\t\tru_majflt: Number(ru.ruMajflt),\n\t\t\tru_nswap: Number(ru.ruNswap),\n\t\t\tru_inblock: Number(ru.ruInblock),\n\t\t\tru_oublock: Number(ru.ruOublock),\n\t\t\tru_msgsnd: Number(ru.ruMsgsnd),\n\t\t\tru_msgrcv: Number(ru.ruMsgrcv),\n\t\t\tru_nsignals: Number(ru.ruNsignals),\n\t\t\tru_nvcsw: Number(ru.ruNvcsw),\n\t\t\tru_nivcsw: Number(ru.ruNivcsw),\n\t\t};\n\t}\n\n\tasync updateSettings({\n\t\tlogLevel,\n\t\tlogTags,\n\t}: WorkerUpdateableSettings<WorkerAppData> = {}): Promise<void> {\n\t\tlogger.debug('updateSettings()');\n\n\t\t// Build the request.\n\t\tconst requestOffset = new FbsWorker.UpdateSettingsRequestT(\n\t\t\tlogLevel,\n\t\t\tlogTags\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.WORKER_UPDATE_SETTINGS,\n\t\t\tFbsRequest.Body.Worker_UpdateSettingsRequest,\n\t\t\trequestOffset\n\t\t);\n\t}\n\n\tasync createWebRtcServer<WebRtcServerAppData extends AppData = AppData>({\n\t\tlistenInfos,\n\t\tappData,\n\t}: WebRtcServerOptions<WebRtcServerAppData>): Promise<\n\t\tWebRtcServer<WebRtcServerAppData>\n\t> {\n\t\tlogger.debug('createWebRtcServer()');\n\n\t\tif (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// Build the request.\n\t\tconst fbsListenInfos: FbsTransport.ListenInfoT[] = [];\n\n\t\tfor (const listenInfo of listenInfos) {\n\t\t\tfbsListenInfos.push(\n\t\t\t\tnew FbsTransport.ListenInfoT(\n\t\t\t\t\tlistenInfo.protocol === 'udp'\n\t\t\t\t\t\t? FbsTransportProtocol.UDP\n\t\t\t\t\t\t: FbsTransportProtocol.TCP,\n\t\t\t\t\tlistenInfo.ip,\n\t\t\t\t\tlistenInfo.announcedAddress ?? listenInfo.announcedIp,\n\t\t\t\t\tBoolean(listenInfo.exposeInternalIp),\n\t\t\t\t\tlistenInfo.port,\n\t\t\t\t\tportRangeToFbs(listenInfo.portRange),\n\t\t\t\t\tsocketFlagsToFbs(listenInfo.flags),\n\t\t\t\t\tlistenInfo.sendBufferSize,\n\t\t\t\t\tlistenInfo.recvBufferSize\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tconst webRtcServerId = utils.generateUUIDv4();\n\n\t\tconst createWebRtcServerRequestOffset =\n\t\t\tnew FbsWorker.CreateWebRtcServerRequestT(\n\t\t\t\twebRtcServerId,\n\t\t\t\tfbsListenInfos\n\t\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.WORKER_CREATE_WEBRTCSERVER,\n\t\t\tFbsRequest.Body.Worker_CreateWebRtcServerRequest,\n\t\t\tcreateWebRtcServerRequestOffset\n\t\t);\n\n\t\tconst webRtcServer: WebRtcServer<WebRtcServerAppData> =\n\t\t\tnew WebRtcServerImpl({\n\t\t\t\tinternal: { webRtcServerId },\n\t\t\t\tchannel: this.#channel,\n\t\t\t\tappData,\n\t\t\t});\n\n\t\tthis.#webRtcServers.add(webRtcServer);\n\t\twebRtcServer.on('@close', () => this.#webRtcServers.delete(webRtcServer));\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newwebrtcserver', webRtcServer);\n\n\t\treturn webRtcServer;\n\t}\n\n\tasync createRouter<RouterAppData extends AppData = AppData>({\n\t\tmediaCodecs,\n\t\tappData,\n\t}: RouterOptions<RouterAppData> = {}): Promise<Router<RouterAppData>> {\n\t\tlogger.debug('createRouter()');\n\n\t\tif (appData && typeof appData !== 'object') {\n\t\t\tthrow new TypeError('if given, appData must be an object');\n\t\t}\n\n\t\t// Clone given media codecs to not modify input data.\n\t\tconst clonedMediaCodecs = utils.clone<\n\t\t\tRouterRtpCodecCapability[] | undefined\n\t\t>(mediaCodecs);\n\n\t\t// This may throw.\n\t\tconst rtpCapabilities =\n\t\t\tortc.generateRouterRtpCapabilities(clonedMediaCodecs);\n\n\t\tconst routerId = utils.generateUUIDv4();\n\n\t\t// Get flatbuffer builder.\n\t\tconst createRouterRequestOffset = new FbsWorker.CreateRouterRequestT(\n\t\t\trouterId\n\t\t).pack(this.#channel.bufferBuilder);\n\n\t\tawait this.#channel.request(\n\t\t\tFbsRequest.Method.WORKER_CREATE_ROUTER,\n\t\t\tFbsRequest.Body.Worker_CreateRouterRequest,\n\t\t\tcreateRouterRequestOffset\n\t\t);\n\n\t\tconst data = { rtpCapabilities };\n\t\tconst router: Router<RouterAppData> = new RouterImpl({\n\t\t\tinternal: {\n\t\t\t\trouterId,\n\t\t\t},\n\t\t\tdata,\n\t\t\tchannel: this.#channel,\n\t\t\tappData,\n\t\t});\n\n\t\tthis.#routers.add(router);\n\t\trouter.on('@close', () => this.#routers.delete(router));\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('newrouter', router);\n\n\t\treturn router;\n\t}\n\n\tprivate workerDied(error: Error): void {\n\t\tif (this.#closed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlogger.debug(`workerDied() [error:${error.toString()}]`);\n\n\t\tthis.#closed = true;\n\t\tthis.#subprocessClosed = true;\n\t\tthis.#died = true;\n\n\t\t// Close the Channel instance.\n\t\tthis.#channel.close();\n\n\t\t// Close every Router.\n\t\tfor (const router of this.#routers) {\n\t\t\trouter.workerClosed();\n\t\t}\n\t\tthis.#routers.clear();\n\n\t\t// Close every WebRtcServer.\n\t\tfor (const webRtcServer of this.#webRtcServers) {\n\t\t\twebRtcServer.workerClosed();\n\t\t}\n\t\tthis.#webRtcServers.clear();\n\n\t\tlogger.debug(`workerDied() | emitting 'died' and 'subprocessclose' events`);\n\n\t\tthis.safeEmit('died', error);\n\t\tthis.safeEmit('subprocessclose');\n\n\t\t// Emit observer event.\n\t\tthis.#observer.safeEmit('close');\n\t}\n\n\tprivate handleListenerError(): void {\n\t\tthis.on('listenererror', (eventName, error) => {\n\t\t\tlogger.error(\n\t\t\t\t`event listener threw an error [eventName:${eventName}]:`,\n\t\t\t\terror\n\t\t\t);\n\t\t});\n\t}\n\n\t// NOTE: Arrow method on purpose.\n\tprivate onSignal = (signal: 'SIGINT' | 'SIGTERM'): void => {\n\t\tlogger.debug(\n\t\t\t`signal received, closing the worker process [pid:${this.#pid}, signal:${signal}]`\n\t\t);\n\n\t\tthis.close();\n\t};\n}\n\nfunction parseWorkerDumpResponse(binary: FbsWorker.DumpResponse): WorkerDump {\n\tconst dump: WorkerDump = {\n\t\tpid: binary.pid(),\n\t\twebRtcServerIds: fbsUtils.parseVector(binary, 'webRtcServerIds'),\n\t\trouterIds: fbsUtils.parseVector(binary, 'routerIds'),\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: fbsUtils.parseVector(\n\t\t\t\tbinary.channelMessageHandlers()!,\n\t\t\t\t'channelRequestHandlers'\n\t\t\t),\n\t\t\tchannelNotificationHandlers: fbsUtils.parseVector(\n\t\t\t\tbinary.channelMessageHandlers()!,\n\t\t\t\t'channelNotificationHandlers'\n\t\t\t),\n\t\t},\n\t};\n\n\tif (binary.liburing()) {\n\t\tdump.liburing = {\n\t\t\tsqeProcessCount: Number(binary.liburing()!.sqeProcessCount()),\n\t\t\tsqeMissCount: Number(binary.liburing()!.sqeMissCount()),\n\t\t\tuserDataMissCount: Number(binary.liburing()!.userDataMissCount()),\n\t\t};\n\t}\n\n\treturn dump;\n}\n\nfunction getDefaultWorkerBin(): string {\n\t// If MEDIASOUP_WORKER_BIN env is given, use it as worker binary.\n\tif (process.env['MEDIASOUP_WORKER_BIN']) {\n\t\tlogger.debug(\n\t\t\t`getDefaultWorkerBin() | using MEDIASOUP_WORKER_BIN environment variable: ${process.env['MEDIASOUP_WORKER_BIN']}`\n\t\t);\n\n\t\treturn process.env['MEDIASOUP_WORKER_BIN'];\n\t}\n\n\t// Obtain the path of the mediasoup module.\n\tlet mediasoupModulePath: string | undefined;\n\n\ttry {\n\t\t// NOTE: This will throw `MODULE_NOT_FOUND` if mediasoup is installed\n\t\t// globally.\n\t\tmediasoupModulePath = require.resolve('mediasoup');\n\n\t\t// NOTE: Returned path will include 'node/lib/index.js' since that's the\n\t\t// main entry point in package.json, so remove it.\n\t\tmediasoupModulePath = path.join(\n\t\t\tpath.dirname(mediasoupModulePath),\n\t\t\t'..',\n\t\t\t'..'\n\t\t);\n\t} catch (error) {\n\t\tlogger.warn(\n\t\t\t`getDefaultWorkerBin() | require.resolve('mediasoup') failed, using __dirname: ${error}`\n\t\t);\n\n\t\t// mediasoup module path is two folders above this file.\n\t\tmediasoupModulePath = path.join(__dirname, '..', '..');\n\t}\n\n\t// If env MEDIASOUP_BUILDTYPE is 'Debug' use the Debug binary. Otherwise use\n\t// the Release binary.\n\tconst buildType: 'Release' | 'Debug' =\n\t\tprocess.env['MEDIASOUP_BUILDTYPE'] === 'Debug' ? 'Debug' : 'Release';\n\n\tconst defaultWorkerBinPath = path.join(\n\t\tmediasoupModulePath,\n\t\t'worker',\n\t\t'out',\n\t\tbuildType,\n\t\t'mediasoup-worker'\n\t);\n\n\tlogger.debug(\n\t\t`getDefaultWorkerBin() | detected worker binary path: ${defaultWorkerBinPath}`\n\t);\n\n\treturn defaultWorkerBinPath;\n}\n"
  },
  {
    "path": "node/src/WorkerTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes';\nimport type { Router, RouterOptions } from './RouterTypes';\nimport type { AppData } from './types';\n\nexport type WorkerLogLevel = 'debug' | 'warn' | 'error' | 'none';\n\nexport type WorkerLogTag =\n\t| 'info'\n\t| 'ice'\n\t| 'dtls'\n\t| 'rtp'\n\t| 'srtp'\n\t| 'rtcp'\n\t| 'rtx'\n\t| 'bwe'\n\t| 'score'\n\t| 'simulcast'\n\t| 'svc'\n\t| 'sctp'\n\t| 'message';\n\nexport type WorkerSettings<WorkerAppData extends AppData = AppData> = {\n\t/**\n\t * Logging level for logs generated by the media worker subprocesses (check\n\t * the Debugging documentation). Valid values are 'debug', 'warn', 'error' and\n\t * 'none'. Default 'error'.\n\t */\n\tlogLevel?: WorkerLogLevel;\n\n\t/**\n\t * Log tags for debugging. Check the meaning of each available tag in the\n\t * Debugging documentation.\n\t */\n\tlogTags?: WorkerLogTag[];\n\n\t/**\n\t * Minimun RTC port for ICE, DTLS, RTP, etc. Default 10000.\n\t *\n\t * @deprecated Use |portRange| in TransportListenInfo object instead.\n\t */\n\trtcMinPort?: number;\n\n\t/**\n\t * Maximum RTC port for ICE, DTLS, RTP, etc. Default 59999.\n\t *\n\t * @deprecated Use |portRange| in TransportListenInfo object instead.\n\t */\n\trtcMaxPort?: number;\n\n\t/**\n\t * Absolute path to the DTLS public certificate file in PEM format. If unset,\n\t * a certificate is dynamically created.\n\t */\n\tdtlsCertificateFile?: string;\n\n\t/**\n\t * Absolute path to the DTLS certificate private key file in PEM format. If\n\t * unset, a certificate is dynamically created.\n\t */\n\tdtlsPrivateKeyFile?: string;\n\n\t/**\n\t * Absolute path to the mediasoup-worker binary. If given it overrides the\n\t * default location of the binary and the MEDIASOUP_WORKER_BIN environment\n\t * variable.\n\t */\n\tworkerBin?: string;\n\n\t/**\n\t * Field trials for libwebrtc.\n\t *\n\t * @remarks For advanced users only. An invalid value will make the worker\n\t * crash. Default value is \"WebRTC-Bwe-AlrLimitedBackoff/Enabled/\".\n\t */\n\tlibwebrtcFieldTrials?: string;\n\n\t/**\n\t * Disable liburing (io_uring) despite it's supported in current host.\n\t */\n\tdisableLiburing?: boolean;\n\n\t/**\n\t * Use the mediasoup built-in SCTP stack instead usrsctp.\n\t */\n\tuseBuiltInSctpStack?: boolean;\n\n\t/**\n\t * Custom application data.\n\t */\n\tappData?: WorkerAppData;\n};\n\nexport type WorkerUpdateableSettings<T extends AppData = AppData> = Pick<\n\tWorkerSettings<T>,\n\t'logLevel' | 'logTags'\n>;\n\n/**\n * An object with the fields of the uv_rusage_t struct.\n *\n * - http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t\n * - https://linux.die.net/man/2/getrusage\n */\nexport type WorkerResourceUsage = {\n\t/**\n\t * User CPU time used (in ms).\n\t */\n\tru_utime: number;\n\n\t/**\n\t * System CPU time used (in ms).\n\t */\n\tru_stime: number;\n\n\t/**\n\t * Maximum resident set size.\n\t */\n\tru_maxrss: number;\n\n\t/**\n\t * Integral shared memory size.\n\t */\n\tru_ixrss: number;\n\n\t/**\n\t * Integral unshared data size.\n\t */\n\tru_idrss: number;\n\n\t/**\n\t * Integral unshared stack size.\n\t */\n\tru_isrss: number;\n\n\t/**\n\t * Page reclaims (soft page faults).\n\t */\n\tru_minflt: number;\n\n\t/**\n\t * Page faults (hard page faults).\n\t */\n\tru_majflt: number;\n\n\t/**\n\t * Swaps.\n\t */\n\tru_nswap: number;\n\n\t/**\n\t * Block input operations.\n\t */\n\tru_inblock: number;\n\n\t/**\n\t * Block output operations.\n\t */\n\tru_oublock: number;\n\n\t/**\n\t * IPC messages sent.\n\t */\n\tru_msgsnd: number;\n\n\t/**\n\t * IPC messages received.\n\t */\n\tru_msgrcv: number;\n\n\t/**\n\t * Signals received.\n\t */\n\tru_nsignals: number;\n\n\t/**\n\t * Voluntary context switches.\n\t */\n\tru_nvcsw: number;\n\n\t/**\n\t * Involuntary context switches.\n\t */\n\tru_nivcsw: number;\n};\n\nexport type WorkerDump = {\n\tpid: number;\n\twebRtcServerIds: string[];\n\trouterIds: string[];\n\tchannelMessageHandlers: {\n\t\tchannelRequestHandlers: string[];\n\t\tchannelNotificationHandlers: string[];\n\t};\n\tliburing?: {\n\t\tsqeProcessCount: number;\n\t\tsqeMissCount: number;\n\t\tuserDataMissCount: number;\n\t};\n};\n\nexport type WorkerEvents = {\n\tdied: [Error];\n\tsubprocessclose: [];\n\t// Private events.\n\t'@success': [];\n\t'@failure': [Error];\n};\n\nexport type WorkerObserver = EnhancedEventEmitter<WorkerObserverEvents>;\n\nexport type WorkerObserverEvents = {\n\tclose: [];\n\tnewwebrtcserver: [WebRtcServer];\n\tnewrouter: [Router];\n};\n\nexport interface Worker<\n\tWorkerAppData extends AppData = AppData,\n> extends EnhancedEventEmitter<WorkerEvents> {\n\t/**\n\t * Worker process identifier (PID).\n\t */\n\tget pid(): number;\n\n\t/**\n\t * Whether the Worker is closed.\n\t */\n\tget closed(): boolean;\n\n\t/**\n\t * Whether the Worker died.\n\t */\n\tget died(): boolean;\n\n\t/**\n\t * Whether the Worker subprocess is closed.\n\t */\n\tget subprocessClosed(): boolean;\n\n\t/**\n\t * App custom data.\n\t */\n\tget appData(): WorkerAppData;\n\n\t/**\n\t * App custom data setter.\n\t */\n\tset appData(appData: WorkerAppData);\n\n\t/**\n\t * Observer.\n\t */\n\tget observer(): WorkerObserver;\n\n\t/**\n\t * Close the Worker.\n\t */\n\tclose(): void;\n\n\t/**\n\t * Dump Worker.\n\t */\n\tdump(): Promise<WorkerDump>;\n\n\t/**\n\t * Get mediasoup-worker process resource usage.\n\t */\n\tgetResourceUsage(): Promise<WorkerResourceUsage>;\n\n\t/**\n\t * Update settings.\n\t */\n\tupdateSettings(\n\t\toptions?: WorkerUpdateableSettings<WorkerAppData>\n\t): Promise<void>;\n\n\t/**\n\t * Create a WebRtcServer.\n\t */\n\tcreateWebRtcServer<WebRtcServerAppData extends AppData = AppData>(\n\t\toptions: WebRtcServerOptions<WebRtcServerAppData>\n\t): Promise<WebRtcServer<WebRtcServerAppData>>;\n\n\t/**\n\t * Create a Router.\n\t */\n\tcreateRouter<RouterAppData extends AppData = AppData>(\n\t\toptions?: RouterOptions<RouterAppData>\n\t): Promise<Router<RouterAppData>>;\n}\n"
  },
  {
    "path": "node/src/enhancedEvents.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { EventEmitter, once } from 'node:events';\n\ntype Events = Record<string, any[]>;\n\nexport class EnhancedEventEmitter<\n\tE extends Events = Events,\n\tBuiltInEvents extends Events = {\n\t\tlistenererror: [keyof E, Error];\n\t},\n\tE2 extends Events = E & BuiltInEvents,\n> extends EventEmitter {\n\tconstructor() {\n\t\tsuper();\n\n\t\tthis.setMaxListeners(Infinity);\n\t}\n\n\toverride emit<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\t...args: E2[K]\n\t): boolean {\n\t\treturn super.emit(eventName, ...args);\n\t}\n\n\t/**\n\t * Special addition to the EventEmitter API.\n\t */\n\tsafeEmit<K extends keyof E2 & string>(eventName: K, ...args: E2[K]): boolean {\n\t\ttry {\n\t\t\treturn super.emit(eventName, ...args);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tsuper.emit('listenererror', eventName, error);\n\t\t\t} catch (error2) {\n\t\t\t\t// Ignore it.\n\t\t\t}\n\n\t\t\treturn Boolean(super.listenerCount(eventName));\n\t\t}\n\t}\n\n\toverride on<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.on(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride off<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.off(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride addListener<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.on(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride prependListener<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.prependListener(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride once<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.once(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride prependOnceListener<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.prependOnceListener(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride removeListener<K extends keyof E2 & string>(\n\t\teventName: K,\n\t\tlistener: (...args: E2[K]) => void\n\t): this {\n\t\tsuper.off(eventName, listener as (...args: any[]) => void);\n\n\t\treturn this;\n\t}\n\n\toverride removeAllListeners<K extends keyof E2 & string>(\n\t\teventName?: K\n\t): this {\n\t\tsuper.removeAllListeners(eventName);\n\n\t\treturn this;\n\t}\n\n\toverride listenerCount<K extends keyof E2 & string>(eventName: K): number {\n\t\treturn super.listenerCount(eventName);\n\t}\n\n\toverride listeners<K extends keyof E2 & string>(\n\t\teventName: K\n\t): ((...args: any[]) => void)[] {\n\t\treturn super.listeners(eventName);\n\t}\n\n\toverride rawListeners<K extends keyof E2 & string>(\n\t\teventName: K\n\t): ((...args: any[]) => void)[] {\n\t\treturn super.rawListeners(eventName);\n\t}\n}\n\n/**\n * TypeScript version of events.once():\n *   https://nodejs.org/api/events.html#eventsonceemitter-name-options\n *\n * Usage example:\n * ```ts\n * await enhancedOnce<ConsumerEvents>(videoConsumer, 'producerpause');\n * ```\n */\nexport async function enhancedOnce<E extends Events = Events>(\n\temmiter: EnhancedEventEmitter<E>,\n\teventName: keyof E & string,\n\toptions?: any\n): Promise<any[]> {\n\treturn once(emmiter, eventName, options);\n}\n"
  },
  {
    "path": "node/src/errors.ts",
    "content": "/**\n * Error indicating not support for something.\n */\nexport class UnsupportedError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = 'UnsupportedError';\n\n\t\tif (Error.hasOwnProperty('captureStackTrace')) {\n\t\t\t// Just in V8.\n\t\t\tError.captureStackTrace(this, UnsupportedError);\n\t\t} else {\n\t\t\tthis.stack = new Error(message).stack;\n\t\t}\n\t}\n}\n\n/**\n * Error produced when calling a method in an invalid state.\n */\nexport class InvalidStateError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\n\t\tthis.name = 'InvalidStateError';\n\n\t\tif (Error.hasOwnProperty('captureStackTrace')) {\n\t\t\t// Just in V8.\n\t\t\tError.captureStackTrace(this, InvalidStateError);\n\t\t} else {\n\t\t\tthis.stack = new Error(message).stack;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "node/src/extras.ts",
    "content": "export { EnhancedEventEmitter, enhancedOnce } from './enhancedEvents';\n"
  },
  {
    "path": "node/src/fbsUtils.ts",
    "content": "/**\n * Parse flatbuffers vector into an array of the given T.\n */\nexport function parseVector<T>(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tparseFn?: (binary2: any) => T\n): T[] {\n\tconst array: T[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tif (parseFn) {\n\t\t\tarray.push(parseFn(binary[methodName](i)));\n\t\t} else {\n\t\t\tarray.push(binary[methodName](i) as T);\n\t\t}\n\t}\n\n\treturn array;\n}\n\n/**\n * Parse flatbuffers vector of StringString into the corresponding array.\n */\nexport function parseStringStringVector(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string\n): { key: string; value: string }[] {\n\tconst array: { key: string; value: string }[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tconst kv = binary[methodName](i)!;\n\n\t\tarray.push({ key: kv.key(), value: kv.value() });\n\t}\n\n\treturn array;\n}\n\n/**\n * Parse flatbuffers vector of StringUint8 into the corresponding array.\n */\nexport function parseStringUint8Vector(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string\n): { key: string; value: number }[] {\n\tconst array: { key: string; value: number }[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tconst kv = binary[methodName](i)!;\n\n\t\tarray.push({ key: kv.key(), value: kv.value() });\n\t}\n\n\treturn array;\n}\n\n/**\n * Parse flatbuffers vector of Uint16String into the corresponding array.\n */\nexport function parseUint16StringVector(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string\n): { key: number; value: string }[] {\n\tconst array: { key: number; value: string }[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tconst kv = binary[methodName](i)!;\n\n\t\tarray.push({ key: kv.key(), value: kv.value() });\n\t}\n\n\treturn array;\n}\n\n/**\n * Parse flatbuffers vector of Uint32String into the corresponding array.\n */\nexport function parseUint32StringVector(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string\n): { key: number; value: string }[] {\n\tconst array: { key: number; value: string }[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tconst kv = binary[methodName](i)!;\n\n\t\tarray.push({ key: kv.key(), value: kv.value() });\n\t}\n\n\treturn array;\n}\n\n/**\n * Parse flatbuffers vector of StringStringArray into the corresponding array.\n */\nexport function parseStringStringArrayVector(\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tbinary: any,\n\tmethodName: string\n): { key: string; values: string[] }[] {\n\tconst array: { key: string; values: string[] }[] = [];\n\n\tfor (let i = 0; i < binary[`${methodName}Length`](); ++i) {\n\t\tconst kv = binary[methodName](i)!;\n\t\tconst values: string[] = [];\n\n\t\tfor (let i2 = 0; i2 < kv.valuesLength(); ++i2) {\n\t\t\tvalues.push(kv.values(i2)! as string);\n\t\t}\n\n\t\tarray.push({ key: kv.key(), values });\n\t}\n\n\treturn array;\n}\n"
  },
  {
    "path": "node/src/index.ts",
    "content": "import { Logger, LoggerEmitter } from './Logger';\nimport { EnhancedEventEmitter } from './enhancedEvents';\nimport type {\n\tObserver,\n\tObserverEvents,\n\tLogEventListeners,\n\tIndex,\n} from './indexTypes';\nimport type { Worker, WorkerSettings } from './WorkerTypes';\nimport { WorkerImpl, defaultWorkerBin as workerBin } from './Worker';\nimport { supportedRtpCapabilities } from './supportedRtpCapabilities';\nimport type { RouterRtpCapabilities } from './rtpParametersTypes';\nimport { parseScalabilityMode } from './scalabilityModesUtils';\nimport type { AppData } from './types';\nimport * as utils from './utils';\n\n/**\n * Expose all types.\n */\nexport type * as types from './types';\n\n/**\n * Expose mediasoup version.\n */\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nexport const version: string = require('../../package.json').version;\n\nconst observer: Observer = new EnhancedEventEmitter<ObserverEvents>();\n\n/**\n * Observer.\n */\nexport { observer };\n\n/**\n * Absolute path of the mediasoup-worker binary.\n */\nexport { defaultWorkerBin as workerBin } from './Worker';\n\nconst logger = new Logger();\n\n/**\n * Set event listeners for mediasoup generated logs. If called with no arguments\n * then no events will be emitted.\n *\n * @example\n * ```ts\n * mediasoup.setLogEventListeners({\n *   ondebug: undefined,\n *   onwarn: (namespace: string, log: string) => {\n *     MyEnterpriseLogger.warn(`${namespace} ${log}`);\n *   },\n *   onerror: (namespace: string, log: string, error?: Error) => {\n *     if (error) {\n *       MyEnterpriseLogger.error(`${namespace} ${log}: ${error}`);\n *     } else {\n *       MyEnterpriseLogger.error(`${namespace} ${log}`);\n *     }\n *   }\n * });\n * ```\n */\nexport function setLogEventListeners(listeners?: LogEventListeners): void {\n\tlogger.debug('setLogEventListeners()');\n\n\tlet debugLogEmitter: LoggerEmitter | undefined;\n\tlet warnLogEmitter: LoggerEmitter | undefined;\n\tlet errorLogEmitter: LoggerEmitter | undefined;\n\n\tif (listeners?.ondebug) {\n\t\tdebugLogEmitter = new EnhancedEventEmitter();\n\n\t\tdebugLogEmitter.on('debuglog', listeners.ondebug);\n\t}\n\n\tif (listeners?.onwarn) {\n\t\twarnLogEmitter = new EnhancedEventEmitter();\n\n\t\twarnLogEmitter.on('warnlog', listeners.onwarn);\n\t}\n\n\tif (listeners?.onerror) {\n\t\terrorLogEmitter = new EnhancedEventEmitter();\n\n\t\terrorLogEmitter.on('errorlog', listeners.onerror);\n\t}\n\n\tLogger.setEmitters(debugLogEmitter, warnLogEmitter, errorLogEmitter);\n}\n\n/**\n * Create a Worker.\n */\nexport async function createWorker<WorkerAppData extends AppData = AppData>({\n\tlogLevel = 'error',\n\tlogTags,\n\trtcMinPort = 10000,\n\trtcMaxPort = 59999,\n\tdtlsCertificateFile,\n\tdtlsPrivateKeyFile,\n\t// eslint-disable-next-line no-shadow\n\tworkerBin,\n\tlibwebrtcFieldTrials,\n\tdisableLiburing = false,\n\tuseBuiltInSctpStack = false,\n\tappData,\n}: WorkerSettings<WorkerAppData> = {}): Promise<Worker<WorkerAppData>> {\n\tlogger.debug('createWorker()');\n\n\tif (appData && typeof appData !== 'object') {\n\t\tthrow new TypeError('if given, appData must be an object');\n\t}\n\n\tconst worker: Worker<WorkerAppData> = new WorkerImpl({\n\t\tlogLevel,\n\t\tlogTags,\n\t\trtcMinPort,\n\t\trtcMaxPort,\n\t\tdtlsCertificateFile,\n\t\tdtlsPrivateKeyFile,\n\t\tworkerBin,\n\t\tlibwebrtcFieldTrials,\n\t\tdisableLiburing,\n\t\tuseBuiltInSctpStack,\n\t\tappData,\n\t});\n\n\treturn new Promise((resolve, reject) => {\n\t\tworker.on('@success', () => {\n\t\t\t// Emit observer event.\n\t\t\tobserver.safeEmit('newworker', worker);\n\n\t\t\tresolve(worker);\n\t\t});\n\n\t\tworker.on('@failure', reject);\n\t});\n}\n\n/**\n * Get a cloned copy of the mediasoup supported RTP capabilities.\n */\nexport function getSupportedRtpCapabilities(): RouterRtpCapabilities {\n\treturn utils.clone<RouterRtpCapabilities>(supportedRtpCapabilities);\n}\n\n/**\n * Expose parseScalabilityMode() function.\n */\nexport { parseScalabilityMode } from './scalabilityModesUtils';\n\n/**\n * Expose all ORTC functions.\n */\nexport * as ortc from './ortc';\n\n/**\n * Expose extras module.\n */\nexport * as extras from './extras';\n\n// NOTE: This constant of type Index is created just to check at TypeScript\n// level that everything exported here (all but TS types) matches the Index\n// interface exposed by indexTypes.ts.\n//\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst indexImpl: Index = {\n\tversion,\n\tobserver,\n\tworkerBin,\n\tsetLogEventListeners,\n\tcreateWorker,\n\tgetSupportedRtpCapabilities,\n\tparseScalabilityMode,\n};\n"
  },
  {
    "path": "node/src/indexTypes.ts",
    "content": "import type { EnhancedEventEmitter } from './enhancedEvents';\nimport type { Worker, WorkerSettings } from './WorkerTypes';\nimport type { RouterRtpCapabilities } from './rtpParametersTypes';\nimport type { parseScalabilityMode } from './scalabilityModesUtils';\nimport type { AppData } from './types';\n\nexport type ObserverEvents = {\n\tnewworker: [Worker];\n};\n\nexport type Observer = EnhancedEventEmitter<ObserverEvents>;\n\n/**\n * Event listeners for mediasoup generated logs.\n */\nexport type LogEventListeners = {\n\tondebug?: (namespace: string, log: string) => void;\n\tonwarn?: (namespace: string, log: string) => void;\n\tonerror?: (namespace: string, log: string, error?: Error) => void;\n};\n\nexport interface Index {\n\tversion: string;\n\tobserver: EnhancedEventEmitter<ObserverEvents>;\n\tworkerBin: string;\n\tsetLogEventListeners: (listeners?: LogEventListeners) => void;\n\tcreateWorker: <WorkerAppData extends AppData = AppData>(\n\t\toptions?: WorkerSettings<WorkerAppData>\n\t) => Promise<Worker<WorkerAppData>>;\n\tgetSupportedRtpCapabilities: () => RouterRtpCapabilities;\n\tparseScalabilityMode: typeof parseScalabilityMode;\n}\n"
  },
  {
    "path": "node/src/ortc.ts",
    "content": "import * as h264 from 'h264-profile-level-id';\nimport type * as flatbuffers from 'flatbuffers';\nimport { supportedRtpCapabilities } from './supportedRtpCapabilities';\nimport { parseScalabilityMode } from './scalabilityModesUtils';\nimport type {\n\tRtpCapabilities,\n\tRouterRtpCapabilities,\n\tMediaKind,\n\tRtpCodecCapability,\n\tRouterRtpCodecCapability,\n\tRtpHeaderExtension,\n\tRtpParameters,\n\tRtpCodecParameters,\n\tRtcpFeedback,\n\tRtpEncodingParameters,\n\tRtpHeaderExtensionParameters,\n\tRtcpParameters,\n} from './rtpParametersTypes';\nimport type { SctpStreamParameters } from './sctpParametersTypes';\nimport * as utils from './utils';\nimport { UnsupportedError } from './errors';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\n\nexport type RtpCodecsEncodingsMapping = {\n\tcodecs: {\n\t\tpayloadType: number;\n\t\tmappedPayloadType: number;\n\t}[];\n\tencodings: {\n\t\tssrc?: number;\n\t\trid?: string;\n\t\tscalabilityMode?: string;\n\t\tmappedSsrc: number;\n\t}[];\n};\n\nconst DynamicPayloadTypes = [\n\t100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,\n\t115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 96, 97, 98,\n\t99,\n];\n\n// TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n// extension.\n//\n// This is an object where we store some objects we may later need.\ntype Cache = {\n\tdependencyDescriptorHeaderExtensionParametersForPipeConsumer?: RtpHeaderExtensionParameters;\n};\n\nconst cache: Cache = {\n\tdependencyDescriptorHeaderExtensionParametersForPipeConsumer: undefined,\n};\n\n/**\n * Validates RtpCapabilities. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nexport function validateAndNormalizeRtpCapabilities(\n\tcaps: RtpCapabilities | RouterRtpCapabilities\n): void {\n\tif (typeof caps !== 'object') {\n\t\tthrow new TypeError('caps is not an object');\n\t}\n\n\t// codecs is optional. If unset, fill with an empty array.\n\tif (caps.codecs && !Array.isArray(caps.codecs)) {\n\t\tthrow new TypeError('caps.codecs is not an array');\n\t} else if (!caps.codecs) {\n\t\tcaps.codecs = [];\n\t}\n\n\tfor (const codec of caps.codecs) {\n\t\tvalidateAndNormalizeRtpCodecCapability(codec);\n\t}\n\n\t// headerExtensions is optional. If unset, fill with an empty array.\n\tif (caps.headerExtensions && !Array.isArray(caps.headerExtensions)) {\n\t\tthrow new TypeError('caps.headerExtensions is not an array');\n\t} else if (!caps.headerExtensions) {\n\t\tcaps.headerExtensions = [];\n\t}\n\n\tfor (const ext of caps.headerExtensions) {\n\t\tvalidateAndNormalizeRtpHeaderExtension(ext);\n\t}\n}\n\n/**\n * Validates RtpParameters. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nexport function validateAndNormalizeRtpParameters(params: RtpParameters): void {\n\tif (typeof params !== 'object') {\n\t\tthrow new TypeError('params is not an object');\n\t}\n\n\t// mid is optional.\n\tif (params.mid && typeof params.mid !== 'string') {\n\t\tthrow new TypeError('params.mid is not a string');\n\t}\n\n\t// codecs is mandatory.\n\tif (!Array.isArray(params.codecs)) {\n\t\tthrow new TypeError('missing params.codecs');\n\t}\n\n\tfor (const codec of params.codecs) {\n\t\tvalidateAndNormalizeRtpCodecParameters(codec);\n\t}\n\n\t// headerExtensions is optional. If unset, fill with an empty array.\n\tif (params.headerExtensions && !Array.isArray(params.headerExtensions)) {\n\t\tthrow new TypeError('params.headerExtensions is not an array');\n\t} else if (!params.headerExtensions) {\n\t\tparams.headerExtensions = [];\n\t}\n\n\tfor (const ext of params.headerExtensions) {\n\t\tvalidateAndNormalizeRtpHeaderExtensionParameters(ext);\n\t}\n\n\t// encodings is optional. If unset, fill with an empty array.\n\tif (params.encodings && !Array.isArray(params.encodings)) {\n\t\tthrow new TypeError('params.encodings is not an array');\n\t} else if (!params.encodings) {\n\t\tparams.encodings = [];\n\t}\n\n\tfor (const encoding of params.encodings) {\n\t\tvalidateAndNormalizeRtpEncodingParameters(encoding);\n\t}\n\n\t// rtcp is optional. If unset, fill with an empty object.\n\tif (params.rtcp && typeof params.rtcp !== 'object') {\n\t\tthrow new TypeError('params.rtcp is not an object');\n\t} else if (!params.rtcp) {\n\t\tparams.rtcp = {};\n\t}\n\n\t// msid is optional.\n\tif (params.msid && typeof params.msid !== 'string') {\n\t\tthrow new TypeError('params.msid is not a string');\n\t}\n\n\tvalidateAndNormalizeRtcpParameters(params.rtcp);\n}\n\n/**\n * Validates SctpStreamParameters. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nexport function validateAndNormalizeSctpStreamParameters(\n\tparams: SctpStreamParameters\n): void {\n\tif (typeof params !== 'object') {\n\t\tthrow new TypeError('params is not an object');\n\t}\n\n\t// streamId is mandatory.\n\tif (typeof params.streamId !== 'number') {\n\t\tthrow new TypeError('missing params.streamId');\n\t}\n\n\t// ordered is optional.\n\tlet orderedGiven = false;\n\n\tif (typeof params.ordered === 'boolean') {\n\t\torderedGiven = true;\n\t} else {\n\t\tparams.ordered = true;\n\t}\n\n\t// maxPacketLifeTime is optional.\n\tif (\n\t\tparams.maxPacketLifeTime &&\n\t\ttypeof params.maxPacketLifeTime !== 'number'\n\t) {\n\t\tthrow new TypeError('invalid params.maxPacketLifeTime');\n\t}\n\n\t// maxRetransmits is optional.\n\tif (params.maxRetransmits && typeof params.maxRetransmits !== 'number') {\n\t\tthrow new TypeError('invalid params.maxRetransmits');\n\t}\n\n\tif (params.maxPacketLifeTime && params.maxRetransmits) {\n\t\tthrow new TypeError(\n\t\t\t'cannot provide both maxPacketLifeTime and maxRetransmits'\n\t\t);\n\t}\n\n\tif (\n\t\torderedGiven &&\n\t\tparams.ordered &&\n\t\t(params.maxPacketLifeTime || params.maxRetransmits)\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'cannot be ordered with maxPacketLifeTime or maxRetransmits'\n\t\t);\n\t} else if (\n\t\t!orderedGiven &&\n\t\t(params.maxPacketLifeTime || params.maxRetransmits)\n\t) {\n\t\tparams.ordered = false;\n\t}\n}\n\n/**\n * Generate RTP capabilities for the Router based on the given media codecs and\n * mediasoup supported RTP capabilities.\n */\nexport function generateRouterRtpCapabilities(\n\tmediaCodecs: RouterRtpCodecCapability[] = []\n): RtpCapabilities {\n\t// Normalize supported RTP capabilities.\n\tvalidateAndNormalizeRtpCapabilities(supportedRtpCapabilities);\n\n\tif (!Array.isArray(mediaCodecs)) {\n\t\tthrow new TypeError('mediaCodecs must be an Array');\n\t}\n\n\tconst clonedSupportedRtpCapabilities = utils.clone<RouterRtpCapabilities>(\n\t\tsupportedRtpCapabilities\n\t);\n\tconst dynamicPayloadTypes = utils.clone<number[]>(DynamicPayloadTypes);\n\tconst caps: RtpCapabilities = {\n\t\tcodecs: [],\n\t\theaderExtensions: clonedSupportedRtpCapabilities.headerExtensions,\n\t};\n\n\tfor (const mediaCodec of mediaCodecs) {\n\t\t// This may throw.\n\t\tvalidateAndNormalizeRtpCodecCapability(mediaCodec);\n\n\t\tconst matchedSupportedCodec = clonedSupportedRtpCapabilities.codecs!.find(\n\t\t\tsupportedCodec =>\n\t\t\t\tmatchCodecs(mediaCodec, supportedCodec, { strict: false })\n\t\t);\n\n\t\tif (!matchedSupportedCodec) {\n\t\t\tthrow new UnsupportedError(\n\t\t\t\t`media codec not supported [mimeType:${mediaCodec.mimeType}]`\n\t\t\t);\n\t\t}\n\n\t\t// Clone the supported codec.\n\t\tconst codec = utils.clone<RouterRtpCodecCapability>(matchedSupportedCodec);\n\n\t\t// If the given media codec has preferredPayloadType, keep it.\n\t\tif (typeof mediaCodec.preferredPayloadType === 'number') {\n\t\t\tcodec.preferredPayloadType = mediaCodec.preferredPayloadType;\n\n\t\t\t// Also remove the pt from the list of available dynamic values.\n\t\t\tconst idx = dynamicPayloadTypes.indexOf(codec.preferredPayloadType);\n\n\t\t\tif (idx > -1) {\n\t\t\t\tdynamicPayloadTypes.splice(idx, 1);\n\t\t\t}\n\t\t}\n\t\t// Otherwise if the supported codec has preferredPayloadType, use it.\n\t\telse if (typeof codec.preferredPayloadType === 'number') {\n\t\t\t// No need to remove it from the list since it's not a dynamic value.\n\t\t}\n\t\t// Otherwise choose a dynamic one.\n\t\telse {\n\t\t\t// Take the first available pt and remove it from the list.\n\t\t\tconst pt = dynamicPayloadTypes.shift();\n\n\t\t\tif (!pt) {\n\t\t\t\tthrow new Error('cannot allocate more dynamic codec payload types');\n\t\t\t}\n\n\t\t\tcodec.preferredPayloadType = pt;\n\t\t}\n\n\t\t// Ensure there is not duplicated preferredPayloadType values.\n\t\tif (\n\t\t\tcaps.codecs!.some(\n\t\t\t\tc => c.preferredPayloadType === codec.preferredPayloadType\n\t\t\t)\n\t\t) {\n\t\t\tthrow new TypeError('duplicated codec.preferredPayloadType');\n\t\t}\n\n\t\t// Merge the media codec parameters.\n\t\tcodec.parameters = { ...codec.parameters, ...mediaCodec.parameters };\n\n\t\t// Append to the codec list.\n\t\tcaps.codecs!.push(codec as RtpCodecCapability);\n\n\t\t// Add a RTX video codec if video.\n\t\tif (codec.kind === 'video') {\n\t\t\t// Take the first available pt and remove it from the list.\n\t\t\tconst pt = dynamicPayloadTypes.shift();\n\n\t\t\tif (!pt) {\n\t\t\t\tthrow new Error('cannot allocate more dynamic codec payload types');\n\t\t\t}\n\n\t\t\tconst rtxCodec: RtpCodecCapability = {\n\t\t\t\tkind: codec.kind,\n\t\t\t\tmimeType: `${codec.kind}/rtx`,\n\t\t\t\tpreferredPayloadType: pt,\n\t\t\t\tclockRate: codec.clockRate,\n\t\t\t\tparameters: {\n\t\t\t\t\tapt: codec.preferredPayloadType,\n\t\t\t\t},\n\t\t\t\trtcpFeedback: [],\n\t\t\t};\n\n\t\t\t// Append to the codec list.\n\t\t\tcaps.codecs!.push(rtxCodec);\n\t\t}\n\t}\n\n\t// TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n\t// extension.\n\t//\n\t// We need to create and store this Dependency-Descriptor header extension to\n\t// leter be used by `getPipeConsumerRtpParameters()` function.\n\tconst dependencyDescriptorHeaderExtensionForPipeConsumer:\n\t\t| RtpHeaderExtension\n\t\t| undefined = supportedRtpCapabilities.headerExtensions!.find(\n\t\theaderExtension =>\n\t\t\theaderExtension.uri ===\n\t\t\t\t'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension' &&\n\t\t\theaderExtension.direction !== 'sendrecv'\n\t);\n\n\tif (dependencyDescriptorHeaderExtensionForPipeConsumer) {\n\t\tcache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer = {\n\t\t\turi: dependencyDescriptorHeaderExtensionForPipeConsumer.uri,\n\t\t\tid: dependencyDescriptorHeaderExtensionForPipeConsumer.preferredId,\n\t\t\tencrypt:\n\t\t\t\tdependencyDescriptorHeaderExtensionForPipeConsumer.preferredEncrypt,\n\t\t\tparameters: {},\n\t\t};\n\t}\n\n\treturn caps;\n}\n\n/**\n * Get a mapping of codec payloads and encodings of the given Producer RTP\n * parameters as values expected by the Router.\n *\n * It may throw if invalid or non supported RTP parameters are given.\n */\nexport function getProducerRtpParametersMapping(\n\tparams: RtpParameters,\n\tcaps: RtpCapabilities\n): RtpCodecsEncodingsMapping {\n\tconst rtpMapping: RtpCodecsEncodingsMapping = {\n\t\tcodecs: [],\n\t\tencodings: [],\n\t};\n\n\t// Match parameters media codecs to capabilities media codecs.\n\tconst codecToCapCodec: Map<RtpCodecParameters, RtpCodecCapability> =\n\t\tnew Map();\n\n\tfor (const codec of params.codecs) {\n\t\tif (isRtxCodec(codec)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Search for the same media codec in capabilities.\n\t\tconst matchedCapCodec = caps.codecs!.find(capCodec =>\n\t\t\tmatchCodecs(codec, capCodec, { strict: true, modify: true })\n\t\t);\n\n\t\tif (!matchedCapCodec) {\n\t\t\tthrow new UnsupportedError(\n\t\t\t\t`unsupported codec [mimeType:${codec.mimeType}, payloadType:${codec.payloadType}]`\n\t\t\t);\n\t\t}\n\n\t\tcodecToCapCodec.set(codec, matchedCapCodec);\n\t}\n\n\t// Match parameters RTX codecs to capabilities RTX codecs.\n\tfor (const codec of params.codecs) {\n\t\tif (!isRtxCodec(codec)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Search for the associated media codec.\n\t\tconst associatedMediaCodec = params.codecs.find(\n\t\t\tmediaCodec => mediaCodec.payloadType === codec.parameters!['apt']\n\t\t);\n\n\t\tif (!associatedMediaCodec) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`missing media codec found for RTX PT ${codec.payloadType}`\n\t\t\t);\n\t\t}\n\n\t\tconst capMediaCodec = codecToCapCodec.get(associatedMediaCodec);\n\n\t\t// Ensure that the capabilities media codec has a RTX codec.\n\t\tconst associatedCapRtxCodec = caps.codecs!.find(\n\t\t\tcapCodec =>\n\t\t\t\tisRtxCodec(capCodec) &&\n\t\t\t\tcapCodec.parameters!['apt'] === capMediaCodec!.preferredPayloadType\n\t\t);\n\n\t\tif (!associatedCapRtxCodec) {\n\t\t\tthrow new UnsupportedError(\n\t\t\t\t`no RTX codec for capability codec PT ${\n\t\t\t\t\tcapMediaCodec!.preferredPayloadType\n\t\t\t\t}`\n\t\t\t);\n\t\t}\n\n\t\tcodecToCapCodec.set(codec, associatedCapRtxCodec);\n\t}\n\n\t// Generate codecs mapping.\n\tfor (const [codec, capCodec] of codecToCapCodec) {\n\t\trtpMapping.codecs.push({\n\t\t\tpayloadType: codec.payloadType,\n\t\t\tmappedPayloadType: capCodec.preferredPayloadType,\n\t\t});\n\t}\n\n\t// Generate encodings mapping.\n\tlet mappedSsrc = utils.generateRandomNumber();\n\n\tfor (const encoding of params.encodings!) {\n\t\tconst mappedEncoding = {\n\t\t\tssrc: encoding.ssrc,\n\t\t\trid: encoding.rid,\n\t\t\tscalabilityMode: encoding.scalabilityMode,\n\t\t\tmappedSsrc: mappedSsrc++,\n\t\t};\n\n\t\trtpMapping.encodings.push(mappedEncoding);\n\t}\n\n\treturn rtpMapping;\n}\n\n/**\n * Generate RTP parameters to be internally used by Consumers given the RTP\n * parameters of a Producer and the RTP capabilities of the Router.\n */\nexport function getConsumableRtpParameters(\n\tkind: string,\n\tparams: RtpParameters,\n\tcaps: RtpCapabilities,\n\trtpMapping: RtpCodecsEncodingsMapping\n): RtpParameters {\n\tconst consumableParams: RtpParameters = {\n\t\tcodecs: [],\n\t\theaderExtensions: [],\n\t\tencodings: [],\n\t\trtcp: {},\n\t\tmsid: undefined,\n\t};\n\n\tfor (const codec of params.codecs) {\n\t\tif (isRtxCodec(codec)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst consumableCodecPt = rtpMapping.codecs.find(\n\t\t\tentry => entry.payloadType === codec.payloadType\n\t\t)!.mappedPayloadType;\n\n\t\tconst matchedCapCodec = caps.codecs!.find(\n\t\t\tcapCodec => capCodec.preferredPayloadType === consumableCodecPt\n\t\t)!;\n\n\t\tconst consumableCodec: RtpCodecParameters = {\n\t\t\tmimeType: matchedCapCodec.mimeType,\n\t\t\tpayloadType: matchedCapCodec.preferredPayloadType,\n\t\t\tclockRate: matchedCapCodec.clockRate,\n\t\t\tchannels: matchedCapCodec.channels,\n\t\t\tparameters: codec.parameters, // Keep the Producer codec parameters.\n\t\t\trtcpFeedback: matchedCapCodec.rtcpFeedback,\n\t\t};\n\n\t\tconsumableParams.codecs.push(consumableCodec);\n\n\t\tconst consumableCapRtxCodec = caps.codecs!.find(\n\t\t\tcapRtxCodec =>\n\t\t\t\tisRtxCodec(capRtxCodec) &&\n\t\t\t\tcapRtxCodec.parameters!['apt'] === consumableCodec.payloadType\n\t\t);\n\n\t\tif (consumableCapRtxCodec) {\n\t\t\tconst consumableRtxCodec: RtpCodecParameters = {\n\t\t\t\tmimeType: consumableCapRtxCodec.mimeType,\n\t\t\t\tpayloadType: consumableCapRtxCodec.preferredPayloadType,\n\t\t\t\tclockRate: consumableCapRtxCodec.clockRate,\n\t\t\t\tparameters: consumableCapRtxCodec.parameters,\n\t\t\t\trtcpFeedback: consumableCapRtxCodec.rtcpFeedback,\n\t\t\t};\n\n\t\t\tconsumableParams.codecs.push(consumableRtxCodec);\n\t\t}\n\t}\n\n\tfor (const capExt of caps.headerExtensions!) {\n\t\t// Just take RTP header extension that can be used in Consumers.\n\t\tif (\n\t\t\tcapExt.kind !== kind ||\n\t\t\t(capExt.direction !== 'sendrecv' && capExt.direction !== 'sendonly')\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst consumableExt = {\n\t\t\turi: capExt.uri,\n\t\t\tid: capExt.preferredId,\n\t\t\tencrypt: capExt.preferredEncrypt,\n\t\t\tparameters: {},\n\t\t};\n\n\t\tconsumableParams.headerExtensions!.push(consumableExt);\n\t}\n\n\t// Clone Producer encodings since we'll mangle them.\n\tconst consumableEncodings =\n\t\tutils.clone<RtpEncodingParameters[] | undefined>(params.encodings) ?? [];\n\n\tfor (let i = 0; i < consumableEncodings.length; ++i) {\n\t\tconst consumableEncoding = consumableEncodings[i]!;\n\t\tconst { mappedSsrc } = rtpMapping.encodings[i]!;\n\n\t\t// Remove useless fields.\n\t\tdelete consumableEncoding.rid;\n\t\tdelete consumableEncoding.rtx;\n\t\tdelete consumableEncoding.codecPayloadType;\n\n\t\t// Set the mapped ssrc.\n\t\tconsumableEncoding.ssrc = mappedSsrc;\n\n\t\tconsumableParams.encodings!.push(consumableEncoding);\n\t}\n\n\tconsumableParams.rtcp = {\n\t\tcname: params.rtcp!.cname,\n\t\treducedSize: true,\n\t};\n\n\tconsumableParams.msid = params.msid;\n\n\treturn consumableParams;\n}\n\n/**\n * Check whether the given RTP capabilities can consume the given Producer.\n */\nexport function canConsume(\n\tconsumableParams: RtpParameters,\n\tcaps: RtpCapabilities\n): boolean {\n\t// This may throw.\n\tvalidateAndNormalizeRtpCapabilities(caps);\n\n\tconst matchingCodecs: RtpCodecParameters[] = [];\n\n\tfor (const codec of consumableParams.codecs) {\n\t\tconst matchedCapCodec = caps.codecs!.find(capCodec =>\n\t\t\tmatchCodecs(capCodec, codec, { strict: true })\n\t\t);\n\n\t\tif (!matchedCapCodec) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tmatchingCodecs.push(codec);\n\t}\n\n\t// Ensure there is at least one media codec.\n\tif (matchingCodecs.length === 0 || isRtxCodec(matchingCodecs[0]!)) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/**\n * Generate RTP parameters for a specific Consumer.\n *\n * It reduces encodings to just one and takes into account given RTP\n * capabilities to reduce codecs, codecs' RTCP feedback and header extensions,\n * and also enables or disables RTX.\n */\nexport function getConsumerRtpParameters({\n\tconsumableRtpParameters,\n\tremoteRtpCapabilities,\n\tpipe,\n\tenableRtx,\n}: {\n\tconsumableRtpParameters: RtpParameters;\n\tremoteRtpCapabilities: RtpCapabilities;\n\tpipe: boolean;\n\tenableRtx: boolean;\n}): RtpParameters {\n\tconst consumerParams: RtpParameters = {\n\t\tcodecs: [],\n\t\theaderExtensions: [],\n\t\tencodings: [],\n\t\trtcp: consumableRtpParameters.rtcp,\n\t\tmsid: consumableRtpParameters.msid,\n\t};\n\n\tfor (const capCodec of remoteRtpCapabilities.codecs!) {\n\t\tvalidateAndNormalizeRtpCodecCapability(capCodec);\n\t}\n\n\tconst consumableCodecs =\n\t\tutils.clone<RtpCodecParameters[] | undefined>(\n\t\t\tconsumableRtpParameters.codecs\n\t\t) ?? [];\n\n\tlet rtxSupported = false;\n\n\tfor (const codec of consumableCodecs) {\n\t\tif (!enableRtx && isRtxCodec(codec)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst matchedCapCodec = remoteRtpCapabilities.codecs!.find(capCodec =>\n\t\t\tmatchCodecs(capCodec, codec, { strict: true })\n\t\t);\n\n\t\tif (!matchedCapCodec) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tcodec.rtcpFeedback = matchedCapCodec.rtcpFeedback!.filter(\n\t\t\tfb => enableRtx || fb.type !== 'nack' || fb.parameter\n\t\t);\n\n\t\tconsumerParams.codecs.push(codec);\n\t}\n\n\t// Must sanitize the list of matched codecs by removing useless RTX codecs.\n\tfor (let idx = consumerParams.codecs.length - 1; idx >= 0; --idx) {\n\t\tconst codec = consumerParams.codecs[idx]!;\n\n\t\tif (isRtxCodec(codec)) {\n\t\t\t// Search for the associated media codec.\n\t\t\tconst associatedMediaCodec = consumerParams.codecs.find(\n\t\t\t\tmediaCodec => mediaCodec.payloadType === codec.parameters!['apt']\n\t\t\t);\n\n\t\t\tif (associatedMediaCodec) {\n\t\t\t\trtxSupported = true;\n\t\t\t} else {\n\t\t\t\tconsumerParams.codecs.splice(idx, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure there is at least one media codec.\n\tif (\n\t\tconsumerParams.codecs.length === 0 ||\n\t\tisRtxCodec(consumerParams.codecs[0]!)\n\t) {\n\t\tthrow new UnsupportedError('no compatible media codecs');\n\t}\n\n\tconsumerParams.headerExtensions =\n\t\tconsumableRtpParameters.headerExtensions!.filter(ext =>\n\t\t\tremoteRtpCapabilities.headerExtensions!.some(\n\t\t\t\tcapExt => capExt.preferredId === ext.id && capExt.uri === ext.uri\n\t\t\t)\n\t\t);\n\n\t// Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise.\n\tif (\n\t\tconsumerParams.headerExtensions.some(\n\t\t\text =>\n\t\t\t\text.uri ===\n\t\t\t\t'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'\n\t\t)\n\t) {\n\t\tfor (const codec of consumerParams.codecs) {\n\t\t\tcodec.rtcpFeedback = codec.rtcpFeedback!.filter(\n\t\t\t\tfb => fb.type !== 'goog-remb'\n\t\t\t);\n\t\t}\n\t} else if (\n\t\tconsumerParams.headerExtensions.some(\n\t\t\text =>\n\t\t\t\text.uri === 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time'\n\t\t)\n\t) {\n\t\tfor (const codec of consumerParams.codecs) {\n\t\t\tcodec.rtcpFeedback = codec.rtcpFeedback!.filter(\n\t\t\t\tfb => fb.type !== 'transport-cc'\n\t\t\t);\n\t\t}\n\t} else {\n\t\tfor (const codec of consumerParams.codecs) {\n\t\t\tcodec.rtcpFeedback = codec.rtcpFeedback!.filter(\n\t\t\t\tfb => fb.type !== 'transport-cc' && fb.type !== 'goog-remb'\n\t\t\t);\n\t\t}\n\t}\n\n\tif (!pipe) {\n\t\tconst consumerEncoding: RtpEncodingParameters = {\n\t\t\tssrc: utils.generateRandomNumber(),\n\t\t};\n\n\t\tif (rtxSupported) {\n\t\t\tconsumerEncoding.rtx = { ssrc: consumerEncoding.ssrc! + 1 };\n\t\t}\n\n\t\t// If any of the consumableRtpParameters.encodings has scalabilityMode,\n\t\t// process it (assume all encodings have the same value).\n\t\tconst encodingWithScalabilityMode = consumableRtpParameters.encodings!.find(\n\t\t\tencoding => encoding.scalabilityMode\n\t\t);\n\n\t\tlet scalabilityMode = encodingWithScalabilityMode\n\t\t\t? encodingWithScalabilityMode.scalabilityMode\n\t\t\t: undefined;\n\n\t\t// If there is simulast, mangle spatial layers in scalabilityMode.\n\t\tif (consumableRtpParameters.encodings!.length > 1) {\n\t\t\tconst { temporalLayers } = parseScalabilityMode(scalabilityMode);\n\n\t\t\tscalabilityMode = `L${\n\t\t\t\tconsumableRtpParameters.encodings!.length\n\t\t\t}T${temporalLayers}`;\n\t\t}\n\n\t\tif (scalabilityMode) {\n\t\t\tconsumerEncoding.scalabilityMode = scalabilityMode;\n\t\t}\n\n\t\t// Use the maximum maxBitrate in any encoding and honor it in the Consumer's\n\t\t// encoding.\n\t\tconst maxEncodingMaxBitrate = consumableRtpParameters.encodings!.reduce(\n\t\t\t(maxBitrate, encoding) =>\n\t\t\t\tencoding.maxBitrate && encoding.maxBitrate > maxBitrate\n\t\t\t\t\t? encoding.maxBitrate\n\t\t\t\t\t: maxBitrate,\n\t\t\t0\n\t\t);\n\n\t\tif (maxEncodingMaxBitrate) {\n\t\t\tconsumerEncoding.maxBitrate = maxEncodingMaxBitrate;\n\t\t}\n\n\t\t// Set a single encoding for the Consumer.\n\t\tconsumerParams.encodings!.push(consumerEncoding);\n\t} else {\n\t\tconst consumableEncodings =\n\t\t\tutils.clone<RtpEncodingParameters[] | undefined>(\n\t\t\t\tconsumableRtpParameters.encodings\n\t\t\t) ?? [];\n\t\tconst baseSsrc = utils.generateRandomNumber();\n\t\tconst baseRtxSsrc = utils.generateRandomNumber();\n\n\t\tfor (let i = 0; i < consumableEncodings.length; ++i) {\n\t\t\tconst encoding = consumableEncodings[i]!;\n\n\t\t\tencoding.ssrc = baseSsrc + i;\n\n\t\t\tif (rtxSupported) {\n\t\t\t\tencoding.rtx = { ssrc: baseRtxSsrc + i };\n\t\t\t} else {\n\t\t\t\tdelete encoding.rtx;\n\t\t\t}\n\n\t\t\tconsumerParams.encodings!.push(encoding);\n\t\t}\n\t}\n\n\treturn consumerParams;\n}\n\n/**\n * Generate RTP parameters for a pipe Consumer.\n *\n * It keeps all original consumable encodings and removes support for BWE. If\n * enableRtx is false, it also removes RTX and NACK support.\n */\nexport function getPipeConsumerRtpParameters({\n\tconsumableRtpParameters,\n\tenableRtx,\n}: {\n\tconsumableRtpParameters: RtpParameters;\n\tenableRtx: boolean;\n}): RtpParameters {\n\tconst consumerParams: RtpParameters = {\n\t\tcodecs: [],\n\t\theaderExtensions: [],\n\t\tencodings: [],\n\t\trtcp: consumableRtpParameters.rtcp,\n\t\tmsid: consumableRtpParameters.msid,\n\t};\n\n\tconst consumableCodecs =\n\t\tutils.clone<RtpCodecParameters[] | undefined>(\n\t\t\tconsumableRtpParameters.codecs\n\t\t) ?? [];\n\n\tfor (const codec of consumableCodecs) {\n\t\tif (!enableRtx && isRtxCodec(codec)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tcodec.rtcpFeedback = codec.rtcpFeedback!.filter(\n\t\t\tfb =>\n\t\t\t\t(fb.type === 'nack' && fb.parameter === 'pli') ||\n\t\t\t\t(fb.type === 'ccm' && fb.parameter === 'fir') ||\n\t\t\t\t(enableRtx && fb.type === 'nack' && !fb.parameter)\n\t\t);\n\n\t\tconsumerParams.codecs.push(codec);\n\t}\n\n\t// Reduce RTP extensions by disabling transport MID and BWE related ones.\n\tconsumerParams.headerExtensions =\n\t\tconsumableRtpParameters.headerExtensions!.filter(\n\t\t\text =>\n\t\t\t\text.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid' &&\n\t\t\t\text.uri !==\n\t\t\t\t\t'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' &&\n\t\t\t\text.uri !==\n\t\t\t\t\t'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'\n\t\t);\n\n\t// TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n\t// extension.\n\t//\n\t// We need to add Dependency-Descriptor header extension manually since it's\n\t// 'recvonly' so it's not present in received `consumableRtpParameters`.\n\tif (cache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer) {\n\t\tconsumerParams.headerExtensions.push(\n\t\t\tcache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer\n\t\t);\n\n\t\t// Sort header extensions by ID.\n\t\tconsumerParams.headerExtensions.sort((a, b) => a.id - b.id);\n\t}\n\n\tconst consumableEncodings =\n\t\tutils.clone<RtpEncodingParameters[] | undefined>(\n\t\t\tconsumableRtpParameters.encodings\n\t\t) ?? [];\n\tconst baseSsrc = utils.generateRandomNumber();\n\tconst baseRtxSsrc = utils.generateRandomNumber();\n\n\tfor (let i = 0; i < consumableEncodings.length; ++i) {\n\t\tconst encoding = consumableEncodings[i]!;\n\n\t\tencoding.ssrc = baseSsrc + i;\n\n\t\tif (enableRtx) {\n\t\t\tencoding.rtx = { ssrc: baseRtxSsrc + i };\n\t\t} else {\n\t\t\tdelete encoding.rtx;\n\t\t}\n\n\t\tconsumerParams.encodings!.push(encoding);\n\t}\n\n\treturn consumerParams;\n}\n\nexport function serializeRtpMapping(\n\tbuilder: flatbuffers.Builder,\n\trtpMapping: RtpCodecsEncodingsMapping\n): number {\n\tconst codecs: number[] = [];\n\n\tfor (const codec of rtpMapping.codecs) {\n\t\tcodecs.push(\n\t\t\tFbsRtpParameters.CodecMapping.createCodecMapping(\n\t\t\t\tbuilder,\n\t\t\t\tcodec.payloadType,\n\t\t\t\tcodec.mappedPayloadType\n\t\t\t)\n\t\t);\n\t}\n\tconst codecsOffset = FbsRtpParameters.RtpMapping.createCodecsVector(\n\t\tbuilder,\n\t\tcodecs\n\t);\n\n\tconst encodings: number[] = [];\n\n\tfor (const encoding of rtpMapping.encodings) {\n\t\tencodings.push(\n\t\t\tFbsRtpParameters.EncodingMapping.createEncodingMapping(\n\t\t\t\tbuilder,\n\t\t\t\tbuilder.createString(encoding.rid),\n\t\t\t\tencoding.ssrc ?? null,\n\t\t\t\tbuilder.createString(encoding.scalabilityMode),\n\t\t\t\tencoding.mappedSsrc\n\t\t\t)\n\t\t);\n\t}\n\n\tconst encodingsOffset = FbsRtpParameters.RtpMapping.createEncodingsVector(\n\t\tbuilder,\n\t\tencodings\n\t);\n\n\treturn FbsRtpParameters.RtpMapping.createRtpMapping(\n\t\tbuilder,\n\t\tcodecsOffset,\n\t\tencodingsOffset\n\t);\n}\n\nfunction isRtxCodec(codec: RtpCodecCapability | RtpCodecParameters): boolean {\n\treturn /.+\\/rtx$/i.test(codec.mimeType);\n}\n\nfunction matchCodecs(\n\taCodec: RtpCodecCapability | RouterRtpCodecCapability | RtpCodecParameters,\n\tbCodec: RtpCodecCapability | RouterRtpCodecCapability | RtpCodecParameters,\n\t{ strict = false, modify = false } = {}\n): boolean {\n\tconst aMimeType = aCodec.mimeType.toLowerCase();\n\tconst bMimeType = bCodec.mimeType.toLowerCase();\n\n\tif (aMimeType !== bMimeType) {\n\t\treturn false;\n\t}\n\n\tif (aCodec.clockRate !== bCodec.clockRate) {\n\t\treturn false;\n\t}\n\n\tif (aCodec.channels !== bCodec.channels) {\n\t\treturn false;\n\t}\n\n\t// Per codec special checks.\n\tswitch (aMimeType) {\n\t\tcase 'audio/multiopus': {\n\t\t\tconst aNumStreams = aCodec.parameters!['num_streams'];\n\t\t\tconst bNumStreams = bCodec.parameters!['num_streams'];\n\n\t\t\tif (aNumStreams !== bNumStreams) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst aCoupledStreams = aCodec.parameters!['coupled_streams'];\n\t\t\tconst bCoupledStreams = bCodec.parameters!['coupled_streams'];\n\n\t\t\tif (aCoupledStreams !== bCoupledStreams) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'video/h264': {\n\t\t\tif (strict) {\n\t\t\t\tconst aPacketizationMode =\n\t\t\t\t\taCodec.parameters!['packetization-mode'] || 0;\n\t\t\t\tconst bPacketizationMode =\n\t\t\t\t\tbCodec.parameters!['packetization-mode'] || 0;\n\n\t\t\t\tif (aPacketizationMode !== bPacketizationMode) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (!h264.isSameProfile(aCodec.parameters, bCodec.parameters)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tlet selectedProfileLevelId;\n\n\t\t\t\ttry {\n\t\t\t\t\tselectedProfileLevelId = h264.generateProfileLevelIdStringForAnswer(\n\t\t\t\t\t\taCodec.parameters,\n\t\t\t\t\t\tbCodec.parameters\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (modify) {\n\t\t\t\t\tif (selectedProfileLevelId) {\n\t\t\t\t\t\taCodec.parameters!['profile-level-id'] = selectedProfileLevelId;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdelete aCodec.parameters!['profile-level-id'];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'video/vp9': {\n\t\t\tif (strict) {\n\t\t\t\tconst aProfileId = aCodec.parameters!['profile-id'] || 0;\n\t\t\t\tconst bProfileId = bCodec.parameters!['profile-id'] || 0;\n\n\t\t\t\tif (aProfileId !== bProfileId) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Validates RtpCodecCapability. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtpCodecCapability(\n\tcodec: RtpCodecCapability | RouterRtpCodecCapability\n): void {\n\tconst MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i');\n\n\tif (typeof codec !== 'object') {\n\t\tthrow new TypeError('codec is not an object');\n\t}\n\n\t// mimeType is mandatory.\n\tif (!codec.mimeType || typeof codec.mimeType !== 'string') {\n\t\tthrow new TypeError('missing codec.mimeType');\n\t}\n\n\tconst mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType);\n\n\tif (!mimeTypeMatch) {\n\t\tthrow new TypeError('invalid codec.mimeType');\n\t}\n\n\t// Just override kind with media component of mimeType.\n\tcodec.kind = mimeTypeMatch[1]!.toLowerCase() as MediaKind;\n\n\t// preferredPayloadType is optional in RouterRtpCodecCapability.\n\tif (\n\t\tcodec.preferredPayloadType &&\n\t\ttypeof codec.preferredPayloadType !== 'number'\n\t) {\n\t\tthrow new TypeError('invalid codec.preferredPayloadType');\n\t}\n\n\t// clockRate is mandatory.\n\tif (typeof codec.clockRate !== 'number') {\n\t\tthrow new TypeError('missing codec.clockRate');\n\t}\n\n\t// channels is optional. If unset, set it to 1 (just if audio).\n\tif (codec.kind === 'audio') {\n\t\tif (typeof codec.channels !== 'number') {\n\t\t\tcodec.channels = 1;\n\t\t}\n\t} else {\n\t\tdelete codec.channels;\n\t}\n\n\t// parameters is optional. If unset, set it to an empty object.\n\tif (!codec.parameters || typeof codec.parameters !== 'object') {\n\t\tcodec.parameters = {};\n\t}\n\n\tfor (const key of Object.keys(codec.parameters)) {\n\t\tlet value = codec.parameters[key];\n\n\t\tif (value === undefined) {\n\t\t\tcodec.parameters[key] = '';\n\t\t\tvalue = '';\n\t\t}\n\n\t\tif (typeof value !== 'string' && typeof value !== 'number') {\n\t\t\tthrow new TypeError(\n\t\t\t\t`invalid codec parameter [key:${key}s, value:${value}]`\n\t\t\t);\n\t\t}\n\n\t\t// Specific parameters validation.\n\t\tif (key === 'apt') {\n\t\t\tif (typeof value !== 'number') {\n\t\t\t\tthrow new TypeError('invalid codec apt parameter');\n\t\t\t}\n\t\t}\n\t}\n\n\t// rtcpFeedback is optional. If unset, set it to an empty array.\n\tif (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) {\n\t\tcodec.rtcpFeedback = [];\n\t}\n\n\tfor (const fb of codec.rtcpFeedback) {\n\t\tvalidateAndNormalizeRtcpFeedback(fb);\n\t}\n}\n\n/**\n * Validates RtcpFeedback. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtcpFeedback(fb: RtcpFeedback): void {\n\tif (typeof fb !== 'object') {\n\t\tthrow new TypeError('fb is not an object');\n\t}\n\n\t// type is mandatory.\n\tif (!fb.type || typeof fb.type !== 'string') {\n\t\tthrow new TypeError('missing fb.type');\n\t}\n\n\t// parameter is optional. If unset set it to an empty string.\n\tif (!fb.parameter || typeof fb.parameter !== 'string') {\n\t\tfb.parameter = '';\n\t}\n}\n\n/**\n * Validates RtpHeaderExtension. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtpHeaderExtension(ext: RtpHeaderExtension): void {\n\tif (typeof ext !== 'object') {\n\t\tthrow new TypeError('ext is not an object');\n\t}\n\n\tif (ext.kind !== 'audio' && ext.kind !== 'video') {\n\t\tthrow new TypeError('invalid ext.kind');\n\t}\n\n\t// uri is mandatory.\n\tif (!ext.uri || typeof ext.uri !== 'string') {\n\t\tthrow new TypeError('missing ext.uri');\n\t}\n\n\t// preferredId is mandatory.\n\tif (typeof ext.preferredId !== 'number') {\n\t\tthrow new TypeError('missing ext.preferredId');\n\t}\n\n\t// preferredEncrypt is optional. If unset set it to false.\n\tif (ext.preferredEncrypt && typeof ext.preferredEncrypt !== 'boolean') {\n\t\tthrow new TypeError('invalid ext.preferredEncrypt');\n\t} else if (!ext.preferredEncrypt) {\n\t\text.preferredEncrypt = false;\n\t}\n\n\t// direction is optional. If unset set it to sendrecv.\n\tif (ext.direction && typeof ext.direction !== 'string') {\n\t\tthrow new TypeError('invalid ext.direction');\n\t} else if (!ext.direction) {\n\t\text.direction = 'sendrecv';\n\t}\n}\n\n/**\n * Validates RtpCodecParameters. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtpCodecParameters(\n\tcodec: RtpCodecParameters\n): void {\n\tconst MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i');\n\n\tif (typeof codec !== 'object') {\n\t\tthrow new TypeError('codec is not an object');\n\t}\n\n\t// mimeType is mandatory.\n\tif (!codec.mimeType || typeof codec.mimeType !== 'string') {\n\t\tthrow new TypeError('missing codec.mimeType');\n\t}\n\n\tconst mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType);\n\n\tif (!mimeTypeMatch) {\n\t\tthrow new TypeError('invalid codec.mimeType');\n\t}\n\n\t// payloadType is mandatory.\n\tif (typeof codec.payloadType !== 'number') {\n\t\tthrow new TypeError('missing codec.payloadType');\n\t}\n\n\t// clockRate is mandatory.\n\tif (typeof codec.clockRate !== 'number') {\n\t\tthrow new TypeError('missing codec.clockRate');\n\t}\n\n\tconst kind = mimeTypeMatch[1]!.toLowerCase() as MediaKind;\n\n\t// channels is optional. If unset, set it to 1 (just if audio).\n\tif (kind === 'audio') {\n\t\tif (typeof codec.channels !== 'number') {\n\t\t\tcodec.channels = 1;\n\t\t}\n\t} else {\n\t\tdelete codec.channels;\n\t}\n\n\t// parameters is optional. If unset, set it to an empty object.\n\tif (!codec.parameters || typeof codec.parameters !== 'object') {\n\t\tcodec.parameters = {};\n\t}\n\n\tfor (const key of Object.keys(codec.parameters)) {\n\t\tlet value = codec.parameters[key];\n\n\t\tif (value === undefined) {\n\t\t\tcodec.parameters[key] = '';\n\t\t\tvalue = '';\n\t\t}\n\n\t\tif (typeof value !== 'string' && typeof value !== 'number') {\n\t\t\tthrow new TypeError(\n\t\t\t\t`invalid codec parameter [key:${key}s, value:${value}]`\n\t\t\t);\n\t\t}\n\n\t\t// Specific parameters validation.\n\t\tif (key === 'apt') {\n\t\t\tif (typeof value !== 'number') {\n\t\t\t\tthrow new TypeError('invalid codec apt parameter');\n\t\t\t}\n\t\t}\n\t}\n\n\t// rtcpFeedback is optional. If unset, set it to an empty array.\n\tif (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) {\n\t\tcodec.rtcpFeedback = [];\n\t}\n\n\tfor (const fb of codec.rtcpFeedback) {\n\t\tvalidateAndNormalizeRtcpFeedback(fb);\n\t}\n}\n\n/**\n * Validates RtpHeaderExtensionParameteters. It may modify given data by adding\n * missing fields with default values. It throws if invalid.\n */\nfunction validateAndNormalizeRtpHeaderExtensionParameters(\n\text: RtpHeaderExtensionParameters\n): void {\n\tif (typeof ext !== 'object') {\n\t\tthrow new TypeError('ext is not an object');\n\t}\n\n\t// uri is mandatory.\n\tif (!ext.uri || typeof ext.uri !== 'string') {\n\t\tthrow new TypeError('missing ext.uri');\n\t}\n\n\t// id is mandatory.\n\tif (typeof ext.id !== 'number') {\n\t\tthrow new TypeError('missing ext.id');\n\t}\n\n\t// encrypt is optional. If unset set it to false.\n\tif (ext.encrypt && typeof ext.encrypt !== 'boolean') {\n\t\tthrow new TypeError('invalid ext.encrypt');\n\t} else if (!ext.encrypt) {\n\t\text.encrypt = false;\n\t}\n\n\t// parameters is optional. If unset, set it to an empty object.\n\tif (!ext.parameters || typeof ext.parameters !== 'object') {\n\t\text.parameters = {};\n\t}\n\n\tfor (const key of Object.keys(ext.parameters)) {\n\t\tlet value = ext.parameters[key];\n\n\t\tif (value === undefined) {\n\t\t\text.parameters[key] = '';\n\t\t\tvalue = '';\n\t\t}\n\n\t\tif (typeof value !== 'string' && typeof value !== 'number') {\n\t\t\tthrow new TypeError('invalid header extension parameter');\n\t\t}\n\t}\n}\n\n/**\n * Validates RtpEncodingParameters. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtpEncodingParameters(\n\tencoding: RtpEncodingParameters\n): void {\n\tif (typeof encoding !== 'object') {\n\t\tthrow new TypeError('encoding is not an object');\n\t}\n\n\t// ssrc is optional.\n\tif (encoding.ssrc && typeof encoding.ssrc !== 'number') {\n\t\tthrow new TypeError('invalid encoding.ssrc');\n\t}\n\n\t// rid is optional.\n\tif (encoding.rid && typeof encoding.rid !== 'string') {\n\t\tthrow new TypeError('invalid encoding.rid');\n\t}\n\n\t// rtx is optional.\n\tif (encoding.rtx && typeof encoding.rtx !== 'object') {\n\t\tthrow new TypeError('invalid encoding.rtx');\n\t} else if (encoding.rtx) {\n\t\t// RTX ssrc is mandatory if rtx is present.\n\t\tif (typeof encoding.rtx.ssrc !== 'number') {\n\t\t\tthrow new TypeError('missing encoding.rtx.ssrc');\n\t\t}\n\t}\n\n\t// dtx is optional. If unset set it to false.\n\tif (!encoding.dtx || typeof encoding.dtx !== 'boolean') {\n\t\tencoding.dtx = false;\n\t}\n\n\t// scalabilityMode is optional.\n\tif (\n\t\tencoding.scalabilityMode &&\n\t\ttypeof encoding.scalabilityMode !== 'string'\n\t) {\n\t\tthrow new TypeError('invalid encoding.scalabilityMode');\n\t}\n}\n\n/**\n * Validates RtcpParameters. It may modify given data by adding missing\n * fields with default values.\n * It throws if invalid.\n */\nfunction validateAndNormalizeRtcpParameters(rtcp: RtcpParameters): void {\n\tif (typeof rtcp !== 'object') {\n\t\tthrow new TypeError('rtcp is not an object');\n\t}\n\n\t// cname is optional.\n\tif (rtcp.cname && typeof rtcp.cname !== 'string') {\n\t\tthrow new TypeError('invalid rtcp.cname');\n\t}\n\n\t// reducedSize is optional. If unset set it to true.\n\tif (!rtcp.reducedSize || typeof rtcp.reducedSize !== 'boolean') {\n\t\trtcp.reducedSize = true;\n\t}\n}\n"
  },
  {
    "path": "node/src/rtpParametersFbsUtils.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport type {\n\tRtpParameters,\n\tRtpCodecParameters,\n\tRtcpFeedback,\n\tRtpEncodingParameters,\n\tRtpHeaderExtensionUri,\n\tRtpHeaderExtensionParameters,\n\tRtcpParameters,\n} from './rtpParametersTypes';\nimport * as fbsUtils from './fbsUtils';\nimport {\n\tBoolean as FbsBoolean,\n\tDouble as FbsDouble,\n\tInteger32 as FbsInteger32,\n\tInteger32Array as FbsInteger32Array,\n\tString as FbsString,\n\tParameter as FbsParameter,\n\tRtcpFeedback as FbsRtcpFeedback,\n\tRtcpParameters as FbsRtcpParameters,\n\tRtpCodecParameters as FbsRtpCodecParameters,\n\tRtpEncodingParameters as FbsRtpEncodingParameters,\n\tRtpHeaderExtensionParameters as FbsRtpHeaderExtensionParameters,\n\tRtpHeaderExtensionUri as FbsRtpHeaderExtensionUri,\n\tRtpParameters as FbsRtpParameters,\n\tRtx as FbsRtx,\n\tValue as FbsValue,\n} from './fbs/rtp-parameters';\n\nexport function serializeRtpParameters(\n\tbuilder: flatbuffers.Builder,\n\trtpParameters: RtpParameters\n): number {\n\tconst codecs: number[] = [];\n\tconst headerExtensions: number[] = [];\n\n\tfor (const codec of rtpParameters.codecs) {\n\t\tconst mimeTypeOffset = builder.createString(codec.mimeType);\n\t\tconst parameters = serializeParameters(builder, codec.parameters!);\n\t\tconst parametersOffset = FbsRtpCodecParameters.createParametersVector(\n\t\t\tbuilder,\n\t\t\tparameters\n\t\t);\n\n\t\tconst rtcpFeedback: number[] = [];\n\n\t\tfor (const rtcp of codec.rtcpFeedback ?? []) {\n\t\t\tconst typeOffset = builder.createString(rtcp.type);\n\t\t\tconst rtcpParametersOffset = builder.createString(rtcp.parameter);\n\n\t\t\trtcpFeedback.push(\n\t\t\t\tFbsRtcpFeedback.createRtcpFeedback(\n\t\t\t\t\tbuilder,\n\t\t\t\t\ttypeOffset,\n\t\t\t\t\trtcpParametersOffset\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\tconst rtcpFeedbackOffset = FbsRtpCodecParameters.createRtcpFeedbackVector(\n\t\t\tbuilder,\n\t\t\trtcpFeedback\n\t\t);\n\n\t\tcodecs.push(\n\t\t\tFbsRtpCodecParameters.createRtpCodecParameters(\n\t\t\t\tbuilder,\n\t\t\t\tmimeTypeOffset,\n\t\t\t\tcodec.payloadType,\n\t\t\t\tcodec.clockRate,\n\t\t\t\tNumber(codec.channels),\n\t\t\t\tparametersOffset,\n\t\t\t\trtcpFeedbackOffset\n\t\t\t)\n\t\t);\n\t}\n\tconst codecsOffset = FbsRtpParameters.createCodecsVector(builder, codecs);\n\n\t// RtpHeaderExtensionParameters.\n\tfor (const headerExtension of rtpParameters.headerExtensions ?? []) {\n\t\tconst uri = rtpHeaderExtensionUriToFbs(headerExtension.uri);\n\t\tconst parameters = serializeParameters(\n\t\t\tbuilder,\n\t\t\theaderExtension.parameters!\n\t\t);\n\t\tconst parametersOffset = FbsRtpCodecParameters.createParametersVector(\n\t\t\tbuilder,\n\t\t\tparameters\n\t\t);\n\n\t\theaderExtensions.push(\n\t\t\tFbsRtpHeaderExtensionParameters.createRtpHeaderExtensionParameters(\n\t\t\t\tbuilder,\n\t\t\t\turi,\n\t\t\t\theaderExtension.id,\n\t\t\t\tBoolean(headerExtension.encrypt),\n\t\t\t\tparametersOffset\n\t\t\t)\n\t\t);\n\t}\n\tconst headerExtensionsOffset = FbsRtpParameters.createHeaderExtensionsVector(\n\t\tbuilder,\n\t\theaderExtensions\n\t);\n\n\t// RtpEncodingParameters.\n\tconst encodingsOffset = serializeRtpEncodingParameters(\n\t\tbuilder,\n\t\trtpParameters.encodings ?? []\n\t);\n\n\t// RtcpParameters.\n\tconst { cname, reducedSize } = rtpParameters.rtcp ?? { reducedSize: true };\n\tconst cnameOffset = builder.createString(cname);\n\n\tconst rtcpOffset = FbsRtcpParameters.createRtcpParameters(\n\t\tbuilder,\n\t\tcnameOffset,\n\t\tBoolean(reducedSize)\n\t);\n\n\tconst midOffset = builder.createString(rtpParameters.mid);\n\tconst msidOffset = builder.createString(rtpParameters.msid);\n\n\tFbsRtpParameters.startRtpParameters(builder);\n\tFbsRtpParameters.addMid(builder, midOffset);\n\tFbsRtpParameters.addCodecs(builder, codecsOffset);\n\tFbsRtpParameters.addHeaderExtensions(builder, headerExtensionsOffset);\n\tFbsRtpParameters.addEncodings(builder, encodingsOffset);\n\tFbsRtpParameters.addRtcp(builder, rtcpOffset);\n\tFbsRtpParameters.addMsid(builder, msidOffset);\n\n\treturn FbsRtpParameters.endRtpParameters(builder);\n}\n\nexport function serializeRtpEncodingParameters(\n\tbuilder: flatbuffers.Builder,\n\trtpEncodingParameters: RtpEncodingParameters[] = []\n): number {\n\tconst encodings: number[] = [];\n\n\tfor (const encoding of rtpEncodingParameters) {\n\t\t// Prepare Rid.\n\t\tconst ridOffset = builder.createString(encoding.rid);\n\n\t\t// Prepare Rtx.\n\t\tlet rtxOffset: number | undefined;\n\n\t\tif (encoding.rtx) {\n\t\t\trtxOffset = FbsRtx.createRtx(builder, encoding.rtx.ssrc);\n\t\t}\n\n\t\t// Prepare scalability mode.\n\t\tlet scalabilityModeOffset: number | undefined;\n\n\t\tif (encoding.scalabilityMode) {\n\t\t\tscalabilityModeOffset = builder.createString(encoding.scalabilityMode);\n\t\t}\n\n\t\t// Start serialization.\n\t\tFbsRtpEncodingParameters.startRtpEncodingParameters(builder);\n\n\t\t// Add SSRC.\n\t\tif (encoding.ssrc) {\n\t\t\tFbsRtpEncodingParameters.addSsrc(builder, encoding.ssrc);\n\t\t}\n\n\t\t// Add Rid.\n\t\tFbsRtpEncodingParameters.addRid(builder, ridOffset);\n\n\t\t// Add payload type.\n\t\tif (encoding.codecPayloadType) {\n\t\t\tFbsRtpEncodingParameters.addCodecPayloadType(\n\t\t\t\tbuilder,\n\t\t\t\tencoding.codecPayloadType\n\t\t\t);\n\t\t}\n\n\t\t// Add RTX.\n\t\tif (rtxOffset) {\n\t\t\tFbsRtpEncodingParameters.addRtx(builder, rtxOffset);\n\t\t}\n\n\t\t// Add DTX.\n\t\tif (encoding.dtx !== undefined) {\n\t\t\tFbsRtpEncodingParameters.addDtx(builder, encoding.dtx);\n\t\t}\n\n\t\t// Add scalability ode.\n\t\tif (scalabilityModeOffset) {\n\t\t\tFbsRtpEncodingParameters.addScalabilityMode(\n\t\t\t\tbuilder,\n\t\t\t\tscalabilityModeOffset\n\t\t\t);\n\t\t}\n\n\t\t// Add max bitrate.\n\t\tif (encoding.maxBitrate !== undefined) {\n\t\t\tFbsRtpEncodingParameters.addMaxBitrate(builder, encoding.maxBitrate);\n\t\t}\n\n\t\t// End serialization.\n\t\tencodings.push(FbsRtpEncodingParameters.endRtpEncodingParameters(builder));\n\t}\n\n\treturn FbsRtpParameters.createEncodingsVector(builder, encodings);\n}\n\nexport function serializeParameters(\n\tbuilder: flatbuffers.Builder,\n\tparameters: Record<string, unknown>\n): number[] {\n\tconst fbsParameters: number[] = [];\n\n\tfor (const key of Object.keys(parameters)) {\n\t\tconst value = parameters[key];\n\t\tconst keyOffset = builder.createString(key);\n\t\tlet parameterOffset: number;\n\n\t\tif (typeof value === 'boolean') {\n\t\t\tparameterOffset = FbsParameter.createParameter(\n\t\t\t\tbuilder,\n\t\t\t\tkeyOffset,\n\t\t\t\tFbsValue.Boolean,\n\t\t\t\tvalue === true ? 1 : 0\n\t\t\t);\n\t\t} else if (typeof value === 'number') {\n\t\t\t// Integer.\n\t\t\tif (value % 1 === 0) {\n\t\t\t\tconst valueOffset = FbsInteger32.createInteger32(builder, value);\n\n\t\t\t\tparameterOffset = FbsParameter.createParameter(\n\t\t\t\t\tbuilder,\n\t\t\t\t\tkeyOffset,\n\t\t\t\t\tFbsValue.Integer32,\n\t\t\t\t\tvalueOffset\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Float.\n\t\t\telse {\n\t\t\t\tconst valueOffset = FbsDouble.createDouble(builder, value);\n\n\t\t\t\tparameterOffset = FbsParameter.createParameter(\n\t\t\t\t\tbuilder,\n\t\t\t\t\tkeyOffset,\n\t\t\t\t\tFbsValue.Double,\n\t\t\t\t\tvalueOffset\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (typeof value === 'string') {\n\t\t\tconst valueOffset = FbsString.createString(\n\t\t\t\tbuilder,\n\t\t\t\tbuilder.createString(value)\n\t\t\t);\n\n\t\t\tparameterOffset = FbsParameter.createParameter(\n\t\t\t\tbuilder,\n\t\t\t\tkeyOffset,\n\t\t\t\tFbsValue.String,\n\t\t\t\tvalueOffset\n\t\t\t);\n\t\t} else if (Array.isArray(value)) {\n\t\t\tconst valueOffset = FbsInteger32Array.createValueVector(builder, value);\n\n\t\t\tparameterOffset = FbsParameter.createParameter(\n\t\t\t\tbuilder,\n\t\t\t\tkeyOffset,\n\t\t\t\tFbsValue.Integer32Array,\n\t\t\t\tvalueOffset\n\t\t\t);\n\t\t} else {\n\t\t\tthrow new Error(`invalid parameter type [key:'${key}', value:${value}]`);\n\t\t}\n\n\t\tfbsParameters.push(parameterOffset);\n\t}\n\n\treturn fbsParameters;\n}\n\nexport function parseRtcpFeedback(data: FbsRtcpFeedback): RtcpFeedback {\n\treturn {\n\t\ttype: data.type()!,\n\t\tparameter: data.parameter() ?? undefined,\n\t};\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function parseParameters(data: any): Record<string, unknown> {\n\tconst parameters: Record<string, unknown> = {};\n\n\tfor (let i = 0; i < data.parametersLength(); i++) {\n\t\tconst fbsParameter = data.parameters(i)!;\n\n\t\tswitch (fbsParameter.valueType()) {\n\t\t\tcase FbsValue.Boolean: {\n\t\t\t\tconst value = new FbsBoolean();\n\n\t\t\t\tfbsParameter.value(value);\n\t\t\t\tparameters[String(fbsParameter.name())] = value.value();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FbsValue.Integer32: {\n\t\t\t\tconst value = new FbsInteger32();\n\n\t\t\t\tfbsParameter.value(value);\n\t\t\t\tparameters[String(fbsParameter.name())] = value.value();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FbsValue.Double: {\n\t\t\t\tconst value = new FbsDouble();\n\n\t\t\t\tfbsParameter.value(value);\n\t\t\t\tparameters[String(fbsParameter.name())] = value.value();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FbsValue.String: {\n\t\t\t\tconst value = new FbsString();\n\n\t\t\t\tfbsParameter.value(value);\n\t\t\t\tparameters[String(fbsParameter.name())] = value.value();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FbsValue.Integer32Array: {\n\t\t\t\tconst value = new FbsInteger32Array();\n\n\t\t\t\tfbsParameter.value(value);\n\t\t\t\tparameters[String(fbsParameter.name())] = value.valueArray();\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parameters;\n}\n\nexport function parseRtpCodecParameters(\n\tdata: FbsRtpCodecParameters\n): RtpCodecParameters {\n\tconst parameters = parseParameters(data);\n\n\tlet rtcpFeedback: RtcpFeedback[] = [];\n\n\tif (data.rtcpFeedbackLength() > 0) {\n\t\trtcpFeedback = fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'rtcpFeedback',\n\t\t\tparseRtcpFeedback\n\t\t);\n\t}\n\n\treturn {\n\t\tmimeType: data.mimeType()!,\n\t\tpayloadType: data.payloadType(),\n\t\tclockRate: data.clockRate(),\n\t\tchannels: data.channels() ?? undefined,\n\t\tparameters,\n\t\trtcpFeedback,\n\t};\n}\n\nexport function rtpHeaderExtensionUriFromFbs(\n\turi: FbsRtpHeaderExtensionUri\n): RtpHeaderExtensionUri {\n\tswitch (uri) {\n\t\tcase FbsRtpHeaderExtensionUri.Mid: {\n\t\t\treturn 'urn:ietf:params:rtp-hdrext:sdes:mid';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.RtpStreamId: {\n\t\t\treturn 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.RepairRtpStreamId: {\n\t\t\treturn 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.AbsSendTime: {\n\t\t\treturn 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.TransportWideCcDraft01: {\n\t\t\treturn 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.SsrcAudioLevel: {\n\t\t\treturn 'urn:ietf:params:rtp-hdrext:ssrc-audio-level';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.DependencyDescriptor: {\n\t\t\treturn 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.VideoOrientation: {\n\t\t\treturn 'urn:3gpp:video-orientation';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.TimeOffset: {\n\t\t\treturn 'urn:ietf:params:rtp-hdrext:toffset';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.AbsCaptureTime: {\n\t\t\treturn 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.PlayoutDelay: {\n\t\t\treturn 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay';\n\t\t}\n\n\t\tcase FbsRtpHeaderExtensionUri.MediasoupPacketId: {\n\t\t\treturn 'urn:mediasoup:params:rtp-hdrext:packet-id';\n\t\t}\n\t}\n}\n\nexport function rtpHeaderExtensionUriToFbs(\n\turi: RtpHeaderExtensionUri\n): FbsRtpHeaderExtensionUri {\n\tswitch (uri) {\n\t\tcase 'urn:ietf:params:rtp-hdrext:sdes:mid': {\n\t\t\treturn FbsRtpHeaderExtensionUri.Mid;\n\t\t}\n\n\t\tcase 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id': {\n\t\t\treturn FbsRtpHeaderExtensionUri.RtpStreamId;\n\t\t}\n\n\t\tcase 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id': {\n\t\t\treturn FbsRtpHeaderExtensionUri.RepairRtpStreamId;\n\t\t}\n\n\t\tcase 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time': {\n\t\t\treturn FbsRtpHeaderExtensionUri.AbsSendTime;\n\t\t}\n\n\t\tcase 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01': {\n\t\t\treturn FbsRtpHeaderExtensionUri.TransportWideCcDraft01;\n\t\t}\n\n\t\tcase 'urn:ietf:params:rtp-hdrext:ssrc-audio-level': {\n\t\t\treturn FbsRtpHeaderExtensionUri.SsrcAudioLevel;\n\t\t}\n\n\t\tcase 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension': {\n\t\t\treturn FbsRtpHeaderExtensionUri.DependencyDescriptor;\n\t\t}\n\n\t\tcase 'urn:3gpp:video-orientation': {\n\t\t\treturn FbsRtpHeaderExtensionUri.VideoOrientation;\n\t\t}\n\n\t\tcase 'urn:ietf:params:rtp-hdrext:toffset': {\n\t\t\treturn FbsRtpHeaderExtensionUri.TimeOffset;\n\t\t}\n\n\t\tcase 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time': {\n\t\t\treturn FbsRtpHeaderExtensionUri.AbsCaptureTime;\n\t\t}\n\n\t\tcase 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay': {\n\t\t\treturn FbsRtpHeaderExtensionUri.PlayoutDelay;\n\t\t}\n\n\t\tcase 'urn:mediasoup:params:rtp-hdrext:packet-id': {\n\t\t\treturn FbsRtpHeaderExtensionUri.MediasoupPacketId;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid RtpHeaderExtensionUri: ${uri}`);\n\t\t}\n\t}\n}\n\nexport function parseRtpHeaderExtensionParameters(\n\tdata: FbsRtpHeaderExtensionParameters\n): RtpHeaderExtensionParameters {\n\treturn {\n\t\turi: rtpHeaderExtensionUriFromFbs(data.uri()),\n\t\tid: data.id(),\n\t\tencrypt: data.encrypt(),\n\t\tparameters: parseParameters(data),\n\t};\n}\n\nexport function parseRtpEncodingParameters(\n\tdata: FbsRtpEncodingParameters\n): RtpEncodingParameters {\n\treturn {\n\t\tssrc: data.ssrc() ?? undefined,\n\t\trid: data.rid() || undefined,\n\t\tcodecPayloadType:\n\t\t\tdata.codecPayloadType() !== null ? data.codecPayloadType()! : undefined,\n\t\trtx: data.rtx() ? { ssrc: data.rtx()!.ssrc() } : undefined,\n\t\tdtx: data.dtx(),\n\t\tscalabilityMode: data.scalabilityMode() || undefined,\n\t\tmaxBitrate: data.maxBitrate() !== null ? data.maxBitrate()! : undefined,\n\t};\n}\n\nexport function parseRtpParameters(data: FbsRtpParameters): RtpParameters {\n\tconst codecs = fbsUtils.parseVector(data, 'codecs', parseRtpCodecParameters);\n\n\tlet headerExtensions: RtpHeaderExtensionParameters[] = [];\n\n\tif (data.headerExtensionsLength() > 0) {\n\t\theaderExtensions = fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'headerExtensions',\n\t\t\tparseRtpHeaderExtensionParameters\n\t\t);\n\t}\n\n\tlet encodings: RtpEncodingParameters[] = [];\n\n\tif (data.encodingsLength() > 0) {\n\t\tencodings = fbsUtils.parseVector(\n\t\t\tdata,\n\t\t\t'encodings',\n\t\t\tparseRtpEncodingParameters\n\t\t);\n\t}\n\n\tlet rtcp: RtcpParameters | undefined;\n\n\tif (data.rtcp()) {\n\t\tconst fbsRtcp = data.rtcp()!;\n\n\t\trtcp = {\n\t\t\tcname: fbsRtcp.cname() || undefined,\n\t\t\treducedSize: fbsRtcp.reducedSize(),\n\t\t};\n\t}\n\n\treturn {\n\t\tmid: data.mid() || undefined,\n\t\tcodecs,\n\t\theaderExtensions,\n\t\tencodings,\n\t\trtcp,\n\t\tmsid: data.msid() || undefined,\n\t};\n}\n"
  },
  {
    "path": "node/src/rtpParametersTypes.ts",
    "content": "/**\n * Media kind ('audio' or 'video').\n */\nexport type MediaKind = 'audio' | 'video';\n\n/**\n * The RTP capabilities define what mediasoup or an endpoint can receive at\n * media level.\n */\nexport type RtpCapabilities = {\n\t/**\n\t * Supported media and RTX codecs.\n\t */\n\tcodecs?: RtpCodecCapability[];\n\n\t/**\n\t * Supported RTP header extensions.\n\t */\n\theaderExtensions?: RtpHeaderExtension[];\n};\n\n/**\n * Special RtpCapabilities for `supportedRtpCapabilities` in which `codecs`\n * is an array of RouterRtpCodecCapability.\n */\nexport type RouterRtpCapabilities = Omit<RtpCapabilities, 'codecs'> & {\n\tcodecs?: RouterRtpCodecCapability[];\n};\n\n/**\n * Provides information on the capabilities of a codec within the RTP\n * capabilities. The list of media codecs supported by mediasoup and their\n * settings is defined in the supportedRtpCapabilities.ts file.\n *\n * Exactly one RtpCodecCapability will be present for each supported combination\n * of parameters that requires a distinct value of preferredPayloadType. For\n * example:\n *\n * - Multiple H264 codecs, each with their own distinct 'packetization-mode' and\n *   'profile-level-id' values.\n * - Multiple VP9 codecs, each with their own distinct 'profile-id' value.\n *\n * RtpCodecCapability entries in the mediaCodecs array of RouterOptions do not\n * require preferredPayloadType field (if unset, mediasoup will choose a random\n * one). If given, make sure it's in the 96-127 range.\n */\nexport type RtpCodecCapability = {\n\t/**\n\t * Media kind.\n\t */\n\tkind: MediaKind;\n\n\t/**\n\t * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8').\n\t */\n\tmimeType: string;\n\n\t/**\n\t * The preferred RTP payload type.\n\t *\n\t * NOTE: Despite it's a mandatory field, it's optional in `mediaCodecs` of\n\t * RouterOptions.\n\t */\n\tpreferredPayloadType: number;\n\n\t/**\n\t * Codec clock rate expressed in Hertz.\n\t */\n\tclockRate: number;\n\n\t/**\n\t * The number of channels supported (e.g. two for stereo). Just for audio.\n\t * Default 1.\n\t */\n\tchannels?: number;\n\n\t/**\n\t * Codec specific parameters. Some parameters (such as 'packetization-mode'\n\t * and 'profile-level-id' in H264 or 'profile-id' in VP9) are critical for\n\t * codec matching.\n\t */\n\tparameters?: Record<string, unknown>;\n\n\t/**\n\t * Transport layer and codec-specific feedback messages for this codec.\n\t */\n\trtcpFeedback?: RtcpFeedback[];\n};\n\n/**\n * Special RtpCodecCapability for RouterOptions in which `preferredPayloadType`\n * is optional.\n */\nexport type RouterRtpCodecCapability = Omit<\n\tRtpCodecCapability,\n\t'preferredPayloadType'\n> & {\n\tpreferredPayloadType?: number;\n};\n\n/**\n * Direction of RTP header extension.\n */\nexport type RtpHeaderExtensionDirection =\n\t| 'sendrecv'\n\t| 'sendonly'\n\t| 'recvonly'\n\t| 'inactive';\n\n/**\n * Provides information relating to supported header extensions. The list of\n * RTP header extensions supported by mediasoup is defined in the\n * supportedRtpCapabilities.ts file.\n *\n * mediasoup does not currently support encrypted RTP header extensions. The\n * direction field is just present in mediasoup RTP capabilities (retrieved via\n * router.rtpCapabilities or mediasoup.getSupportedRtpCapabilities()). It's\n * ignored if present in endpoints' RTP capabilities.\n */\nexport type RtpHeaderExtension = {\n\t/**\n\t * Media kind.\n\t */\n\tkind: MediaKind;\n\n\t/*\n\t * The URI of the RTP header extension, as defined in RFC 5285.\n\t */\n\turi: RtpHeaderExtensionUri;\n\n\t/**\n\t * The preferred numeric identifier that goes in the RTP packet. Must be\n\t * unique.\n\t */\n\tpreferredId: number;\n\n\t/**\n\t * If true, it is preferred that the value in the header be encrypted as per\n\t * RFC 6904. Default false.\n\t */\n\tpreferredEncrypt?: boolean;\n\n\t/**\n\t * If 'sendrecv', mediasoup supports sending and receiving this RTP extension.\n\t * 'sendonly' means that mediasoup can send (but not receive) it. 'recvonly'\n\t * means that mediasoup can receive (but not send) it.\n\t */\n\tdirection?: RtpHeaderExtensionDirection;\n};\n\n/**\n * The RTP send parameters describe a media stream received by mediasoup from\n * an endpoint through its corresponding mediasoup Producer. These parameters\n * may include a mid value that the mediasoup transport will use to match\n * received RTP packets based on their MID RTP extension value.\n *\n * mediasoup allows RTP send parameters with a single encoding and with multiple\n * encodings (simulcast). In the latter case, each entry in the encodings array\n * must include a ssrc field or a rid field (the RID RTP extension value). Check\n * the Simulcast and SVC sections for more information.\n *\n * The RTP receive parameters describe a media stream as sent by mediasoup to\n * an endpoint through its corresponding mediasoup Consumer. The mid value is\n * unset (mediasoup does not include the MID RTP extension into RTP packets\n * being sent to endpoints).\n *\n * There is a single entry in the encodings array (even if the corresponding\n * producer uses simulcast). The consumer sends a single and continuous RTP\n * stream to the endpoint and spatial/temporal layer selection is possible via\n * consumer.setPreferredLayers().\n *\n * As an exception, previous bullet is not true when consuming a stream over a\n * PipeTransport, in which all RTP streams from the associated producer are\n * forwarded verbatim through the consumer.\n *\n * The RTP receive parameters will always have their ssrc values randomly\n * generated for all of its  encodings (and optional rtx: { ssrc: XXXX } if the\n * endpoint supports RTX), regardless of the original RTP send parameters in\n * the associated producer. This applies even if the producer's encodings have\n * rid set.\n */\nexport type RtpParameters = {\n\t/**\n\t * The MID RTP extension value as defined in the BUNDLE specification.\n\t */\n\tmid?: string;\n\n\t/**\n\t * Media and RTX codecs in use.\n\t */\n\tcodecs: RtpCodecParameters[];\n\n\t/**\n\t * RTP header extensions in use.\n\t */\n\theaderExtensions?: RtpHeaderExtensionParameters[];\n\n\t/**\n\t * Transmitted RTP streams and their settings.\n\t */\n\tencodings?: RtpEncodingParameters[];\n\n\t/**\n\t * Parameters used for RTCP.\n\t */\n\trtcp?: RtcpParameters;\n\t/**\n\t * MSID (WebRTC MediaStream Identification).\n\t *\n\t * @see https://datatracker.ietf.org/doc/html/rfc8830\n\t */\n\tmsid?: string;\n};\n\n/**\n * Provides information on codec settings within the RTP parameters. The list\n * of media codecs supported by mediasoup and their settings is defined in the\n * supportedRtpCapabilities.ts file.\n */\nexport type RtpCodecParameters = {\n\t/**\n\t * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8').\n\t */\n\tmimeType: string;\n\n\t/**\n\t * The value that goes in the RTP Payload Type Field. Must be unique.\n\t */\n\tpayloadType: number;\n\n\t/**\n\t * Codec clock rate expressed in Hertz.\n\t */\n\tclockRate: number;\n\n\t/**\n\t * The number of channels supported (e.g. two for stereo). Just for audio.\n\t * Default 1.\n\t */\n\tchannels?: number;\n\n\t/**\n\t * Codec-specific parameters available for signaling. Some parameters (such\n\t * as 'packetization-mode' and 'profile-level-id' in H264 or 'profile-id' in\n\t * VP9) are critical for codec matching.\n\t */\n\tparameters?: Record<string, unknown>;\n\n\t/**\n\t * Transport layer and codec-specific feedback messages for this codec.\n\t */\n\trtcpFeedback?: RtcpFeedback[];\n};\n\n/**\n * Provides information on RTCP feedback messages for a specific codec. Those\n * messages can be transport layer feedback messages or codec-specific feedback\n * messages. The list of RTCP feedbacks supported by mediasoup is defined in the\n * supportedRtpCapabilities.ts file.\n */\nexport type RtcpFeedback = {\n\t/**\n\t * RTCP feedback type.\n\t */\n\ttype: string;\n\n\t/**\n\t * RTCP feedback parameter.\n\t */\n\tparameter?: string;\n};\n\n/**\n * Provides information relating to an encoding, which represents a media RTP\n * stream and its associated RTX stream (if any).\n */\nexport type RtpEncodingParameters = {\n\t/**\n\t * The media SSRC.\n\t */\n\tssrc?: number;\n\n\t/**\n\t * The RID RTP extension value. Must be unique.\n\t */\n\trid?: string;\n\n\t/**\n\t * Codec payload type this encoding affects. If unset, first media codec is\n\t * chosen.\n\t */\n\tcodecPayloadType?: number;\n\n\t/**\n\t * RTX stream information. It must contain a numeric ssrc field indicating\n\t * the RTX SSRC.\n\t */\n\trtx?: { ssrc: number };\n\n\t/**\n\t * It indicates whether discontinuous RTP transmission will be used. Useful\n\t * for audio (if the codec supports it) and for video screen sharing (when\n\t * static content is being transmitted, this option disables the RTP\n\t * inactivity checks in mediasoup). Default false.\n\t */\n\tdtx?: boolean;\n\n\t/**\n\t * Number of spatial and temporal layers in the RTP stream (e.g. 'L1T3').\n\t * See webrtc-svc.\n\t */\n\tscalabilityMode?: string;\n\n\t/**\n\t * Maximum bitrate (bps) announced for this stream.\n\t */\n\tmaxBitrate?: number;\n};\n\nexport type RtpHeaderExtensionUri =\n\t| 'urn:ietf:params:rtp-hdrext:sdes:mid'\n\t| 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id'\n\t| 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id'\n\t| 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time'\n\t| 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'\n\t| 'urn:ietf:params:rtp-hdrext:ssrc-audio-level'\n\t| 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension'\n\t| 'urn:3gpp:video-orientation'\n\t| 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time'\n\t| 'urn:ietf:params:rtp-hdrext:toffset'\n\t| 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay'\n\t| 'urn:mediasoup:params:rtp-hdrext:packet-id';\n\n/**\n * Defines a RTP header extension within the RTP parameters. The list of RTP\n * header extensions supported by mediasoup is defined in the\n * supportedRtpCapabilities.ts file.\n *\n * mediasoup does not currently support encrypted RTP header extensions and no\n * parameters are currently considered.\n */\nexport type RtpHeaderExtensionParameters = {\n\t/**\n\t * The URI of the RTP header extension, as defined in RFC 5285.\n\t */\n\turi: RtpHeaderExtensionUri;\n\n\t/**\n\t * The numeric identifier that goes in the RTP packet. Must be unique.\n\t */\n\tid: number;\n\n\t/**\n\t * If true, the value in the header is encrypted as per RFC 6904. Default false.\n\t */\n\tencrypt?: boolean;\n\n\t/**\n\t * Configuration parameters for the header extension.\n\t */\n\tparameters?: Record<string, unknown>;\n};\n\n/**\n * Provides information on RTCP settings within the RTP parameters.\n *\n * If no cname is given in a producer's RTP parameters, the mediasoup transport\n * will choose a random one that will be used into RTCP SDES messages sent to\n * all its associated consumers.\n *\n * mediasoup assumes reducedSize to always be true.\n */\nexport type RtcpParameters = {\n\t/**\n\t * The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages).\n\t */\n\tcname?: string;\n\n\t/**\n\t * Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP\n\t * as specified in RFC 3550 (if false). Default true.\n\t */\n\treducedSize?: boolean;\n};\n"
  },
  {
    "path": "node/src/rtpStreamStatsFbsUtils.ts",
    "content": "import {\n\tRtpStreamRecvStats,\n\tRtpStreamSendStats,\n\tBaseRtpStreamStats,\n\tBitrateByLayer,\n} from './rtpStreamStatsTypes';\nimport * as FbsRtpStream from './fbs/rtp-stream';\nimport * as FbsRtpParameters from './fbs/rtp-parameters';\n\nexport function parseRtpStreamStats(\n\tbinary: FbsRtpStream.Stats\n): RtpStreamRecvStats | RtpStreamSendStats {\n\tif (binary.dataType() === FbsRtpStream.StatsData.RecvStats) {\n\t\treturn parseRtpStreamRecvStats(binary);\n\t} else {\n\t\treturn parseSendStreamStats(binary);\n\t}\n}\n\nexport function parseRtpStreamRecvStats(\n\tbinary: FbsRtpStream.Stats\n): RtpStreamRecvStats {\n\tconst recvStats = new FbsRtpStream.RecvStats();\n\tconst baseStats = new FbsRtpStream.BaseStats();\n\n\tbinary.data(recvStats);\n\trecvStats.base()!.data(baseStats);\n\n\tconst base = parseBaseStreamStats(baseStats);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'inbound-rtp',\n\t\tbyteCount: Number(recvStats.byteCount()),\n\t\tpacketCount: Number(recvStats.packetCount()),\n\t\tbitrate: Number(recvStats.bitrate()),\n\t\tbitrateByLayer: parseBitrateByLayer(recvStats),\n\t};\n}\n\nexport function parseSendStreamStats(\n\tbinary: FbsRtpStream.Stats\n): RtpStreamSendStats {\n\tconst sendStats = new FbsRtpStream.SendStats();\n\tconst baseStats = new FbsRtpStream.BaseStats();\n\n\tbinary.data(sendStats);\n\tsendStats.base()!.data(baseStats);\n\n\tconst base = parseBaseStreamStats(baseStats);\n\n\treturn {\n\t\t...base,\n\t\ttype: 'outbound-rtp',\n\t\tbyteCount: Number(sendStats.byteCount()),\n\t\tpacketCount: Number(sendStats.packetCount()),\n\t\tbitrate: Number(sendStats.bitrate()),\n\t};\n}\n\nfunction parseBaseStreamStats(\n\tbinary: FbsRtpStream.BaseStats\n): BaseRtpStreamStats {\n\treturn {\n\t\ttimestamp: Number(binary.timestamp()),\n\t\tssrc: binary.ssrc(),\n\t\trtxSsrc: binary.rtxSsrc() ?? undefined,\n\t\trid: binary.rid() || undefined,\n\t\tkind:\n\t\t\tbinary.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video',\n\t\tmimeType: binary.mimeType()!,\n\t\tpacketsLost: Number(binary.packetsLost()),\n\t\tfractionLost: Number(binary.fractionLost()),\n\t\tjitter: Number(binary.jitter()),\n\t\tpacketsDiscarded: Number(binary.packetsDiscarded()),\n\t\tpacketsRetransmitted: Number(binary.packetsRetransmitted()),\n\t\tpacketsRepaired: Number(binary.packetsRepaired()),\n\t\tnackCount: Number(binary.nackCount()),\n\t\tnackPacketCount: Number(binary.nackPacketCount()),\n\t\tpliCount: Number(binary.pliCount()),\n\t\tfirCount: Number(binary.firCount()),\n\t\troundTripTime: binary.roundTripTime(),\n\t\trtxPacketsDiscarded: binary.rtxPacketsDiscarded()\n\t\t\t? Number(binary.rtxPacketsDiscarded())\n\t\t\t: undefined,\n\t\tscore: binary.score(),\n\t};\n}\n\nfunction parseBitrateByLayer(binary: FbsRtpStream.RecvStats): BitrateByLayer {\n\tif (binary.bitrateByLayerLength() === 0) {\n\t\treturn {};\n\t}\n\n\tconst bitRateByLayer: { [key: string]: number } = {};\n\n\tfor (let i = 0; i < binary.bitrateByLayerLength(); ++i) {\n\t\tconst layer: string = binary.bitrateByLayer(i)!.layer()!;\n\t\tconst bitrate = binary.bitrateByLayer(i)!.bitrate();\n\n\t\tbitRateByLayer[layer] = Number(bitrate);\n\t}\n\n\treturn bitRateByLayer;\n}\n"
  },
  {
    "path": "node/src/rtpStreamStatsTypes.ts",
    "content": "export type RtpStreamRecvStats = BaseRtpStreamStats & {\n\ttype: string;\n\tpacketCount: number;\n\tbyteCount: number;\n\tbitrate: number;\n\tbitrateByLayer: BitrateByLayer;\n};\n\nexport type RtpStreamSendStats = BaseRtpStreamStats & {\n\ttype: string;\n\tpacketCount: number;\n\tbyteCount: number;\n\tbitrate: number;\n};\n\nexport type BaseRtpStreamStats = {\n\ttimestamp: number;\n\tssrc: number;\n\trtxSsrc?: number;\n\trid?: string;\n\tkind: string;\n\tmimeType: string;\n\tpacketsLost: number;\n\tfractionLost: number;\n\tjitter: number;\n\tpacketsDiscarded: number;\n\tpacketsRetransmitted: number;\n\tpacketsRepaired: number;\n\tnackCount: number;\n\tnackPacketCount: number;\n\tpliCount: number;\n\tfirCount: number;\n\troundTripTime?: number;\n\trtxPacketsDiscarded?: number;\n\tscore: number;\n};\n\nexport type BitrateByLayer = { [key: string]: number };\n"
  },
  {
    "path": "node/src/scalabilityModesTypes.ts",
    "content": "export type ScalabilityMode = {\n\tspatialLayers: number;\n\ttemporalLayers: number;\n\tksvc: boolean;\n};\n"
  },
  {
    "path": "node/src/scalabilityModesUtils.ts",
    "content": "import { ScalabilityMode } from './scalabilityModesTypes';\n\nconst ScalabilityModeRegex = new RegExp(\n\t'^[LS]([1-9]\\\\d{0,1})T([1-9]\\\\d{0,1})(_KEY)?'\n);\n\nexport function parseScalabilityMode(\n\tscalabilityMode?: string\n): ScalabilityMode {\n\tconst match = ScalabilityModeRegex.exec(scalabilityMode ?? '');\n\n\tif (match) {\n\t\treturn {\n\t\t\tspatialLayers: Number(match[1]),\n\t\t\ttemporalLayers: Number(match[2]),\n\t\t\tksvc: Boolean(match[3]),\n\t\t};\n\t} else {\n\t\treturn {\n\t\t\tspatialLayers: 1,\n\t\t\ttemporalLayers: 1,\n\t\t\tksvc: false,\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "node/src/sctpParametersFbsUtils.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport type {\n\tSctpStreamParameters,\n\tSctpParametersDump,\n} from './sctpParametersTypes';\nimport * as FbsSctpParameters from './fbs/sctp-parameters';\n\nexport function parseSctpParametersDump(\n\tbinary: FbsSctpParameters.SctpParameters\n): SctpParametersDump {\n\treturn {\n\t\tport: binary.port(),\n\t\tOS: binary.os(),\n\t\tMIS: binary.mis(),\n\t\tmaxMessageSize: binary.maxMessageSize(),\n\t\tsendBufferSize: binary.sendBufferSize(),\n\t\tsctpBufferedAmount: binary.sctpBufferedAmount(),\n\t\tisDataChannel: binary.isDataChannel(),\n\t};\n}\n\nexport function serializeSctpStreamParameters(\n\tbuilder: flatbuffers.Builder,\n\tparameters: SctpStreamParameters\n): number {\n\treturn FbsSctpParameters.SctpStreamParameters.createSctpStreamParameters(\n\t\tbuilder,\n\t\tparameters.streamId,\n\t\tparameters.ordered!,\n\t\ttypeof parameters.maxPacketLifeTime === 'number'\n\t\t\t? parameters.maxPacketLifeTime\n\t\t\t: null,\n\t\ttypeof parameters.maxRetransmits === 'number'\n\t\t\t? parameters.maxRetransmits\n\t\t\t: null\n\t);\n}\n\nexport function parseSctpStreamParameters(\n\tparameters: FbsSctpParameters.SctpStreamParameters\n): SctpStreamParameters {\n\treturn {\n\t\tstreamId: parameters.streamId(),\n\t\tordered: parameters.ordered()!,\n\t\tmaxPacketLifeTime:\n\t\t\tparameters.maxPacketLifeTime() !== null\n\t\t\t\t? parameters.maxPacketLifeTime()!\n\t\t\t\t: undefined,\n\t\tmaxRetransmits:\n\t\t\tparameters.maxRetransmits() !== null\n\t\t\t\t? parameters.maxRetransmits()!\n\t\t\t\t: undefined,\n\t};\n}\n"
  },
  {
    "path": "node/src/sctpParametersTypes.ts",
    "content": "export type SctpCapabilities = {\n\tnumStreams: NumSctpStreams;\n};\n\n/**\n * Both OS and MIS are part of the SCTP INIT+ACK handshake. OS refers to the\n * initial number of outgoing SCTP streams that the server side transport creates\n * (to be used by DataConsumers), while MIS refers to the maximum number of\n * incoming SCTP streams that the server side transport can receive (to be used\n * by DataProducers). So, if the server side transport will just be used to\n * create data producers (but no data consumers), OS can be low (~1).\n *\n * mediasoup-client provides specific per browser/version OS and MIS values via\n * the device.sctpCapabilities getter. However those values must be reversed\n * when provided to the mediasoup server transport.\n */\nexport type NumSctpStreams = {\n\t/**\n\t * Initially requested number of outgoing SCTP streams.\n\t */\n\tOS: number;\n\n\t/**\n\t * Maximum number of incoming SCTP streams.\n\t */\n\tMIS: number;\n};\n\nexport type SctpParameters = {\n\t/**\n\t * Must always equal 5000.\n\t */\n\tport: number;\n\n\t/**\n\t * Initially requested number of outgoing SCTP streams.\n\t */\n\tOS: number;\n\n\t/**\n\t * Maximum number of incoming SCTP streams.\n\t */\n\tMIS: number;\n\n\t/**\n\t * Maximum allowed size for SCTP messages.\n\t */\n\tmaxMessageSize: number;\n};\n\n/**\n * SCTP stream parameters describe the reliability of a certain SCTP stream.\n * If ordered is true then maxPacketLifeTime and maxRetransmits must be\n * false.\n * If ordered if false, only one of maxPacketLifeTime or maxRetransmits\n * can be true.\n */\nexport type SctpStreamParameters = {\n\t/**\n\t * SCTP stream id.\n\t */\n\tstreamId: number;\n\n\t/**\n\t * Whether data messages must be received in order. If true the messages will\n\t * be sent reliably. Default true.\n\t */\n\tordered?: boolean;\n\n\t/**\n\t * When ordered is false indicates the time (in milliseconds) after which a\n\t * SCTP packet will stop being retransmitted.\n\t */\n\tmaxPacketLifeTime?: number;\n\n\t/**\n\t * When ordered is false indicates the maximum number of times a packet will\n\t * be retransmitted.\n\t */\n\tmaxRetransmits?: number;\n};\n\nexport type SctpParametersDump = {\n\tport: number;\n\tOS: number;\n\tMIS: number;\n\tmaxMessageSize: number;\n\tsendBufferSize: number;\n\tsctpBufferedAmount: number;\n\tisDataChannel: boolean;\n};\n"
  },
  {
    "path": "node/src/srtpParametersFbsUtils.ts",
    "content": "import type * as flatbuffers from 'flatbuffers';\nimport type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes';\nimport * as FbsSrtpParameters from './fbs/srtp-parameters';\n\nexport function cryptoSuiteFromFbs(\n\tbinary: FbsSrtpParameters.SrtpCryptoSuite\n): SrtpCryptoSuite {\n\tswitch (binary) {\n\t\tcase FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_256_GCM: {\n\t\t\treturn 'AEAD_AES_256_GCM';\n\t\t}\n\n\t\tcase FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_128_GCM: {\n\t\t\treturn 'AEAD_AES_128_GCM';\n\t\t}\n\n\t\tcase FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80: {\n\t\t\treturn 'AES_CM_128_HMAC_SHA1_80';\n\t\t}\n\n\t\tcase FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32: {\n\t\t\treturn 'AES_CM_128_HMAC_SHA1_32';\n\t\t}\n\t}\n}\n\nexport function cryptoSuiteToFbs(\n\tcryptoSuite: SrtpCryptoSuite\n): FbsSrtpParameters.SrtpCryptoSuite {\n\tswitch (cryptoSuite) {\n\t\tcase 'AEAD_AES_256_GCM': {\n\t\t\treturn FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_256_GCM;\n\t\t}\n\n\t\tcase 'AEAD_AES_128_GCM': {\n\t\t\treturn FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_128_GCM;\n\t\t}\n\n\t\tcase 'AES_CM_128_HMAC_SHA1_80': {\n\t\t\treturn FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80;\n\t\t}\n\n\t\tcase 'AES_CM_128_HMAC_SHA1_32': {\n\t\t\treturn FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32;\n\t\t}\n\n\t\tdefault: {\n\t\t\tthrow new TypeError(`invalid SrtpCryptoSuite: ${cryptoSuite}`);\n\t\t}\n\t}\n}\n\nexport function parseSrtpParameters(\n\tbinary: FbsSrtpParameters.SrtpParameters\n): SrtpParameters {\n\treturn {\n\t\tcryptoSuite: cryptoSuiteFromFbs(binary.cryptoSuite()),\n\t\tkeyBase64: binary.keyBase64()!,\n\t};\n}\n\nexport function serializeSrtpParameters(\n\tbuilder: flatbuffers.Builder,\n\tsrtpParameters: SrtpParameters\n): number {\n\tconst keyBase64Offset = builder.createString(srtpParameters.keyBase64);\n\n\treturn FbsSrtpParameters.SrtpParameters.createSrtpParameters(\n\t\tbuilder,\n\t\tcryptoSuiteToFbs(srtpParameters.cryptoSuite),\n\t\tkeyBase64Offset\n\t);\n}\n"
  },
  {
    "path": "node/src/srtpParametersTypes.ts",
    "content": "/**\n * SRTP parameters.\n */\nexport type SrtpParameters = {\n\t/**\n\t * Encryption and authentication transforms to be used.\n\t */\n\tcryptoSuite: SrtpCryptoSuite;\n\n\t/**\n\t * SRTP keying material (master key and salt) in Base64.\n\t */\n\tkeyBase64: string;\n};\n\n/**\n * SRTP crypto suite.\n */\nexport type SrtpCryptoSuite =\n\t| 'AEAD_AES_256_GCM'\n\t| 'AEAD_AES_128_GCM'\n\t| 'AES_CM_128_HMAC_SHA1_80'\n\t| 'AES_CM_128_HMAC_SHA1_32';\n"
  },
  {
    "path": "node/src/supportedRtpCapabilities.ts",
    "content": "import type { RouterRtpCapabilities } from './rtpParametersTypes';\n\nconst supportedRtpCapabilities: RouterRtpCapabilities = {\n\tcodecs: [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\trtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 4,\n\t\t\t// Quad channel.\n\t\t\tparameters: {\n\t\t\t\tchannel_mapping: '0,1,2,3',\n\t\t\t\tnum_streams: 2,\n\t\t\t\tcoupled_streams: 2,\n\t\t\t},\n\t\t\trtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 6,\n\t\t\t// 5.1.\n\t\t\tparameters: {\n\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\tnum_streams: 4,\n\t\t\t\tcoupled_streams: 2,\n\t\t\t},\n\t\t\trtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 8,\n\t\t\t// 7.1.\n\t\t\tparameters: {\n\t\t\t\tchannel_mapping: '0,6,1,2,3,4,5,7',\n\t\t\t\tnum_streams: 5,\n\t\t\t\tcoupled_streams: 3,\n\t\t\t},\n\t\t\trtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/PCMU',\n\t\t\tpreferredPayloadType: 0,\n\t\t\tclockRate: 8000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/PCMA',\n\t\t\tpreferredPayloadType: 8,\n\t\t\tclockRate: 8000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/ISAC',\n\t\t\tclockRate: 32000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/ISAC',\n\t\t\tclockRate: 16000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/G722',\n\t\t\tpreferredPayloadType: 9,\n\t\t\tclockRate: 8000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/iLBC',\n\t\t\tclockRate: 8000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/SILK',\n\t\t\tclockRate: 24000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/SILK',\n\t\t\tclockRate: 16000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/SILK',\n\t\t\tclockRate: 12000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/SILK',\n\t\t\tclockRate: 8000,\n\t\t\trtcpFeedback: [{ type: 'transport-cc' }],\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/CN',\n\t\t\tpreferredPayloadType: 13,\n\t\t\tclockRate: 32000,\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/CN',\n\t\t\tpreferredPayloadType: 13,\n\t\t\tclockRate: 16000,\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/CN',\n\t\t\tpreferredPayloadType: 13,\n\t\t\tclockRate: 8000,\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/telephone-event',\n\t\t\tclockRate: 48000,\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/telephone-event',\n\t\t\tclockRate: 32000,\n\t\t},\n\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/telephone-event',\n\t\t\tclockRate: 16000,\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/telephone-event',\n\t\t\tclockRate: 8000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack' },\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t{ type: 'transport-cc' },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP9',\n\t\t\tclockRate: 90000,\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack' },\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t{ type: 'transport-cc' },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack' },\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t{ type: 'transport-cc' },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/AV1',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack' },\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t{ type: 'transport-cc' },\n\t\t\t],\n\t\t},\n\t],\n\theaderExtensions: [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tpreferredId: 1,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tpreferredId: 1,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',\n\t\t\tpreferredId: 2,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'recvonly',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id',\n\t\t\tpreferredId: 3,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'recvonly',\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\tpreferredId: 4,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\tpreferredId: 4,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t// NOTE: For audio we just enable transport-wide-cc-01 when receiving media.\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',\n\t\t\tpreferredId: 5,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'recvonly',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',\n\t\t\tpreferredId: 5,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\tpreferredId: 6,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t\tpreferredId: 7,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'recvonly',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tpreferredId: 8,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tpreferredId: 9,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tpreferredId: 10,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tpreferredId: 10,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tpreferredId: 11,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tpreferredId: 11,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tpreferredId: 12,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tpreferredId: 12,\n\t\t\tpreferredEncrypt: false,\n\t\t\tdirection: 'sendrecv',\n\t\t},\n\t],\n};\n\nexport { supportedRtpCapabilities };\n"
  },
  {
    "path": "node/src/test/data/dtls-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIUXy3udbf5+Rvhx3MaNGn7vj+zi+UwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA2MjQyMTQwNTZaFw0yMzA2\nMjQyMTQwNTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDLXJlS6702GKmSsfOFWzMP2+NvlgNySLiqnAf6bBuX\nVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf21qaY7VTUbUag6i+Ghc4I15ZRzONP\ngv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa+h1spKyBHs96pFQQzpVNAnGSifk7\nhPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8KlJgm1fIbxdtVn60T7hz54/OuikHBD\njgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjptUGA3BUISf66SS31rP+ol2frlOY9\nQZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQKMO76LaFcRnkbO0GxDnKw/T+1LGqb\nJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtOzDxueJT9fXBAW5etrp7KvWmDY6/5\nHn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXDtZzACRaZ9hjCe8WmzPXVEEOvVIP9\nh5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKuf/ax+0gpeu5VpHNkvOIUyM/QTiBi\nD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT112E3RJJueYTc3IzUPefiHFmCfFZ\neUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7QpKOLIJik9tPm8hScggwLvq0YIwnvN\nVQIDAQABo1MwUTAdBgNVHQ4EFgQUe/9tLjutYDz+myVgALKjHDuXm2QwHwYDVR0j\nBBgwFoAUe/9tLjutYDz+myVgALKjHDuXm2QwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQsFAAOCAgEAXi18lNqjeOXV6P/snVs4b34OiWpkRlDxKKMG95rdRKeF\nkEmPpO7T293nXCGvFmGfME6KBhio4w0MVMlbtC5TVdTFJk6mSgkCEtncA5yEv8Ga\nw2HCoEWfkOpea3S1XL9i5EWVPKvJG/rJ3YMZuh1BvppfC73dHVFSdik5SXNCGqEy\n9OuhJHbpbdlMuwFTLKwQCKDwh5Yvzd0eASyYlJ6Ytpf7TLCc3nvUS9haSOKEfnHQ\nv3TLt2WA+xHK7XIj9qoYuWIsnkXAWIsSJy/utJrDhtym7BcxB+ss7doHkS0LCTHd\nDiAhJwvTMKPYSRA55oF9iejynrcBOidH+tQxYMXHaDisNtH+6ZOtxkcLleneKhNB\n9EAMw9qeyiVl+MHDQi+sA5ksMfVoXzvxqObNGSz9g5z1AfnNuiaX1z9ajXsiHJ5e\n7EQFVCU4Id519RcSEUY2qcKTNMBPyXNbTQfd/oV1C6pzF01tVaNpl12dsXdx+JTh\n9vbK9tI2U+jzIb0Y8ersKrAjRQO/1lfIbjdJ+e0rFlxX1+SmlKlcArOfd8PSuifx\nwbzCJeRjFdOdf2D6mVHO0uMghjnVTHxB3KX6QonKOFzkupwWBeVqginXm4pEaUhK\nnssuSpkKrVshQ3RSEq3H9yRdQQ0qqwdOd3dISEqucVvaEsxhUwHBX/R5p8W0OCo=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "node/src/test/data/dtls-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDLXJlS6702GKmS\nsfOFWzMP2+NvlgNySLiqnAf6bBuXVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf2\n1qaY7VTUbUag6i+Ghc4I15ZRzONPgv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa\n+h1spKyBHs96pFQQzpVNAnGSifk7hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8Kl\nJgm1fIbxdtVn60T7hz54/OuikHBDjgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjp\ntUGA3BUISf66SS31rP+ol2frlOY9QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQK\nMO76LaFcRnkbO0GxDnKw/T+1LGqbJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtO\nzDxueJT9fXBAW5etrp7KvWmDY6/5Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXD\ntZzACRaZ9hjCe8WmzPXVEEOvVIP9h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKu\nf/ax+0gpeu5VpHNkvOIUyM/QTiBiD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT\n112E3RJJueYTc3IzUPefiHFmCfFZeUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7Qp\nKOLIJik9tPm8hScggwLvq0YIwnvNVQIDAQABAoICAAd5PTdDa5431M+L06fEfMFp\n8tdQe4rxKjw25MIqw+7RaE0U6SB1Ei2M1chblACVGgtXKT0oCBWAo32aUOAQ5Muz\nwmM6iAmZFEPV7HPQJFBxP4finqjYq7Hpf2WuqRHdjx6jBMndOlhH/a/KHle2S5Kp\nN7XJoT9G4EzGuLbKdErgLXfiF5bReGwVZqREHPOI7CLWO5gfxgWUoEYiejvdujXY\niKo7hrr5su2OWfiM91s8Nj2jWfsoCTEiI93xBcG3n+W1xTSzlLdt8z53h1M9g1Zd\nJcvh0ZsUQwcGnW6Wd8mrhbYgOHBxjAVnJLqupX+1L1Ii8ANMDMyK/P104+t0zte9\nBpuBzerm9h69UQq7d728YKW2KfY4M5+oLG2l4PKH6YDhoymIso38TYMixutkXpIs\nxZXATFA82sZlcpbSjLJypeJnKxBCbKxu8eYTq5K2SDdq8r82DwZZT/N8FfQMGZoO\nYYtP2KfUSxc2azpQtEHmB00fiPXgCdJNU2I8+J84Ve/ltBDlJcNkJN09h5ob9q/g\nQ8qJQRrzpLedBRhyqharYyDS9oGDUlR9OfmR7vFzUNP5npgNts0TDGy/JpVdY9aC\nYmqWsC8c/Kmms7X+4KaXMsBEpaVNSx9FsoQiPi1CMdy/KpWGcshusdVwgdklrOnz\niKBvvMNvP+gu7bV+uoR3AoIBAQDqBkUaMbVUVup94WqOIFjEl13CQwVwLgmLQaw0\n9BjPkv8BaPiMUfg7ANqEi8V+iJX5m27fcsvWIMUKBe0FvLdo/kH/NahNxD0bub3S\nOikGeJEo0QDsIKhVXTLjYJ/N+qk2P3WYWy/7yGV3kVDkEam0oebmdYNQsfpXK+wu\nGt0hpjU2RDMB2NCmtLHQXCI5DUxX0CtfJD5JTop9tLTRJKnputsIsQZPSmNSTuTl\nFVO0JdyJNv97Xhc8YUzwTfwl73EW7srcJNKO2ZwkzOZ8EghaEa1c5BZ6oV2MWe5f\nHIrRbW9LCYnBavoTC7VGWYkKiklAm5pRfvj9fQylv4tiSRUfAoIBAQDedTl3fAFf\ni/8ak0WfJKn/H0Z2W3AcgeChyBAWdrOrTDZUdhV0olcIT7m1v2qbWQju8boPdJiY\nBqG2DD3CZHS+qETt4lP5r+9j2gvy5g3wdQ4bYpjyr2hRXkwMK2yYQbXCmgmDETb+\nyNTj2avhAA32BmTH9QyxXSGN7ympVwEP1kDcXHM8vDGL7FvGGdRPFg0h4LKHrCHa\nxCQEWPjBv8oPo8Gb2ateSZjeWCraRhTXd7yVWC5s+3uEfW/h+g/QJVrRh2p8ipGv\nzlWDR1BaF+t4racWHFeftrSkc3ZpN/eAVyLXBSTo7plOx8tFujrakJedmZssrHDc\nRc0JeDOrjnsLAoIBAQDGXoo0qe4Kj6I1Ed5Amyqjear//8+cR2nPoNtYB5EAYpnF\nmDUWvGStnwubTt8ZYq295wMUZTpjR2O+G0fOlSji1qMasWD4il9CIS/GA4bC9XAW\nKROfFA+cTGPWWREciFzmnuQPQTxrMHLR51up907izlnq/7FPtY1+VrzcV+kZnMl+\nNlEGP8KdjI0tEOvxcFRGGy6odxBVEz5RT9v1bB6bAMiplWTD0UpfeoCLrohFK9LE\nfNoSuK75f4C4MWKKxWwXBFLwSEYy0EKK7yRwBtkNf+5zzuM/D4k8bv6foJIK87hi\n4rLiQMu5WTNPbpW7WXy+RyeH7Rkhxd3yoWqE5W4BAoIBADjNOdU2hqs89fB1NkvC\nct2/wKAsDN5ak1771I/H02yj0yOR2zyizxJCOSsdKz1raIqKknWr0eLPnq77RTHD\nsMOV97O+HK8eq0OVw4NMFrcVTHrVnDQrcbmFGGnrFJlz/dMovdEHrkE0Spe7VtXm\ny6nMTCN6gLkxDIZPURX6Lz05+enKeWpCq2wM+AoHQlzHRqcl1rAp1aMkfgXWKf5e\n2FtR9veyhr1WkYAEhzygtGWoHzELCR+uvwU/ejf7P9poD15880XFpBl91/vjU7MN\ndISl4ooUxpLzdgCfstZ/AeV1WmII4DnR4rdo8JBnUuvIC86kEClCBrdX41jNpnPh\nt60CggEAQnioYh4vwJctZENvO6nKlexCui2sQmHi4hWwzzA0s8tG2iG++nWXUEV+\nT+SBuCnIZMhnXlFIbqx+gjke3xLxJVbsgeRHDwDMZbkcaE35L1ZdAjIhuWgoNa20\nRSXApmp376VNEoU3lJH6jK0Wa67N9IaD3P5Mb+jekU16beqVgMYWf/YXycvD6bEp\nL9CODZuTPS/Ue1waBP7bKWAH8PRKAdXQACkn+aRBqYm9a5o7Z5jD/Qzo9ia5VaK4\nj+EQ0Gk4BaVBnvIu4LQ/HKuZm4v1NWNy3UhFuoTAzxNoCrQamYII/EaCMqiLATGH\njBJvmV3y/KoVcZGzNGFVB29Ns8VK1g==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "node/src/test/test-ActiveSpeakerObserver.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, ActiveSpeakerObserverEvents } from '../types';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t]),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('router.createActiveSpeakerObserver() succeeds', async () => {\n\tconst onObserverNewRtpObserver = jest.fn();\n\n\tctx.router!.observer.once('newrtpobserver', onObserverNewRtpObserver);\n\n\tconst activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver();\n\n\texpect(onObserverNewRtpObserver).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewRtpObserver).toHaveBeenCalledWith(activeSpeakerObserver);\n\texpect(typeof activeSpeakerObserver.id).toBe('string');\n\texpect(activeSpeakerObserver.closed).toBe(false);\n\texpect(activeSpeakerObserver.type).toBe('activespeaker');\n\texpect(activeSpeakerObserver.paused).toBe(false);\n\texpect(activeSpeakerObserver.appData).toEqual({});\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\trtpObserverIds: [activeSpeakerObserver.id],\n\t});\n}, 2000);\n\ntest('router.createActiveSpeakerObserver() with wrong arguments rejects with TypeError', async () => {\n\tawait expect(\n\t\tctx.router!.createActiveSpeakerObserver(\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t{ interval: false }\n\t\t)\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createActiveSpeakerObserver(\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t{ appData: 'NOT-AN-OBJECT' }\n\t\t)\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('activeSpeakerObserver.pause() and resume() succeed', async () => {\n\tconst activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver();\n\n\tawait activeSpeakerObserver.pause();\n\n\texpect(activeSpeakerObserver.paused).toBe(true);\n\n\tawait activeSpeakerObserver.resume();\n\n\texpect(activeSpeakerObserver.paused).toBe(false);\n}, 2000);\n\ntest('activeSpeakerObserver.close() succeeds', async () => {\n\tconst activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver({\n\t\tinterval: 500,\n\t});\n\n\tlet dump = await ctx.router!.dump();\n\n\texpect(dump.rtpObserverIds.length).toBe(1);\n\n\tactiveSpeakerObserver.close();\n\n\texpect(activeSpeakerObserver.closed).toBe(true);\n\n\tdump = await ctx.router!.dump();\n\n\texpect(dump.rtpObserverIds.length).toBe(0);\n}, 2000);\n\ntest('ActiveSpeakerObserver emits \"routerclose\" if Router is closed', async () => {\n\tconst activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver();\n\n\tconst promise = enhancedOnce<ActiveSpeakerObserverEvents>(\n\t\tactiveSpeakerObserver,\n\t\t'routerclose'\n\t);\n\n\tctx.router!.close();\n\tawait promise;\n\n\texpect(activeSpeakerObserver.closed).toBe(true);\n}, 2000);\n\ntest('ActiveSpeakerObserver emits \"routerclose\" if Worker is closed', async () => {\n\tconst activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver();\n\n\tconst promise = enhancedOnce<ActiveSpeakerObserverEvents>(\n\t\tactiveSpeakerObserver,\n\t\t'routerclose'\n\t);\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(activeSpeakerObserver.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-AudioLevelObserver.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, AudioLevelObserverEvents } from '../types';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t]),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('router.createAudioLevelObserver() succeeds', async () => {\n\tconst onObserverNewRtpObserver = jest.fn();\n\n\tctx.router!.observer.once('newrtpobserver', onObserverNewRtpObserver);\n\n\tconst audioLevelObserver = await ctx.router!.createAudioLevelObserver();\n\n\texpect(onObserverNewRtpObserver).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewRtpObserver).toHaveBeenCalledWith(audioLevelObserver);\n\texpect(typeof audioLevelObserver.id).toBe('string');\n\texpect(audioLevelObserver.closed).toBe(false);\n\texpect(audioLevelObserver.type).toBe('audiolevel');\n\texpect(audioLevelObserver.paused).toBe(false);\n\texpect(audioLevelObserver.appData).toEqual({});\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\trtpObserverIds: [audioLevelObserver.id],\n\t});\n}, 2000);\n\ntest('router.createAudioLevelObserver() with wrong arguments rejects with TypeError', async () => {\n\tawait expect(\n\t\tctx.router!.createAudioLevelObserver({ maxEntries: 0 })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createAudioLevelObserver({ maxEntries: -10 })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createAudioLevelObserver({ threshold: 'foo' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createAudioLevelObserver({ interval: false })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createAudioLevelObserver({ appData: 'NOT-AN-OBJECT' })\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('audioLevelObserver.pause() and resume() succeed', async () => {\n\tconst audioLevelObserver = await ctx.router!.createAudioLevelObserver();\n\n\tawait audioLevelObserver.pause();\n\n\texpect(audioLevelObserver.paused).toBe(true);\n\n\tawait audioLevelObserver.resume();\n\n\texpect(audioLevelObserver.paused).toBe(false);\n}, 2000);\n\ntest('audioLevelObserver.close() succeeds', async () => {\n\tconst audioLevelObserver = await ctx.router!.createAudioLevelObserver({\n\t\tmaxEntries: 8,\n\t});\n\n\tlet dump = await ctx.router!.dump();\n\n\texpect(dump.rtpObserverIds.length).toBe(1);\n\n\taudioLevelObserver.close();\n\n\texpect(audioLevelObserver.closed).toBe(true);\n\n\tdump = await ctx.router!.dump();\n\n\texpect(dump.rtpObserverIds.length).toBe(0);\n}, 2000);\n\ntest('AudioLevelObserver emits \"routerclose\" if Router is closed', async () => {\n\tconst audioLevelObserver = await ctx.router!.createAudioLevelObserver();\n\n\tconst promise = enhancedOnce<AudioLevelObserverEvents>(\n\t\taudioLevelObserver,\n\t\t'routerclose'\n\t);\n\n\tctx.router!.close();\n\tawait promise;\n\n\texpect(audioLevelObserver.closed).toBe(true);\n}, 2000);\n\ntest('AudioLevelObserver emits \"routerclose\" if Worker is closed', async () => {\n\tconst audioLevelObserver = await ctx.router!.createAudioLevelObserver();\n\n\tconst promise = enhancedOnce<AudioLevelObserverEvents>(\n\t\taudioLevelObserver,\n\t\t'routerclose'\n\t);\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(audioLevelObserver.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-Consumer.ts",
    "content": "import * as flatbuffers from 'flatbuffers';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, ConsumerEvents } from '../types';\nimport type { ConsumerImpl } from '../Consumer';\nimport { UnsupportedError } from '../errors';\nimport * as utils from '../utils';\nimport {\n\tNotification,\n\tBody as NotificationBody,\n\tEvent,\n} from '../fbs/notification';\nimport * as FbsConsumer from '../fbs/consumer';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\taudioProducerOptions: mediasoup.types.ProducerOptions;\n\tvideoProducerOptions: mediasoup.types.ProducerOptions;\n\tconsumerDeviceCapabilities: mediasoup.types.RtpCapabilities;\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\twebRtcTransport1?: mediasoup.types.WebRtcTransport;\n\twebRtcTransport2?: mediasoup.types.WebRtcTransport;\n\taudioProducer?: mediasoup.types.Producer;\n\tvideoProducer?: mediasoup.types.Producer;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t]),\n\taudioProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpayloadType: 111,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tuseinbandfec: 1,\n\t\t\t\t\t\tusedtx: 1,\n\t\t\t\t\t\tfoo: 222.222,\n\t\t\t\t\t\tbar: '333',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tid: 12,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [{ ssrc: 11111111 }],\n\t\t\trtcp: {\n\t\t\t\tcname: 'FOOBAR',\n\t\t\t},\n\t\t\tmsid: '1111-1111-1111-1111 2222-2222-2222-2222',\n\t\t},\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n\tvideoProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'video',\n\t\trtpParameters: {\n\t\t\tmid: 'VIDEO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t},\n\t\t\t\t\trtcpFeedback: [\n\t\t\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\tpayloadType: 113,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: { apt: 112 },\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\t\tid: 13,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [\n\t\t\t\t{ ssrc: 22222222, scalabilityMode: 'L1T5', rtx: { ssrc: 22222223 } },\n\t\t\t\t{ ssrc: 22222224, scalabilityMode: 'L1T5', rtx: { ssrc: 22222225 } },\n\t\t\t\t{ ssrc: 22222226, scalabilityMode: 'L1T5', rtx: { ssrc: 22222227 } },\n\t\t\t\t{ ssrc: 22222228, scalabilityMode: 'L1T5', rtx: { ssrc: 22222229 } },\n\t\t\t],\n\t\t\trtcp: {\n\t\t\t\tcname: 'FOOBAR',\n\t\t\t},\n\t\t},\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n\tconsumerDeviceCapabilities: utils.deepFreeze<mediasoup.types.RtpCapabilities>(\n\t\t{\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t\trtcpFeedback: [{ type: 'nack', parameter: '' }],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/H264',\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\tpreferredPayloadType: 101,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t},\n\t\t\t\t\trtcpFeedback: [\n\t\t\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\tpreferredPayloadType: 102,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tapt: 101,\n\t\t\t\t\t},\n\t\t\t\t\trtcpFeedback: [],\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tpreferredId: 1,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tpreferredId: 1,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',\n\t\t\t\t\tpreferredId: 2,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\t\t\tpreferredId: 4,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\t\t\tpreferredId: 4,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tpreferredId: 6,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\t\tpreferredId: 8,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\t\t\tpreferredId: 9,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n\tctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tctx.audioProducer = await ctx.webRtcTransport1.produce(\n\t\tctx.audioProducerOptions\n\t);\n\tctx.videoProducer = await ctx.webRtcTransport1.produce(\n\t\tctx.videoProducerOptions\n\t);\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('transport.consume() succeeds', async () => {\n\tconst onObserverNewConsumer1 = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer1);\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: ctx.audioProducer!.id,\n\t\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\t})\n\t).toBe(true);\n\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\tappData: { baz: 'LOL' },\n\t});\n\n\texpect(onObserverNewConsumer1).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewConsumer1).toHaveBeenCalledWith(audioConsumer);\n\texpect(typeof audioConsumer.id).toBe('string');\n\texpect(audioConsumer.producerId).toBe(ctx.audioProducer!.id);\n\texpect(audioConsumer.closed).toBe(false);\n\texpect(audioConsumer.kind).toBe('audio');\n\texpect(typeof audioConsumer.rtpParameters).toBe('object');\n\texpect(audioConsumer.rtpParameters.mid).toBe('0');\n\texpect(audioConsumer.rtpParameters.codecs.length).toBe(1);\n\texpect(audioConsumer.rtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'audio/opus',\n\t\tpayloadType: 100,\n\t\tclockRate: 48000,\n\t\tchannels: 2,\n\t\tparameters: {\n\t\t\tuseinbandfec: 1,\n\t\t\tusedtx: 1,\n\t\t\tfoo: 222.222,\n\t\t\tbar: '333',\n\t\t},\n\t\trtcpFeedback: [],\n\t});\n\texpect(audioConsumer.rtpParameters.msid).toBe(\n\t\t'1111-1111-1111-1111 2222-2222-2222-2222'\n\t);\n\texpect(audioConsumer.type).toBe('simple');\n\texpect(audioConsumer.paused).toBe(false);\n\texpect(audioConsumer.producerPaused).toBe(false);\n\texpect(audioConsumer.priority).toBe(1);\n\texpect(audioConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 0,\n\t\tproducerScores: [0],\n\t});\n\texpect(audioConsumer.preferredLayers).toBeUndefined();\n\texpect(audioConsumer.currentLayers).toBeUndefined();\n\texpect(audioConsumer.appData).toEqual({ baz: 'LOL' });\n\n\tconst dump1 = await ctx.router!.dump();\n\n\texpect(dump1.mapProducerIdConsumerIds).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: ctx.audioProducer!.id, values: [audioConsumer.id] },\n\t\t])\n\t);\n\n\texpect(dump1.mapConsumerIdProducerId).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: audioConsumer.id, value: ctx.audioProducer!.id },\n\t\t])\n\t);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tproducerIds: [],\n\t\tconsumerIds: [audioConsumer.id],\n\t});\n\n\tconst onObserverNewConsumer2 = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer2);\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: ctx.videoProducer!.id,\n\t\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\t})\n\t).toBe(true);\n\n\t// Pause videoProducer.\n\tawait ctx.videoProducer!.pause();\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\tpaused: true,\n\t\tpreferredLayers: { spatialLayer: 12, temporalLayer: 0 },\n\t\tappData: { baz: 'LOL' },\n\t});\n\n\texpect(onObserverNewConsumer2).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewConsumer2).toHaveBeenCalledWith(videoConsumer);\n\texpect(typeof videoConsumer.id).toBe('string');\n\texpect(videoConsumer.producerId).toBe(ctx.videoProducer!.id);\n\texpect(videoConsumer.closed).toBe(false);\n\texpect(videoConsumer.kind).toBe('video');\n\texpect(typeof videoConsumer.rtpParameters).toBe('object');\n\texpect(videoConsumer.rtpParameters.mid).toBe('1');\n\texpect(videoConsumer.rtpParameters.codecs.length).toBe(2);\n\texpect(videoConsumer.rtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'video/H264',\n\t\tpayloadType: 103,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\t'packetization-mode': 1,\n\t\t\t'profile-level-id': '4d0032',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t],\n\t});\n\texpect(videoConsumer.rtpParameters.codecs[1]).toEqual({\n\t\tmimeType: 'video/rtx',\n\t\tpayloadType: 104,\n\t\tclockRate: 90000,\n\t\tparameters: { apt: 103 },\n\t\trtcpFeedback: [],\n\t});\n\texpect(videoConsumer.rtpParameters.msid).toBe(undefined);\n\texpect(videoConsumer.type).toBe('simulcast');\n\texpect(videoConsumer.paused).toBe(true);\n\texpect(videoConsumer.producerPaused).toBe(true);\n\texpect(videoConsumer.priority).toBe(1);\n\texpect(videoConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 0,\n\t\tproducerScores: [0, 0, 0, 0],\n\t});\n\texpect(videoConsumer.preferredLayers).toEqual({\n\t\tspatialLayer: 3,\n\t\ttemporalLayer: 0,\n\t});\n\texpect(videoConsumer.currentLayers).toBeUndefined();\n\texpect(videoConsumer.appData).toEqual({ baz: 'LOL' });\n\n\tconst onObserverNewConsumer3 = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer3);\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: ctx.videoProducer!.id,\n\t\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\t})\n\t).toBe(true);\n\n\tconst videoPipeConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\tpipe: true,\n\t});\n\n\texpect(onObserverNewConsumer3).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewConsumer3).toHaveBeenCalledWith(videoPipeConsumer);\n\texpect(typeof videoPipeConsumer.id).toBe('string');\n\texpect(videoPipeConsumer.producerId).toBe(ctx.videoProducer!.id);\n\texpect(videoPipeConsumer.closed).toBe(false);\n\texpect(videoPipeConsumer.kind).toBe('video');\n\texpect(typeof videoPipeConsumer.rtpParameters).toBe('object');\n\texpect(videoPipeConsumer.rtpParameters.mid).toBeUndefined();\n\texpect(videoPipeConsumer.rtpParameters.codecs.length).toBe(2);\n\texpect(videoPipeConsumer.rtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'video/H264',\n\t\tpayloadType: 103,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\t'packetization-mode': 1,\n\t\t\t'profile-level-id': '4d0032',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t],\n\t});\n\texpect(videoPipeConsumer.rtpParameters.codecs[1]).toEqual({\n\t\tmimeType: 'video/rtx',\n\t\tpayloadType: 104,\n\t\tclockRate: 90000,\n\t\tparameters: { apt: 103 },\n\t\trtcpFeedback: [],\n\t});\n\texpect(videoPipeConsumer.type).toBe('pipe');\n\texpect(videoPipeConsumer.paused).toBe(false);\n\texpect(videoPipeConsumer.producerPaused).toBe(true);\n\texpect(videoPipeConsumer.priority).toBe(1);\n\texpect(videoPipeConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 10,\n\t\tproducerScores: [0, 0, 0, 0],\n\t});\n\texpect(videoPipeConsumer.preferredLayers).toBeUndefined();\n\texpect(videoPipeConsumer.currentLayers).toBeUndefined();\n\texpect(videoPipeConsumer.appData).toEqual({});\n\n\tconst dump2 = await ctx.router!.dump();\n\n\texpect(Array.isArray(dump2.mapProducerIdConsumerIds)).toBe(true);\n\n\texpect(dump2.mapProducerIdConsumerIds).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{\n\t\t\t\tkey: ctx.audioProducer!.id,\n\t\t\t\tvalues: [audioConsumer.id],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkey: ctx.videoProducer!.id,\n\t\t\t\tvalues: expect.arrayContaining([\n\t\t\t\t\tvideoConsumer.id,\n\t\t\t\t\tvideoPipeConsumer.id,\n\t\t\t\t]),\n\t\t\t},\n\t\t])\n\t);\n\texpect(dump2.mapConsumerIdProducerId).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: audioConsumer.id, value: ctx.audioProducer!.id },\n\t\t\t{ key: videoConsumer.id, value: ctx.videoProducer!.id },\n\t\t\t{ key: videoPipeConsumer.id, value: ctx.videoProducer!.id },\n\t\t])\n\t);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tproducerIds: [],\n\t\tconsumerIds: expect.arrayContaining([\n\t\t\taudioConsumer.id,\n\t\t\tvideoConsumer.id,\n\t\t\tvideoPipeConsumer.id,\n\t\t]),\n\t});\n}, 2000);\n\ntest('transport.consume() with enableRtx succeeds', async () => {\n\tconst audioConsumer2 = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\tenableRtx: true,\n\t});\n\n\texpect(audioConsumer2.kind).toBe('audio');\n\texpect(audioConsumer2.rtpParameters.codecs.length).toBe(1);\n\texpect(audioConsumer2.rtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'audio/opus',\n\t\tpayloadType: 100,\n\t\tclockRate: 48000,\n\t\tchannels: 2,\n\t\tparameters: {\n\t\t\tuseinbandfec: 1,\n\t\t\tusedtx: 1,\n\t\t\tfoo: 222.222,\n\t\t\tbar: '333',\n\t\t},\n\t\trtcpFeedback: [{ type: 'nack', parameter: '' }],\n\t});\n}, 2000);\n\ntest('transport.consume() can be created with user provided mid', async () => {\n\tconst audioConsumer1 = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(audioConsumer1.rtpParameters.mid).toEqual(\n\t\texpect.stringMatching(/^[0-9]+/)\n\t);\n\n\tconst audioConsumer2 = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\tmid: 'custom-mid',\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(audioConsumer2.rtpParameters.mid).toBe('custom-mid');\n\n\tconst audioConsumer3 = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(audioConsumer3.rtpParameters.mid).toEqual(\n\t\texpect.stringMatching(/^[0-9]+/)\n\t);\n\texpect(Number(audioConsumer1.rtpParameters.mid) + 1).toBe(\n\t\tNumber(audioConsumer3.rtpParameters.mid)\n\t);\n}, 2000);\n\ntest('transport.consume() with incompatible rtpCapabilities rejects with UnsupportedError', async () => {\n\tlet invalidDeviceCapabilities: mediasoup.types.RtpCapabilities;\n\n\tinvalidDeviceCapabilities = {\n\t\tcodecs: [\n\t\t\t{\n\t\t\t\tkind: 'audio',\n\t\t\t\tmimeType: 'audio/ISAC',\n\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\tclockRate: 32000,\n\t\t\t\tchannels: 1,\n\t\t\t},\n\t\t],\n\t\theaderExtensions: [],\n\t};\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: ctx.audioProducer!.id,\n\t\t\trtpCapabilities: invalidDeviceCapabilities,\n\t\t})\n\t).toBe(false);\n\n\tawait expect(\n\t\tctx.webRtcTransport2!.consume({\n\t\t\tproducerId: ctx.audioProducer!.id,\n\t\t\trtpCapabilities: invalidDeviceCapabilities,\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n\n\tinvalidDeviceCapabilities = {\n\t\tcodecs: [],\n\t\theaderExtensions: [],\n\t};\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: ctx.audioProducer!.id,\n\t\t\trtpCapabilities: invalidDeviceCapabilities,\n\t\t})\n\t).toBe(false);\n\n\tawait expect(\n\t\tctx.webRtcTransport2!.consume({\n\t\t\tproducerId: ctx.audioProducer!.id,\n\t\t\trtpCapabilities: invalidDeviceCapabilities,\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n}, 2000);\n\ntest('consumer.dump() succeeds', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst dump1 = await audioConsumer.dump();\n\n\texpect(dump1.id).toBe(audioConsumer.id);\n\texpect(dump1.producerId).toBe(audioConsumer.producerId);\n\texpect(dump1.kind).toBe(audioConsumer.kind);\n\texpect(typeof dump1.rtpParameters).toBe('object');\n\texpect(Array.isArray(dump1.rtpParameters.codecs)).toBe(true);\n\texpect(dump1.rtpParameters.codecs.length).toBe(1);\n\texpect(dump1.rtpParameters.codecs[0]!.mimeType).toBe('audio/opus');\n\texpect(dump1.rtpParameters.codecs[0]!.payloadType).toBe(100);\n\texpect(dump1.rtpParameters.codecs[0]!.clockRate).toBe(48000);\n\texpect(dump1.rtpParameters.codecs[0]!.channels).toBe(2);\n\texpect(dump1.rtpParameters.codecs[0]!.parameters).toEqual({\n\t\tuseinbandfec: 1,\n\t\tusedtx: 1,\n\t\tfoo: 222.222,\n\t\tbar: '333',\n\t});\n\texpect(dump1.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([]);\n\texpect(Array.isArray(dump1.rtpParameters.headerExtensions)).toBe(true);\n\texpect(dump1.rtpParameters.headerExtensions!.length).toBe(3);\n\texpect(dump1.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tid: 1,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\tid: 4,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\tid: 6,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t]);\n\texpect(Array.isArray(dump1.rtpParameters.encodings)).toBe(true);\n\texpect(dump1.rtpParameters.encodings!.length).toBe(1);\n\texpect(dump1.rtpParameters.encodings).toEqual([\n\t\texpect.objectContaining({\n\t\t\tcodecPayloadType: 100,\n\t\t\tssrc: audioConsumer.rtpParameters.encodings![0]!.ssrc,\n\t\t}),\n\t]);\n\texpect(dump1.rtpParameters.msid).toBe(\n\t\t'1111-1111-1111-1111 2222-2222-2222-2222'\n\t);\n\texpect(dump1.type).toBe('simple');\n\texpect(Array.isArray(dump1.consumableRtpEncodings)).toBe(true);\n\texpect(dump1.consumableRtpEncodings!.length).toBe(1);\n\texpect(dump1.consumableRtpEncodings).toEqual([\n\t\texpect.objectContaining({\n\t\t\tssrc: ctx.audioProducer!.consumableRtpParameters.encodings![0]!.ssrc,\n\t\t}),\n\t]);\n\texpect(dump1.supportedCodecPayloadTypes).toEqual([100]);\n\texpect(dump1.paused).toBe(false);\n\texpect(dump1.producerPaused).toBe(false);\n\texpect(dump1.priority).toBe(1);\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\tpaused: true,\n\t});\n\tconst dump2 = await videoConsumer.dump();\n\n\texpect(dump2.id).toBe(videoConsumer.id);\n\texpect(dump2.producerId).toBe(videoConsumer.producerId);\n\texpect(dump2.kind).toBe(videoConsumer.kind);\n\texpect(typeof dump2.rtpParameters).toBe('object');\n\texpect(Array.isArray(dump2.rtpParameters.codecs)).toBe(true);\n\texpect(dump2.rtpParameters.codecs.length).toBe(2);\n\texpect(dump2.rtpParameters.codecs[0]!.mimeType).toBe('video/H264');\n\texpect(dump2.rtpParameters.codecs[0]!.payloadType).toBe(103);\n\texpect(dump2.rtpParameters.codecs[0]!.clockRate).toBe(90000);\n\texpect(dump2.rtpParameters.codecs[0]!.channels).toBeUndefined();\n\texpect(dump2.rtpParameters.codecs[0]!.parameters).toEqual({\n\t\t'packetization-mode': 1,\n\t\t'profile-level-id': '4d0032',\n\t});\n\texpect(dump2.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([\n\t\t{ type: 'nack' },\n\t\t{ type: 'nack', parameter: 'pli' },\n\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t{ type: 'goog-remb' },\n\t]);\n\texpect(Array.isArray(dump2.rtpParameters.headerExtensions)).toBe(true);\n\texpect(dump2.rtpParameters.headerExtensions!.length).toBe(4);\n\texpect(dump2.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tid: 1,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\tid: 4,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 8,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tid: 9,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t]);\n\texpect(Array.isArray(dump2.rtpParameters.encodings)).toBe(true);\n\texpect(dump2.rtpParameters.encodings!.length).toBe(1);\n\texpect(dump2.rtpParameters.encodings).toMatchObject([\n\t\t{\n\t\t\tcodecPayloadType: 103,\n\t\t\tssrc: videoConsumer.rtpParameters.encodings![0]!.ssrc,\n\t\t\trtx: {\n\t\t\t\tssrc: videoConsumer.rtpParameters.encodings![0]!.rtx?.ssrc,\n\t\t\t},\n\t\t\tscalabilityMode: 'L4T5',\n\t\t},\n\t]);\n\texpect(dump2.rtpParameters.msid).toBe(undefined);\n\texpect(Array.isArray(dump2.consumableRtpEncodings)).toBe(true);\n\texpect(dump2.consumableRtpEncodings!.length).toBe(4);\n\texpect(dump2.consumableRtpEncodings![0]).toEqual(\n\t\texpect.objectContaining({\n\t\t\tssrc: ctx.videoProducer!.consumableRtpParameters.encodings![0]!.ssrc,\n\t\t\tscalabilityMode: 'L1T5',\n\t\t})\n\t);\n\texpect(dump2.consumableRtpEncodings![1]).toEqual(\n\t\texpect.objectContaining({\n\t\t\tssrc: ctx.videoProducer!.consumableRtpParameters.encodings![1]!.ssrc,\n\t\t\tscalabilityMode: 'L1T5',\n\t\t})\n\t);\n\texpect(dump2.consumableRtpEncodings![2]).toEqual(\n\t\texpect.objectContaining({\n\t\t\tssrc: ctx.videoProducer!.consumableRtpParameters.encodings![2]!.ssrc,\n\t\t\tscalabilityMode: 'L1T5',\n\t\t})\n\t);\n\texpect(dump2.consumableRtpEncodings![3]).toEqual(\n\t\texpect.objectContaining({\n\t\t\tssrc: ctx.videoProducer!.consumableRtpParameters.encodings![3]!.ssrc,\n\t\t\tscalabilityMode: 'L1T5',\n\t\t})\n\t);\n\texpect(dump2.supportedCodecPayloadTypes).toEqual([103]);\n\texpect(dump2.paused).toBe(true);\n\texpect(dump2.producerPaused).toBe(false);\n\texpect(dump2.priority).toBe(1);\n}, 2000);\n\ntest('consumer.getStats() succeeds', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait expect(audioConsumer.getStats()).resolves.toEqual([\n\t\texpect.objectContaining({\n\t\t\ttype: 'outbound-rtp',\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tssrc: audioConsumer.rtpParameters.encodings![0]!.ssrc,\n\t\t}),\n\t]);\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait expect(videoConsumer.getStats()).resolves.toEqual([\n\t\texpect.objectContaining({\n\t\t\ttype: 'outbound-rtp',\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tssrc: videoConsumer.rtpParameters.encodings![0]!.ssrc,\n\t\t}),\n\t]);\n}, 2000);\n\ntest('consumer.pause() and resume() succeed', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst onObserverPause = jest.fn();\n\tconst onObserverResume = jest.fn();\n\n\taudioConsumer.observer.on('pause', onObserverPause);\n\taudioConsumer.observer.on('resume', onObserverResume);\n\n\tawait audioConsumer.pause();\n\texpect(audioConsumer.paused).toBe(true);\n\n\tawait expect(audioConsumer.dump()).resolves.toMatchObject({ paused: true });\n\n\tawait audioConsumer.resume();\n\texpect(audioConsumer.paused).toBe(false);\n\n\tawait expect(audioConsumer.dump()).resolves.toMatchObject({ paused: false });\n\n\t// Even if we don't await for pause()/resume() completion, the observer must\n\t// fire 'pause' and 'resume' events if state was the opposite.\n\tvoid audioConsumer.pause();\n\tvoid audioConsumer.resume();\n\tvoid audioConsumer.pause();\n\tvoid audioConsumer.pause();\n\tvoid audioConsumer.pause();\n\tawait audioConsumer.resume();\n\n\texpect(onObserverPause).toHaveBeenCalledTimes(3);\n\texpect(onObserverResume).toHaveBeenCalledTimes(3);\n}, 2000);\n\ntest('producer.pause() and resume() emit events', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst promises = [];\n\tconst events: string[] = [];\n\n\taudioConsumer.observer.once('resume', () => {\n\t\tevents.push('resume');\n\t});\n\n\taudioConsumer.observer.once('pause', () => {\n\t\tevents.push('pause');\n\t});\n\n\tpromises.push(ctx.audioProducer!.pause());\n\tpromises.push(ctx.audioProducer!.resume());\n\n\tawait Promise.all(promises);\n\n\t// Must also wait a bit for the corresponding events in the consumer.\n\tawait new Promise(resolve => setTimeout(resolve, 100));\n\n\texpect(events).toEqual(['pause', 'resume']);\n\texpect(audioConsumer.paused).toBe(false);\n}, 2000);\n\ntest('consumer.setPreferredLayers() succeed', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait audioConsumer.setPreferredLayers({ spatialLayer: 1, temporalLayer: 1 });\n\n\texpect(audioConsumer.preferredLayers).toBeUndefined();\n\n\tawait videoConsumer.setPreferredLayers({ spatialLayer: 2, temporalLayer: 3 });\n\n\texpect(videoConsumer.preferredLayers).toEqual({\n\t\tspatialLayer: 2,\n\t\ttemporalLayer: 3,\n\t});\n\n\tawait videoConsumer.setPreferredLayers({ spatialLayer: 3 });\n\n\texpect(videoConsumer.preferredLayers).toEqual({\n\t\tspatialLayer: 3,\n\t\ttemporalLayer: 4,\n\t});\n\n\tawait videoConsumer.setPreferredLayers({ spatialLayer: 3, temporalLayer: 0 });\n\n\texpect(videoConsumer.preferredLayers).toEqual({\n\t\tspatialLayer: 3,\n\t\ttemporalLayer: 0,\n\t});\n\n\tawait videoConsumer.setPreferredLayers({\n\t\tspatialLayer: 66,\n\t\ttemporalLayer: 66,\n\t});\n\n\texpect(videoConsumer.preferredLayers).toEqual({\n\t\tspatialLayer: 3,\n\t\ttemporalLayer: 4,\n\t});\n}, 2000);\n\ntest('consumer.setPreferredLayers() with wrong arguments rejects with TypeError', async () => {\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(videoConsumer.setPreferredLayers({})).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tvideoConsumer.setPreferredLayers({ foo: '123' })\n\t).rejects.toThrow(TypeError);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(videoConsumer.setPreferredLayers('foo')).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\t// Missing spatialLayer.\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tvideoConsumer.setPreferredLayers({ temporalLayer: 2 })\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('consumer.setPriority() succeed', async () => {\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait videoConsumer.setPriority(2);\n\texpect(videoConsumer.priority).toBe(2);\n}, 2000);\n\ntest('consumer.setPriority() with wrong arguments rejects with TypeError', async () => {\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(videoConsumer.setPriority()).rejects.toThrow(TypeError);\n\n\tawait expect(videoConsumer.setPriority(0)).rejects.toThrow(TypeError);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(videoConsumer.setPriority('foo')).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('consumer.unsetPriority() succeed', async () => {\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait videoConsumer.unsetPriority();\n\texpect(videoConsumer.priority).toBe(1);\n}, 2000);\n\ntest('consumer.enableTraceEvent() succeed', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait audioConsumer.enableTraceEvent(['rtp', 'pli']);\n\tconst dump1 = await audioConsumer.dump();\n\n\texpect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli']));\n\n\tawait audioConsumer.enableTraceEvent();\n\n\tconst dump2 = await audioConsumer.dump();\n\n\texpect(dump2.traceEventTypes).toEqual(expect.arrayContaining([]));\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait audioConsumer.enableTraceEvent(['nack', 'FOO', 'fir']);\n\n\tconst dump3 = await audioConsumer.dump();\n\n\texpect(dump3.traceEventTypes).toEqual(\n\t\texpect.arrayContaining(['nack', 'fir'])\n\t);\n\n\tawait audioConsumer.enableTraceEvent();\n\n\tconst dump4 = await audioConsumer.dump();\n\n\texpect(dump4.traceEventTypes).toEqual(expect.arrayContaining([]));\n}, 2000);\n\ntest('consumer.enableTraceEvent() with wrong arguments rejects with TypeError', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(audioConsumer.enableTraceEvent(123)).rejects.toThrow(TypeError);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(audioConsumer.enableTraceEvent('rtp')).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\taudioConsumer.enableTraceEvent(['fir', 123.123])\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('Consumer emits \"producerpause\" and \"producerresume\"', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tawait Promise.all([\n\t\tenhancedOnce<ConsumerEvents>(audioConsumer, 'producerpause'),\n\n\t\t// Let's await for pause() to resolve to avoid aborted channel requests\n\t\t// due to worker closure.\n\t\tctx.audioProducer!.pause(),\n\t]);\n\n\texpect(audioConsumer.paused).toBe(false);\n\texpect(audioConsumer.producerPaused).toBe(true);\n\n\tawait Promise.all([\n\t\tenhancedOnce<ConsumerEvents>(audioConsumer, 'producerresume'),\n\n\t\t// Let's await for resume() to resolve to avoid aborted channel requests\n\t\t// due to worker closure.\n\t\tctx.audioProducer!.resume(),\n\t]);\n\n\texpect(audioConsumer.paused).toBe(false);\n\texpect(audioConsumer.producerPaused).toBe(false);\n}, 2000);\n\ntest('Consumer emits \"score\"', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\t// API not exposed in the interface.\n\tconst channel = (audioConsumer as ConsumerImpl).channelForTesting;\n\tconst onScore = jest.fn();\n\n\taudioConsumer.on('score', onScore);\n\n\t// Simulate a 'score' notification coming through the channel.\n\tconst builder = new flatbuffers.Builder();\n\tconst consumerScore = new FbsConsumer.ConsumerScoreT(9, 10, [8]);\n\tconst consumerScoreNotification = new FbsConsumer.ScoreNotificationT(\n\t\tconsumerScore\n\t);\n\tconst notificationOffset = Notification.createNotification(\n\t\tbuilder,\n\t\tbuilder.createString(audioConsumer.id),\n\t\tEvent.CONSUMER_SCORE,\n\t\tNotificationBody.Consumer_ScoreNotification,\n\t\tconsumerScoreNotification.pack(builder)\n\t);\n\n\tbuilder.finish(notificationOffset);\n\n\tconst notification = Notification.getRootAsNotification(\n\t\tnew flatbuffers.ByteBuffer(builder.asUint8Array())\n\t);\n\n\tchannel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification);\n\tchannel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification);\n\tchannel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification);\n\n\texpect(onScore).toHaveBeenCalledTimes(3);\n\texpect(audioConsumer.score).toEqual({\n\t\tscore: 9,\n\t\tproducerScore: 10,\n\t\tproducerScores: [8],\n\t});\n}, 2000);\n\ntest('consumer.close() succeeds', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst onObserverClose = jest.fn();\n\n\taudioConsumer.observer.once('close', onObserverClose);\n\taudioConsumer.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(audioConsumer.closed).toBe(true);\n\n\tconst routerDump = await ctx.router!.dump();\n\n\texpect(routerDump.mapProducerIdConsumerIds).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: ctx.audioProducer!.id, values: [] },\n\t\t\t{ key: ctx.videoProducer!.id, values: [videoConsumer.id] },\n\t\t])\n\t);\n\texpect(routerDump.mapConsumerIdProducerId).toEqual([\n\t\t{ key: videoConsumer!.id, value: ctx.videoProducer!.id },\n\t]);\n\n\tconst transportDump = await ctx.webRtcTransport2!.dump();\n\n\texpect(transportDump).toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tproducerIds: [],\n\t\tconsumerIds: [videoConsumer.id],\n\t});\n}, 2000);\n\ntest('Consumer methods reject if closed', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\taudioConsumer.close();\n\n\tawait expect(audioConsumer.dump()).rejects.toThrow(Error);\n\n\tawait expect(audioConsumer.getStats()).rejects.toThrow(Error);\n\n\tawait expect(audioConsumer.pause()).rejects.toThrow(Error);\n\n\tawait expect(audioConsumer.resume()).rejects.toThrow(Error);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(audioConsumer.setPreferredLayers({})).rejects.toThrow(Error);\n\n\tawait expect(audioConsumer.setPriority(2)).rejects.toThrow(Error);\n\n\tawait expect(audioConsumer.requestKeyFrame()).rejects.toThrow(Error);\n}, 2000);\n\ntest('Consumer emits \"producerclose\" if Producer is closed', async () => {\n\tconst audioConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\tconst onObserverClose = jest.fn();\n\n\taudioConsumer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<ConsumerEvents>(audioConsumer, 'producerclose');\n\n\tctx.audioProducer!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(audioConsumer.closed).toBe(true);\n}, 2000);\n\ntest('Consumer emits \"transportclose\" if Transport is closed', async () => {\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\tvideoConsumer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<ConsumerEvents>(videoConsumer, 'transportclose');\n\n\tctx.webRtcTransport2!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(videoConsumer.closed).toBe(true);\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\tmapProducerIdConsumerIds: expect.arrayContaining([\n\t\t\t{ key: ctx.audioProducer!.id, values: [] },\n\t\t\t{ key: ctx.videoProducer!.id, values: [] },\n\t\t]),\n\t\tmapConsumerIdProducerId: [],\n\t});\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-DataConsumer.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, DataConsumerEvents } from '../types';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tsctpDataProducerOptions: mediasoup.types.DataProducerOptions;\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\twebRtcTransport1?: mediasoup.types.WebRtcTransport;\n\twebRtcTransport2?: mediasoup.types.WebRtcTransport;\n\tdirectTransport?: mediasoup.types.DirectTransport;\n\tsctpDataProducer?: mediasoup.types.DataProducer;\n\tdirectDataProducer?: mediasoup.types.DataProducer;\n};\n\nconst ctx: TestContext = {\n\tsctpDataProducerOptions:\n\t\tutils.deepFreeze<mediasoup.types.DataProducerOptions>({\n\t\t\tsctpStreamParameters: {\n\t\t\t\tstreamId: 12345,\n\t\t\t\tordered: false,\n\t\t\t\tmaxPacketLifeTime: 5000,\n\t\t\t},\n\t\t\tlabel: 'foo',\n\t\t\tprotocol: 'bar',\n\t\t}),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter();\n\tctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n\tctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n\tctx.directTransport = await ctx.router.createDirectTransport();\n\tctx.sctpDataProducer = await ctx.webRtcTransport1.produceData(\n\t\tctx.sctpDataProducerOptions\n\t);\n\tctx.directDataProducer = await ctx.directTransport.produceData();\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('transport.consumeData() succeeds', async () => {\n\tconst onObserverNewDataConsumer = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once(\n\t\t'newdataconsumer',\n\t\tonObserverNewDataConsumer\n\t);\n\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t\tmaxPacketLifeTime: 4000,\n\t\t// Valid values are 0...65535 so others and duplicated ones will be\n\t\t// discarded.\n\t\tsubchannels: [0, 1, 1, 1, 2, 65535, 65536, 65537, 100],\n\t\tappData: { baz: 'LOL' },\n\t});\n\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer);\n\texpect(typeof dataConsumer.id).toBe('string');\n\texpect(dataConsumer.dataProducerId).toBe(ctx.sctpDataProducer!.id);\n\texpect(dataConsumer.closed).toBe(false);\n\texpect(dataConsumer.type).toBe('sctp');\n\texpect(typeof dataConsumer.sctpStreamParameters).toBe('object');\n\texpect(typeof dataConsumer.sctpStreamParameters!.streamId).toBe('number');\n\texpect(dataConsumer.sctpStreamParameters!.ordered).toBe(false);\n\texpect(dataConsumer.sctpStreamParameters!.maxPacketLifeTime).toBe(4000);\n\texpect(dataConsumer.sctpStreamParameters!.maxRetransmits).toBeUndefined();\n\texpect(dataConsumer.label).toBe('foo');\n\texpect(dataConsumer.protocol).toBe('bar');\n\texpect(dataConsumer.paused).toBe(false);\n\texpect(dataConsumer.subchannels).toEqual(\n\t\texpect.arrayContaining([0, 1, 2, 100, 65535])\n\t);\n\texpect(dataConsumer.appData).toEqual({ baz: 'LOL' });\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapDataProducerIdDataConsumerIds).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: ctx.sctpDataProducer!.id, values: [dataConsumer.id] },\n\t\t])\n\t);\n\n\texpect(dump.mapDataConsumerIdDataProducerId).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: dataConsumer.id, value: ctx.sctpDataProducer!.id },\n\t\t])\n\t);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tdataProducerIds: [],\n\t\tdataConsumerIds: [dataConsumer.id],\n\t});\n}, 2000);\n\ntest('dataConsumer.dump() succeeds', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t\tordered: true,\n\t\t// Valid values are 0...65535 so others and duplicated ones will be\n\t\t// discarded.\n\t\tsubchannels: [0, 1, 1, 1, 2, 65535, 65536, 65537, 100],\n\t\tappData: { baz: 'LOL' },\n\t});\n\n\tconst dump = await dataConsumer.dump();\n\n\texpect(dump.id).toBe(dataConsumer.id);\n\texpect(dump.dataProducerId).toBe(dataConsumer.dataProducerId);\n\texpect(dump.type).toBe('sctp');\n\texpect(typeof dump.sctpStreamParameters).toBe('object');\n\texpect(dump.sctpStreamParameters!.streamId).toBe(\n\t\tdataConsumer.sctpStreamParameters!.streamId\n\t);\n\texpect(dump.sctpStreamParameters!.ordered).toBe(true);\n\texpect(dump.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined();\n\texpect(dump.sctpStreamParameters!.maxRetransmits).toBeUndefined();\n\texpect(dump.label).toBe('foo');\n\texpect(dump.protocol).toBe('bar');\n\texpect(dump.paused).toBe(false);\n\texpect(dump.dataProducerPaused).toBe(false);\n\texpect(dump.subchannels).toEqual(\n\t\texpect.arrayContaining([0, 1, 2, 100, 65535])\n\t);\n}, 2000);\n\ntest('dataConsumer.getStats() succeeds', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tawait expect(dataConsumer.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-consumer',\n\t\t\tlabel: dataConsumer.label,\n\t\t\tprotocol: dataConsumer.protocol,\n\t\t\tmessagesSent: 0,\n\t\t\tbytesSent: 0,\n\t\t},\n\t]);\n}, 2000);\n\ntest('dataConsumer.setSubchannels() succeeds', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tawait dataConsumer.setSubchannels([999, 999, 998, 65536]);\n\n\texpect(dataConsumer.subchannels).toEqual(\n\t\texpect.arrayContaining([0, 998, 999])\n\t);\n}, 2000);\n\ntest('dataConsumer.addSubchannel() and .removeSubchannel() succeed', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tawait dataConsumer.setSubchannels([]);\n\texpect(dataConsumer.subchannels).toEqual([]);\n\n\tawait dataConsumer.addSubchannel(5);\n\texpect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5]));\n\n\tawait dataConsumer.addSubchannel(10);\n\texpect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10]));\n\n\tawait dataConsumer.addSubchannel(5);\n\texpect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10]));\n\n\tawait dataConsumer.removeSubchannel(666);\n\texpect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10]));\n\n\tawait dataConsumer.removeSubchannel(5);\n\texpect(dataConsumer.subchannels).toEqual(expect.arrayContaining([10]));\n\n\tawait dataConsumer.setSubchannels([]);\n\texpect(dataConsumer.subchannels).toEqual([]);\n}, 2000);\n\ntest('transport.consumeData() from a direct DataProducer succeeds', async () => {\n\tconst onObserverNewDataConsumer = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once(\n\t\t'newdataconsumer',\n\t\tonObserverNewDataConsumer\n\t);\n\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.directDataProducer!.id,\n\t});\n\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer);\n\texpect(typeof dataConsumer.id).toBe('string');\n\texpect(dataConsumer.dataProducerId).toBe(ctx.directDataProducer!.id);\n\texpect(dataConsumer.closed).toBe(false);\n\texpect(dataConsumer.type).toBe('sctp');\n\texpect(typeof dataConsumer.sctpStreamParameters).toBe('object');\n\texpect(typeof dataConsumer.sctpStreamParameters!.streamId).toBe('number');\n\texpect(dataConsumer.sctpStreamParameters!.ordered).toBe(true);\n\texpect(dataConsumer.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined();\n\texpect(dataConsumer.sctpStreamParameters!.maxRetransmits).toBeUndefined();\n\texpect(dataConsumer.label).toBe('');\n\texpect(dataConsumer.protocol).toBe('');\n\texpect(dataConsumer.paused).toBe(false);\n\texpect(dataConsumer.subchannels).toEqual([]);\n\texpect(dataConsumer.appData).toEqual({});\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapDataProducerIdDataConsumerIds).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: ctx.directDataProducer!.id, values: [dataConsumer.id] },\n\t\t])\n\t);\n\n\texpect(dump.mapDataConsumerIdDataProducerId).toEqual(\n\t\texpect.arrayContaining([\n\t\t\t{ key: dataConsumer.id, value: ctx.directDataProducer!.id },\n\t\t])\n\t);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tdataProducerIds: [],\n\t\tdataConsumerIds: [dataConsumer.id],\n\t});\n}, 2000);\n\ntest('dataConsumer.dump() consuming from a direct DataProducer succeeds', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.directDataProducer!.id,\n\t\tordered: false,\n\t\tmaxRetransmits: 2,\n\t\tsubchannels: [0, 1],\n\t});\n\n\tconst dump = await dataConsumer.dump();\n\n\texpect(dump.id).toBe(dataConsumer.id);\n\texpect(dump.dataProducerId).toBe(dataConsumer.dataProducerId);\n\texpect(dump.type).toBe('sctp');\n\texpect(typeof dump.sctpStreamParameters).toBe('object');\n\texpect(dump.sctpStreamParameters!.streamId).toBe(\n\t\tdataConsumer.sctpStreamParameters!.streamId\n\t);\n\texpect(dump.sctpStreamParameters!.ordered).toBe(false);\n\texpect(dump.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined();\n\texpect(dump.sctpStreamParameters!.maxRetransmits).toBe(2);\n\texpect(dump.label).toBe('');\n\texpect(dump.protocol).toBe('');\n\texpect(dump.paused).toBe(false);\n\texpect(dump.dataProducerPaused).toBe(false);\n\texpect(dump.subchannels).toEqual(expect.arrayContaining([0, 1]));\n}, 2000);\n\ntest('transport.consumeData() on a DirectTransport succeeds', async () => {\n\tconst onObserverNewDataConsumer = jest.fn();\n\n\tctx.directTransport!.observer.once(\n\t\t'newdataconsumer',\n\t\tonObserverNewDataConsumer\n\t);\n\n\tconst dataConsumer = await ctx.directTransport!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t\tpaused: true,\n\t\tappData: { hehe: 'HEHE' },\n\t});\n\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer);\n\texpect(typeof dataConsumer.id).toBe('string');\n\texpect(dataConsumer.dataProducerId).toBe(ctx.sctpDataProducer!.id);\n\texpect(dataConsumer.closed).toBe(false);\n\texpect(dataConsumer.type).toBe('direct');\n\texpect(dataConsumer.sctpStreamParameters).toBeUndefined();\n\texpect(dataConsumer.label).toBe('foo');\n\texpect(dataConsumer.protocol).toBe('bar');\n\texpect(dataConsumer.paused).toBe(true);\n\texpect(dataConsumer.appData).toEqual({ hehe: 'HEHE' });\n\n\tawait expect(ctx.directTransport!.dump()).resolves.toMatchObject({\n\t\tid: ctx.directTransport!.id,\n\t\tdataProducerIds: [ctx.directDataProducer!.id],\n\t\tdataConsumerIds: [dataConsumer.id],\n\t});\n}, 2000);\n\ntest('dataConsumer.dump() on a DirectTransport succeeds', async () => {\n\tconst dataConsumer = await ctx.directTransport!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t\tpaused: true,\n\t});\n\n\tconst dump = await dataConsumer.dump();\n\n\texpect(dump.id).toBe(dataConsumer.id);\n\texpect(dump.dataProducerId).toBe(dataConsumer.dataProducerId);\n\texpect(dump.type).toBe('direct');\n\texpect(dump.sctpStreamParameters).toBeUndefined();\n\texpect(dump.label).toBe('foo');\n\texpect(dump.protocol).toBe('bar');\n\texpect(dump.paused).toBe(true);\n\texpect(dump.subchannels).toEqual([]);\n}, 2000);\n\ntest('dataConsumer.getStats() on a DirectTransport succeeds', async () => {\n\tconst dataConsumer = await ctx.directTransport!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tawait expect(dataConsumer.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-consumer',\n\t\t\tlabel: dataConsumer.label,\n\t\t\tprotocol: dataConsumer.protocol,\n\t\t\tmessagesSent: 0,\n\t\t\tbytesSent: 0,\n\t\t},\n\t]);\n}, 2000);\n\ntest('dataConsumer.pause() and resume() succeed', async () => {\n\tconst onObserverPause = jest.fn();\n\tconst onObserverResume = jest.fn();\n\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tdataConsumer.observer.on('pause', onObserverPause);\n\tdataConsumer.observer.on('resume', onObserverResume);\n\n\tawait dataConsumer.pause();\n\n\texpect(dataConsumer.paused).toBe(true);\n\n\tconst dump1 = await dataConsumer.dump();\n\n\texpect(dump1.paused).toBe(true);\n\n\tawait dataConsumer.resume();\n\n\texpect(dataConsumer.paused).toBe(false);\n\n\tconst dump2 = await dataConsumer.dump();\n\n\texpect(dump2.paused).toBe(false);\n\n\t// Even if we don't await for pause()/resume() completion, the observer must\n\t// fire 'pause' and 'resume' events if state was the opposite.\n\tvoid dataConsumer.pause();\n\tvoid dataConsumer.resume();\n\tvoid dataConsumer.pause();\n\tvoid dataConsumer.pause();\n\tvoid dataConsumer.pause();\n\tawait dataConsumer.resume();\n\n\texpect(onObserverPause).toHaveBeenCalledTimes(3);\n\texpect(onObserverResume).toHaveBeenCalledTimes(3);\n}, 2000);\n\ntest('dataProducer.pause() and resume() emit events', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\tconst promises = [];\n\tconst events: string[] = [];\n\n\tdataConsumer.observer.once('resume', () => {\n\t\tevents.push('resume');\n\t});\n\n\tdataConsumer.observer.once('pause', () => {\n\t\tevents.push('pause');\n\t});\n\n\tpromises.push(ctx.sctpDataProducer!.pause());\n\tpromises.push(ctx.sctpDataProducer!.resume());\n\n\tawait Promise.all(promises);\n\n\t// Must also wait a bit for the corresponding events in the data consumer.\n\tawait new Promise(resolve => setTimeout(resolve, 100));\n\n\texpect(events).toEqual(['pause', 'resume']);\n\texpect(dataConsumer.paused).toBe(false);\n}, 2000);\n\ntest('dataConsumer.close() succeeds', async () => {\n\tconst onObserverClose = jest.fn();\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tdataConsumer.observer.once('close', onObserverClose);\n\tdataConsumer.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(dataConsumer.closed).toBe(true);\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapDataProducerIdDataConsumerIds).toEqual(\n\t\texpect.arrayContaining([{ key: ctx.sctpDataProducer!.id, values: [] }])\n\t);\n\n\texpect(dump.mapDataConsumerIdDataProducerId).toEqual([]);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tdataProducerIds: [],\n\t\tdataConsumerIds: [],\n\t});\n}, 2000);\n\ntest('Consumer methods reject if closed', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\n\tdataConsumer.close();\n\n\tawait expect(dataConsumer.dump()).rejects.toThrow(Error);\n\n\tawait expect(dataConsumer.getStats()).rejects.toThrow(Error);\n}, 2000);\n\ntest('DataConsumer emits \"dataproducerclose\" if DataProducer is closed', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\tconst onObserverClose = jest.fn();\n\n\tdataConsumer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<DataConsumerEvents>(\n\t\tdataConsumer,\n\t\t'dataproducerclose'\n\t);\n\n\tctx.sctpDataProducer!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(dataConsumer.closed).toBe(true);\n}, 2000);\n\ntest('DataConsumer emits \"transportclose\" if Transport is closed', async () => {\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: ctx.sctpDataProducer!.id,\n\t});\n\tconst onObserverClose = jest.fn();\n\n\tdataConsumer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<DataConsumerEvents>(\n\t\tdataConsumer,\n\t\t'transportclose'\n\t);\n\n\tctx.webRtcTransport2!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(dataConsumer.closed).toBe(true);\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\tmapDataProducerIdDataConsumerIds: {},\n\t\tmapDataConsumerIdDataProducerId: {},\n\t});\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-DataProducer.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, DataProducerEvents } from '../types';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tdataProducerOptions1: mediasoup.types.DataProducerOptions;\n\tdataProducerOptions2: mediasoup.types.DataProducerOptions;\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\twebRtcTransport1?: mediasoup.types.WebRtcTransport;\n\twebRtcTransport2?: mediasoup.types.WebRtcTransport;\n};\n\nconst ctx: TestContext = {\n\tdataProducerOptions1: utils.deepFreeze<mediasoup.types.DataProducerOptions>({\n\t\tsctpStreamParameters: {\n\t\t\tstreamId: 666,\n\t\t},\n\t\tlabel: 'foo',\n\t\tprotocol: 'bar',\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n\tdataProducerOptions2: utils.deepFreeze<mediasoup.types.DataProducerOptions>({\n\t\tsctpStreamParameters: {\n\t\t\tstreamId: 777,\n\t\t\tmaxRetransmits: 3,\n\t\t},\n\t\tlabel: 'foo',\n\t\tprotocol: 'bar',\n\t\tpaused: true,\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter();\n\tctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n\tctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('webRtcTransport1.produceData() succeeds', async () => {\n\tconst onObserverNewDataProducer = jest.fn();\n\n\tctx.webRtcTransport1!.observer.once(\n\t\t'newdataproducer',\n\t\tonObserverNewDataProducer\n\t);\n\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\texpect(onObserverNewDataProducer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewDataProducer).toHaveBeenCalledWith(dataProducer1);\n\texpect(typeof dataProducer1.id).toBe('string');\n\texpect(dataProducer1.closed).toBe(false);\n\texpect(dataProducer1.type).toBe('sctp');\n\texpect(typeof dataProducer1.sctpStreamParameters).toBe('object');\n\texpect(dataProducer1.sctpStreamParameters?.streamId).toBe(666);\n\texpect(dataProducer1.sctpStreamParameters?.ordered).toBe(true);\n\texpect(dataProducer1.sctpStreamParameters?.maxPacketLifeTime).toBeUndefined();\n\texpect(dataProducer1.sctpStreamParameters?.maxRetransmits).toBeUndefined();\n\texpect(dataProducer1.label).toBe('foo');\n\texpect(dataProducer1.protocol).toBe('bar');\n\texpect(dataProducer1.paused).toBe(false);\n\texpect(dataProducer1.appData).toEqual({ foo: 1, bar: '2' });\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapDataProducerIdDataConsumerIds).toEqual(\n\t\texpect.arrayContaining([{ key: dataProducer1.id, values: [] }])\n\t);\n\n\texpect(dump.mapDataConsumerIdDataProducerId.length).toBe(0);\n\n\tawait expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport1!.id,\n\t\tdataProducerIds: [dataProducer1.id],\n\t\tdataConsumerIds: [],\n\t});\n}, 2000);\n\ntest('webRtcTransport2.produceData() succeeds', async () => {\n\tconst onObserverNewDataProducer = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once(\n\t\t'newdataproducer',\n\t\tonObserverNewDataProducer\n\t);\n\n\tconst dataProducer2 = await ctx.webRtcTransport2!.produceData(\n\t\tctx.dataProducerOptions2\n\t);\n\n\texpect(onObserverNewDataProducer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewDataProducer).toHaveBeenCalledWith(dataProducer2);\n\texpect(typeof dataProducer2.id).toBe('string');\n\texpect(dataProducer2.closed).toBe(false);\n\texpect(dataProducer2.type).toBe('sctp');\n\texpect(typeof dataProducer2.sctpStreamParameters).toBe('object');\n\texpect(dataProducer2.sctpStreamParameters?.streamId).toBe(777);\n\texpect(dataProducer2.sctpStreamParameters?.ordered).toBe(false);\n\texpect(dataProducer2.sctpStreamParameters?.maxPacketLifeTime).toBeUndefined();\n\texpect(dataProducer2.sctpStreamParameters?.maxRetransmits).toBe(3);\n\texpect(dataProducer2.label).toBe('foo');\n\texpect(dataProducer2.protocol).toBe('bar');\n\texpect(dataProducer2.paused).toBe(true);\n\texpect(dataProducer2.appData).toEqual({ foo: 1, bar: '2' });\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapDataProducerIdDataConsumerIds).toEqual(\n\t\texpect.arrayContaining([{ key: dataProducer2.id, values: [] }])\n\t);\n\n\texpect(dump.mapDataConsumerIdDataProducerId.length).toBe(0);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tdataProducerIds: [dataProducer2.id],\n\t\tdataConsumerIds: [],\n\t});\n}, 2000);\n\ntest('webRtcTransport1.produceData() with wrong arguments rejects with TypeError', async () => {\n\tawait expect(ctx.webRtcTransport1!.produceData({})).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\t// Missing or empty sctpStreamParameters.streamId.\n\tawait expect(\n\t\tctx.webRtcTransport1!.produceData({\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsctpStreamParameters: { foo: 'foo' },\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('transport.produceData() with already used streamId rejects with Error', async () => {\n\tawait ctx.webRtcTransport1!.produceData(ctx.dataProducerOptions1);\n\n\tawait expect(\n\t\tctx.webRtcTransport1!.produceData({\n\t\t\tsctpStreamParameters: {\n\t\t\t\tstreamId: 666,\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('transport.produceData() with ordered and maxPacketLifeTime rejects with TypeError', async () => {\n\tawait expect(\n\t\tctx.webRtcTransport1!.produceData({\n\t\t\tsctpStreamParameters: {\n\t\t\t\tstreamId: 999,\n\t\t\t\tordered: true,\n\t\t\t\tmaxPacketLifeTime: 4000,\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('dataProducer.dump() succeeds', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tconst dump1 = await dataProducer1.dump();\n\n\texpect(dump1.id).toBe(dataProducer1.id);\n\texpect(dump1.type).toBe('sctp');\n\texpect(typeof dump1.sctpStreamParameters).toBe('object');\n\texpect(dump1.sctpStreamParameters!.streamId).toBe(666);\n\texpect(dump1.sctpStreamParameters!.ordered).toBe(true);\n\texpect(dump1.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined();\n\texpect(dump1.sctpStreamParameters!.maxRetransmits).toBeUndefined();\n\texpect(dump1.label).toBe('foo');\n\texpect(dump1.protocol).toBe('bar');\n\texpect(dump1.paused).toBe(false);\n\n\tconst dataProducer2 = await ctx.webRtcTransport2!.produceData(\n\t\tctx.dataProducerOptions2\n\t);\n\n\tconst dump2 = await dataProducer2.dump();\n\n\texpect(dump2.id).toBe(dataProducer2.id);\n\texpect(dump2.type).toBe('sctp');\n\texpect(typeof dump2.sctpStreamParameters).toBe('object');\n\texpect(dump2.sctpStreamParameters!.streamId).toBe(777);\n\texpect(dump2.sctpStreamParameters!.ordered).toBe(false);\n\texpect(dump2.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined();\n\texpect(dump2.sctpStreamParameters!.maxRetransmits).toBe(3);\n\texpect(dump2.label).toBe('foo');\n\texpect(dump2.protocol).toBe('bar');\n\texpect(dump2.paused).toBe(true);\n}, 2000);\n\ntest('dataProducer.getStats() succeeds', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tawait expect(dataProducer1.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-producer',\n\t\t\tlabel: dataProducer1.label,\n\t\t\tprotocol: dataProducer1.protocol,\n\t\t\tmessagesReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t},\n\t]);\n\n\tconst dataProducer2 = await ctx.webRtcTransport2!.produceData(\n\t\tctx.dataProducerOptions2\n\t);\n\n\tawait expect(dataProducer2.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-producer',\n\t\t\tlabel: dataProducer2.label,\n\t\t\tprotocol: dataProducer2.protocol,\n\t\t\tmessagesReceived: 0,\n\t\t\tbytesReceived: 0,\n\t\t},\n\t]);\n}, 2000);\n\ntest('dataProducer.pause() and resume() succeed', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tconst onObserverPause = jest.fn();\n\tconst onObserverResume = jest.fn();\n\n\tdataProducer1.observer.on('pause', onObserverPause);\n\tdataProducer1.observer.on('resume', onObserverResume);\n\n\tawait dataProducer1.pause();\n\n\texpect(dataProducer1.paused).toBe(true);\n\n\tconst dump1 = await dataProducer1.dump();\n\n\texpect(dump1.paused).toBe(true);\n\n\tawait dataProducer1.resume();\n\n\texpect(dataProducer1.paused).toBe(false);\n\n\tconst dump2 = await dataProducer1.dump();\n\n\texpect(dump2.paused).toBe(false);\n\n\t// Even if we don't await for pause()/resume() completion, the observer must\n\t// fire 'pause' and 'resume' events if state was the opposite.\n\tvoid dataProducer1.pause();\n\tvoid dataProducer1.resume();\n\tvoid dataProducer1.pause();\n\tvoid dataProducer1.pause();\n\tvoid dataProducer1.pause();\n\tawait dataProducer1.resume();\n\n\texpect(onObserverPause).toHaveBeenCalledTimes(3);\n\texpect(onObserverResume).toHaveBeenCalledTimes(3);\n}, 2000);\n\ntest('producer.pause() and resume() emit events', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tconst promises = [];\n\tconst events: string[] = [];\n\n\tdataProducer1.observer.once('resume', () => {\n\t\tevents.push('resume');\n\t});\n\n\tdataProducer1.observer.once('pause', () => {\n\t\tevents.push('pause');\n\t});\n\n\tpromises.push(dataProducer1.pause());\n\tpromises.push(dataProducer1.resume());\n\n\tawait Promise.all(promises);\n\n\texpect(events).toEqual(['pause', 'resume']);\n\texpect(dataProducer1.paused).toBe(false);\n}, 2000);\n\ntest('dataProducer.close() succeeds', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tconst onObserverClose = jest.fn();\n\n\tdataProducer1.observer.once('close', onObserverClose);\n\tdataProducer1.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(dataProducer1.closed).toBe(true);\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\tmapDataProducerIdDataConsumerIds: {},\n\t\tmapDataConsumerIdDataProducerId: {},\n\t});\n\n\tawait expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport1!.id,\n\t\tdataProducerIds: [],\n\t\tdataConsumerIds: [],\n\t});\n}, 2000);\n\ntest('DataProducer methods reject if closed', async () => {\n\tconst dataProducer1 = await ctx.webRtcTransport1!.produceData(\n\t\tctx.dataProducerOptions1\n\t);\n\n\tdataProducer1.close();\n\n\tawait expect(dataProducer1.dump()).rejects.toThrow(Error);\n\n\tawait expect(dataProducer1.getStats()).rejects.toThrow(Error);\n}, 2000);\n\ntest('DataProducer emits \"transportclose\" if Transport is closed', async () => {\n\tconst dataProducer2 = await ctx.webRtcTransport2!.produceData(\n\t\tctx.dataProducerOptions2\n\t);\n\n\tconst onObserverClose = jest.fn();\n\n\tdataProducer2.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<DataProducerEvents>(\n\t\tdataProducer2,\n\t\t'transportclose'\n\t);\n\n\tctx.webRtcTransport2!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(dataProducer2.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-DirectTransport.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { DirectTransportEvents } from '../DirectTransportTypes';\nimport type { WorkerEvents } from '../types';\n\ntype TestContext = {\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n};\n\nconst ctx: TestContext = {};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter();\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('router.createDirectTransport() succeeds', async () => {\n\tconst onObserverNewTransport = jest.fn();\n\n\tctx.router!.observer.once('newtransport', onObserverNewTransport);\n\n\tconst directTransport = await ctx.router!.createDirectTransport({\n\t\tmaxMessageSize: 1024,\n\t\tappData: { foo: 'bar' },\n\t});\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\ttransportIds: [directTransport.id],\n\t});\n\n\texpect(onObserverNewTransport).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewTransport).toHaveBeenCalledWith(directTransport);\n\texpect(typeof directTransport.id).toBe('string');\n\texpect(directTransport.type).toBe('direct');\n\texpect(directTransport.closed).toBe(false);\n\texpect(directTransport.appData).toEqual({ foo: 'bar' });\n\n\tconst dump = await directTransport.dump();\n\n\texpect(dump.id).toBe(directTransport.id);\n\texpect(dump.producerIds).toEqual([]);\n\texpect(dump.consumerIds).toEqual([]);\n\texpect(dump.dataProducerIds).toEqual([]);\n\texpect(dump.dataConsumerIds).toEqual([]);\n\texpect(dump.recvRtpHeaderExtensions).toBeDefined();\n\texpect(typeof dump.rtpListener).toBe('object');\n\n\tdirectTransport.close();\n\texpect(directTransport.closed).toBe(true);\n}, 2000);\n\ntest('router.createDirectTransport() with wrong arguments rejects with TypeError', async () => {\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createDirectTransport({ maxMessageSize: 'foo' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createDirectTransport({ maxMessageSize: -2000 })\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('directTransport.getStats() succeeds', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\n\tconst stats = await directTransport.getStats();\n\n\texpect(Array.isArray(stats)).toBe(true);\n\texpect(stats.length).toBe(1);\n\texpect(stats[0]!.type).toBe('direct-transport');\n\texpect(stats[0]!.transportId).toBe(directTransport.id);\n\texpect(typeof stats[0]!.timestamp).toBe('number');\n\texpect(stats[0]!.bytesReceived).toBe(0);\n\texpect(stats[0]!.recvBitrate).toBe(0);\n\texpect(stats[0]!.bytesSent).toBe(0);\n\texpect(stats[0]!.sendBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesReceived).toBe(0);\n\texpect(stats[0]!.rtpRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesSent).toBe(0);\n\texpect(stats[0]!.rtpSendBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesReceived).toBe(0);\n\texpect(stats[0]!.rtxRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesSent).toBe(0);\n\texpect(stats[0]!.rtxSendBitrate).toBe(0);\n\texpect(stats[0]!.probationBytesSent).toBe(0);\n\texpect(stats[0]!.probationSendBitrate).toBe(0);\n}, 2000);\n\ntest('directTransport.connect() succeeds', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\n\tawait expect(directTransport.connect()).resolves.toBeUndefined();\n}, 2000);\n\ntest('dataProducer.send() succeeds', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\tconst dataProducer = await directTransport.produceData({\n\t\tlabel: 'foo',\n\t\tprotocol: 'bar',\n\t\tappData: { foo: 'bar' },\n\t});\n\tconst dataConsumer = await directTransport.consumeData({\n\t\tdataProducerId: dataProducer.id,\n\t});\n\tconst numMessages = 200;\n\tconst pauseSendingAtMessage = 10;\n\tconst resumeSendingAtMessage = 20;\n\tconst pauseReceivingAtMessage = 40;\n\tconst resumeReceivingAtMessage = 60;\n\tconst expectedReceivedNumMessages =\n\t\tnumMessages -\n\t\t(resumeSendingAtMessage - pauseSendingAtMessage) -\n\t\t(resumeReceivingAtMessage - pauseReceivingAtMessage);\n\n\tlet sentMessageBytes = 0;\n\tlet effectivelySentMessageBytes = 0;\n\tlet recvMessageBytes = 0;\n\tlet numSentMessages = 0;\n\tlet numReceivedMessages = 0;\n\n\tasync function sendNextMessage(): Promise<void> {\n\t\tconst id = ++numSentMessages;\n\t\tlet message: Buffer | string;\n\n\t\tif (id === pauseSendingAtMessage) {\n\t\t\tawait dataProducer.pause();\n\t\t} else if (id === resumeSendingAtMessage) {\n\t\t\tawait dataProducer.resume();\n\t\t} else if (id === pauseReceivingAtMessage) {\n\t\t\tawait dataConsumer.pause();\n\t\t} else if (id === resumeReceivingAtMessage) {\n\t\t\tawait dataConsumer.resume();\n\t\t}\n\n\t\tlet messageSize: number;\n\n\t\t// Send string (WebRTC DataChannel string).\n\t\tif (id < numMessages / 2) {\n\t\t\tmessage = String(id);\n\t\t\tmessageSize = Buffer.from(message).byteLength;\n\t\t}\n\t\t// Send string (WebRTC DataChannel binary).\n\t\telse {\n\t\t\tmessage = Buffer.from(String(id));\n\t\t\tmessageSize = message.byteLength;\n\t\t}\n\n\t\tdataProducer.send(message);\n\n\t\tsentMessageBytes += messageSize;\n\n\t\tif (!dataProducer.paused && !dataConsumer.paused) {\n\t\t\teffectivelySentMessageBytes += messageSize;\n\t\t}\n\n\t\tif (id < numMessages) {\n\t\t\tvoid sendNextMessage();\n\t\t}\n\t}\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tdataProducer.on('listenererror', (eventName, error) => {\n\t\t\treject(\n\t\t\t\tnew Error(\n\t\t\t\t\t`dataProducer 'listenererror' [eventName:${eventName}]: ${error.toString()}`\n\t\t\t\t)\n\t\t\t);\n\t\t});\n\n\t\tdataConsumer.on('listenererror', (eventName, error) => {\n\t\t\treject(\n\t\t\t\tnew Error(\n\t\t\t\t\t`dataConsumer 'listenererror' [eventName:${eventName}]: ${error.toString()}`\n\t\t\t\t)\n\t\t\t);\n\t\t});\n\n\t\tdataConsumer.on('message', (message, ppid) => {\n\t\t\t++numReceivedMessages;\n\n\t\t\t// message is always a Buffer.\n\t\t\trecvMessageBytes += message.byteLength;\n\n\t\t\tconst id = Number(message.toString('utf8'));\n\n\t\t\tif (id === numMessages) {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t\t// PPID of WebRTC DataChannel string.\n\t\t\telse if (id < numMessages / 2 && ppid !== 51) {\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`ppid in message with id ${id} should be 51 but it is ${ppid}`\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t\t// PPID of WebRTC DataChannel binary.\n\t\t\telse if (id > numMessages / 2 && ppid !== 53) {\n\t\t\t\treject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t`ppid in message with id ${id} should be 53 but it is ${ppid}`\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\tvoid sendNextMessage();\n\t});\n\n\texpect(numSentMessages).toBe(numMessages);\n\texpect(numReceivedMessages).toBe(expectedReceivedNumMessages);\n\texpect(recvMessageBytes).toBe(effectivelySentMessageBytes);\n\n\tawait expect(dataProducer.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-producer',\n\t\t\tlabel: dataProducer.label,\n\t\t\tprotocol: dataProducer.protocol,\n\t\t\tmessagesReceived: numMessages,\n\t\t\tbytesReceived: sentMessageBytes,\n\t\t},\n\t]);\n\n\tawait expect(dataConsumer.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-consumer',\n\t\t\tlabel: dataConsumer.label,\n\t\t\tprotocol: dataConsumer.protocol,\n\t\t\tmessagesSent: expectedReceivedNumMessages,\n\t\t\tbytesSent: recvMessageBytes,\n\t\t},\n\t]);\n}, 5000);\n\ntest('dataProducer.send() with subchannels succeeds', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\tconst dataProducer = await directTransport.produceData();\n\tconst dataConsumer1 = await directTransport.consumeData({\n\t\tdataProducerId: dataProducer.id,\n\t\tsubchannels: [1, 11, 666],\n\t});\n\tconst dataConsumer2 = await directTransport.consumeData({\n\t\tdataProducerId: dataProducer.id,\n\t\tsubchannels: [2, 22, 666],\n\t});\n\tconst expectedReceivedNumMessages1 = 7;\n\tconst expectedReceivedNumMessages2 = 5;\n\tconst receivedMessages1: string[] = [];\n\tconst receivedMessages2: string[] = [];\n\n\tawait new Promise<void>(resolve => {\n\t\t// Must be received by dataConsumer1 and dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'both',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ undefined,\n\t\t\t/* requiredSubchannel */ undefined\n\t\t);\n\n\t\t// Must be received by dataConsumer1 and dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'both',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [1, 2],\n\t\t\t/* requiredSubchannel */ undefined\n\t\t);\n\n\t\t// Must be received by dataConsumer1 and dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'both',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [11, 22, 33],\n\t\t\t/* requiredSubchannel */ 666\n\t\t);\n\n\t\t// Must not be received by neither dataConsumer1 nor dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'none',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [3],\n\t\t\t/* requiredSubchannel */ 666\n\t\t);\n\n\t\t// Must not be received by neither dataConsumer1 nor dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'none',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [666],\n\t\t\t/* requiredSubchannel */ 3\n\t\t);\n\n\t\t// Must be received by dataConsumer1.\n\t\tdataProducer.send(\n\t\t\t'dc1',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [1],\n\t\t\t/* requiredSubchannel */ undefined\n\t\t);\n\n\t\t// Must be received by dataConsumer1.\n\t\tdataProducer.send(\n\t\t\t'dc1',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [11],\n\t\t\t/* requiredSubchannel */ 1\n\t\t);\n\n\t\t// Must be received by dataConsumer1.\n\t\tdataProducer.send(\n\t\t\t'dc1',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [666],\n\t\t\t/* requiredSubchannel */ 11\n\t\t);\n\n\t\t// Must be received by dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'dc2',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [666],\n\t\t\t/* requiredSubchannel */ 2\n\t\t);\n\n\t\t// Make dataConsumer2 also subscribe to subchannel 1.\n\t\t// NOTE: No need to await for this call.\n\t\tvoid dataConsumer2.setSubchannels([...dataConsumer2.subchannels, 1]);\n\n\t\t// Must be received by dataConsumer1 and dataConsumer2.\n\t\tdataProducer.send(\n\t\t\t'both',\n\t\t\t/* ppid */ undefined,\n\t\t\t/* subchannels */ [1],\n\t\t\t/* requiredSubchannel */ 666\n\t\t);\n\n\t\tdataConsumer1.on('message', message => {\n\t\t\treceivedMessages1.push(message.toString('utf8'));\n\n\t\t\tif (\n\t\t\t\treceivedMessages1.length === expectedReceivedNumMessages1 &&\n\t\t\t\treceivedMessages2.length === expectedReceivedNumMessages2\n\t\t\t) {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t});\n\n\t\tdataConsumer2.on('message', message => {\n\t\t\treceivedMessages2.push(message.toString('utf8'));\n\n\t\t\tif (\n\t\t\t\treceivedMessages1.length === expectedReceivedNumMessages1 &&\n\t\t\t\treceivedMessages2.length === expectedReceivedNumMessages2\n\t\t\t) {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t});\n\t});\n\n\texpect(receivedMessages1.length).toBe(expectedReceivedNumMessages1);\n\texpect(receivedMessages2.length).toBe(expectedReceivedNumMessages2);\n\n\tfor (const message of receivedMessages1) {\n\t\texpect(['both', 'dc1'].includes(message)).toBe(true);\n\t\texpect(['dc2'].includes(message)).toBe(false);\n\t}\n\n\tfor (const message of receivedMessages2) {\n\t\texpect(['both', 'dc2'].includes(message)).toBe(true);\n\t\texpect(['dc1'].includes(message)).toBe(false);\n\t}\n}, 5000);\n\ntest('DirectTransport methods reject if closed', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\tconst onObserverClose = jest.fn();\n\n\tdirectTransport.observer.once('close', onObserverClose);\n\tdirectTransport.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(directTransport.closed).toBe(true);\n\n\tawait expect(directTransport.dump()).rejects.toThrow(Error);\n\n\tawait expect(directTransport.getStats()).rejects.toThrow(Error);\n}, 2000);\n\ntest('DirectTransport emits \"routerclose\" if Router is closed', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\tconst onObserverClose = jest.fn();\n\n\tdirectTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<DirectTransportEvents>(\n\t\tdirectTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.router!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(directTransport.closed).toBe(true);\n}, 2000);\n\ntest('DirectTransport emits \"routerclose\" if Worker is closed', async () => {\n\tconst directTransport = await ctx.router!.createDirectTransport();\n\tconst onObserverClose = jest.fn();\n\n\tdirectTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<DirectTransportEvents>(\n\t\tdirectTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(directTransport.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-PipeTransport.ts",
    "content": "import { pickPort } from 'pick-port';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type {\n\tWorkerEvents,\n\tConsumerEvents,\n\tProducerObserverEvents,\n\tDataConsumerEvents,\n} from '../types';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\taudioProducerOptions: mediasoup.types.ProducerOptions;\n\tvideoProducerOptions: mediasoup.types.ProducerOptions;\n\tdataProducerOptions: mediasoup.types.DataProducerOptions;\n\tconsumerDeviceCapabilities: mediasoup.types.RtpCapabilities;\n\tworker1?: mediasoup.types.Worker;\n\tworker2?: mediasoup.types.Worker;\n\trouter1?: mediasoup.types.Router;\n\trouter2?: mediasoup.types.Router;\n\twebRtcTransport1?: mediasoup.types.WebRtcTransport;\n\twebRtcTransport2?: mediasoup.types.WebRtcTransport;\n\taudioProducer?: mediasoup.types.Producer;\n\tvideoProducer?: mediasoup.types.Producer;\n\tvideoConsumer?: mediasoup.types.Consumer;\n\tdataProducer?: mediasoup.types.DataProducer;\n\tdataConsumer?: mediasoup.types.DataConsumer;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t]),\n\taudioProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpayloadType: 111,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tuseinbandfec: 1,\n\t\t\t\t\t\tfoo: 'bar1',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [{ ssrc: 11111111 }],\n\t\t\trtcp: {\n\t\t\t\tcname: 'FOOBAR',\n\t\t\t},\n\t\t},\n\t\tappData: { foo: 'bar1' },\n\t}),\n\tvideoProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'video',\n\t\trtpParameters: {\n\t\t\tmid: 'VIDEO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/VP8',\n\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\trtcpFeedback: [\n\t\t\t\t\t\t{ type: 'nack' },\n\t\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t\t\t{ type: 'lalala' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\t\t\tid: 11,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\t\tid: 13,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [{ ssrc: 22222222 }, { ssrc: 22222223 }, { ssrc: 22222224 }],\n\t\t\trtcp: {\n\t\t\t\tcname: 'FOOBAR',\n\t\t\t},\n\t\t\tmsid: 'aaaa-bbbb',\n\t\t},\n\t\tappData: { foo: 'bar2' },\n\t}),\n\tdataProducerOptions: utils.deepFreeze<mediasoup.types.DataProducerOptions>({\n\t\tsctpStreamParameters: {\n\t\t\tstreamId: 666,\n\t\t\tordered: false,\n\t\t\tmaxPacketLifeTime: 5000,\n\t\t},\n\t\tlabel: 'foo',\n\t\tprotocol: 'bar',\n\t}),\n\tconsumerDeviceCapabilities: utils.deepFreeze<mediasoup.types.RtpCapabilities>(\n\t\t{\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\tmimeType: 'video/VP8',\n\t\t\t\t\tpreferredPayloadType: 101,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\trtcpFeedback: [\n\t\t\t\t\t\t{ type: 'nack' },\n\t\t\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t\t\t{ type: 'transport-cc' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\tpreferredPayloadType: 102,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tapt: 101,\n\t\t\t\t\t},\n\t\t\t\t\trtcpFeedback: [],\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\t\t\tpreferredId: 4,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t\tdirection: 'sendrecv',\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'video',\n\t\t\t\t\turi: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',\n\t\t\t\t\tpreferredId: 5,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tpreferredId: 6,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t),\n};\n\nbeforeEach(async () => {\n\tctx.worker1 = await mediasoup.createWorker();\n\tctx.worker2 = await mediasoup.createWorker();\n\tctx.router1 = await ctx.worker1.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tctx.router2 = await ctx.worker2.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tctx.webRtcTransport1 = await ctx.router1.createWebRtcTransport({\n\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1' }],\n\t\tenableSctp: true,\n\t});\n\tctx.webRtcTransport2 = await ctx.router2.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n\tctx.audioProducer = await ctx.webRtcTransport1.produce(\n\t\tctx.audioProducerOptions\n\t);\n\tctx.videoProducer = await ctx.webRtcTransport1.produce(\n\t\tctx.videoProducerOptions\n\t);\n\tctx.dataProducer = await ctx.webRtcTransport1.produceData(\n\t\tctx.dataProducerOptions\n\t);\n});\n\nafterEach(async () => {\n\tctx.worker1?.close();\n\tctx.worker2?.close();\n\n\tif (ctx.worker1?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker1, 'subprocessclose');\n\t}\n\n\tif (ctx.worker2?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker2, 'subprocessclose');\n\t}\n});\n\ntest('router.pipeToRouter() succeeds with audio', async () => {\n\tconst { pipeConsumer, pipeProducer } = (await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.audioProducer!.id,\n\t\trouter: ctx.router2!,\n\t})) as {\n\t\tpipeConsumer: mediasoup.types.Consumer;\n\t\tpipeProducer: mediasoup.types.Producer;\n\t};\n\n\tconst dump1 = await ctx.router1!.dump();\n\n\t// There should be two Transports in router1:\n\t// - WebRtcTransport for audioProducer and videoProducer.\n\t// - PipeTransport between router1 and router2.\n\texpect(dump1.transportIds.length).toBe(2);\n\n\tconst dump2 = await ctx.router2!.dump();\n\n\t// There should be two Transports in router2:\n\t// - WebRtcTransport for audioConsumer and videoConsumer.\n\t// - PipeTransport between router2 and router1.\n\texpect(dump2.transportIds.length).toBe(2);\n\n\texpect(typeof pipeConsumer.id).toBe('string');\n\texpect(pipeConsumer.closed).toBe(false);\n\texpect(pipeConsumer.kind).toBe('audio');\n\texpect(typeof pipeConsumer.rtpParameters).toBe('object');\n\texpect(pipeConsumer.rtpParameters.mid).toBeUndefined();\n\texpect(pipeConsumer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tpayloadType: 100,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar1',\n\t\t\t},\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n\n\texpect(pipeConsumer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\tid: 6,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\tencrypt: false,\n\t\t\tid: 7,\n\t\t\tparameters: {},\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tid: 10,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tid: 11,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tid: 12,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\texpect(pipeConsumer.type).toBe('pipe');\n\texpect(pipeConsumer.paused).toBe(false);\n\texpect(pipeConsumer.producerPaused).toBe(false);\n\texpect(pipeConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 10,\n\t\tproducerScores: [],\n\t});\n\texpect(pipeConsumer.appData).toEqual({});\n\n\texpect(pipeProducer.id).toBe(ctx.audioProducer!.id);\n\texpect(pipeProducer.closed).toBe(false);\n\texpect(pipeProducer.kind).toBe('audio');\n\texpect(typeof pipeProducer.rtpParameters).toBe('object');\n\texpect(pipeProducer.rtpParameters.mid).toBeUndefined();\n\texpect(pipeProducer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'audio/opus',\n\t\t\tpayloadType: 100,\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar1',\n\t\t\t},\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n\texpect(pipeProducer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\tid: 6,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\tencrypt: false,\n\t\t\tid: 7,\n\t\t\tparameters: {},\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tid: 10,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tid: 11,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tid: 12,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\texpect(pipeProducer.paused).toBe(false);\n}, 2000);\n\ntest('router.pipeToRouter() succeeds with video', async () => {\n\tawait ctx.videoProducer!.pause();\n\n\tconst { pipeConsumer, pipeProducer } = (await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trouter: ctx.router2!,\n\t})) as {\n\t\tpipeConsumer: mediasoup.types.Consumer;\n\t\tpipeProducer: mediasoup.types.Producer;\n\t};\n\n\tconst dump1 = await ctx.router1!.dump();\n\n\t// No new PipeTransport should has been created. The existing one is used.\n\texpect(dump1.transportIds.length).toBe(2);\n\n\tconst dump2 = await ctx.router2!.dump();\n\n\t// No new PipeTransport should has been created. The existing one is used.\n\texpect(dump2.transportIds.length).toBe(2);\n\n\texpect(typeof pipeConsumer.id).toBe('string');\n\texpect(pipeConsumer.closed).toBe(false);\n\texpect(pipeConsumer.kind).toBe('video');\n\texpect(typeof pipeConsumer.rtpParameters).toBe('object');\n\texpect(pipeConsumer.rtpParameters.mid).toBeUndefined();\n\texpect(pipeConsumer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'video/VP8',\n\t\t\tpayloadType: 101,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t],\n\t\t},\n\t]);\n\texpect(pipeConsumer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t\tid: 7,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 8,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tid: 9,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tid: 10,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tid: 11,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tid: 12,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\n\texpect(pipeConsumer.type).toBe('pipe');\n\texpect(pipeConsumer.paused).toBe(false);\n\texpect(pipeConsumer.producerPaused).toBe(true);\n\texpect(pipeConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 10,\n\t\tproducerScores: [],\n\t});\n\texpect(pipeConsumer.appData).toEqual({});\n\n\texpect(pipeProducer.id).toBe(ctx.videoProducer!.id);\n\texpect(pipeProducer.closed).toBe(false);\n\texpect(pipeProducer.kind).toBe('video');\n\texpect(typeof pipeProducer.rtpParameters).toBe('object');\n\texpect(pipeProducer.rtpParameters.mid).toBeUndefined();\n\texpect(pipeProducer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'video/VP8',\n\t\t\tpayloadType: 101,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t],\n\t\t},\n\t]);\n\texpect(pipeProducer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t\tid: 7,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 8,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tid: 9,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tid: 10,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tid: 11,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tid: 12,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\texpect(pipeProducer.paused).toBe(true);\n}, 2000);\n\ntest('router.createPipeTransport() with wrong arguments rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(ctx.router1!.createPipeTransport({})).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router1!.createPipeTransport({\n\t\t\tlistenInfo: {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tportRange: { min: 4000, max: 3000 },\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router1!.createPipeTransport({ listenIp: '123' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router1!.createPipeTransport({ listenIp: ['127.0.0.1'] })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router1!.createPipeTransport({\n\t\t\tlistenInfo: { protocol: 'tcp', ip: '127.0.0.1' },\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router1!.createPipeTransport({\n\t\t\tlistenInfo: { protocol: 'udp', ip: '127.0.0.1' },\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tappData: 'NOT-AN-OBJECT',\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('router.createPipeTransport() with enableRtx succeeds', async () => {\n\tconst pipeTransport = await ctx.router1!.createPipeTransport({\n\t\tlistenInfo: {\n\t\t\tprotocol: 'udp',\n\t\t\tip: '127.0.0.1',\n\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t},\n\t\tenableRtx: true,\n\t});\n\n\texpect(pipeTransport.type).toBe('pipe');\n\n\tconst pipeConsumer = await pipeTransport.consume({\n\t\tproducerId: ctx.videoProducer!.id,\n\t});\n\n\texpect(typeof pipeConsumer.id).toBe('string');\n\texpect(pipeConsumer.closed).toBe(false);\n\texpect(pipeConsumer.kind).toBe('video');\n\texpect(typeof pipeConsumer.rtpParameters).toBe('object');\n\texpect(pipeConsumer.rtpParameters.mid).toBeUndefined();\n\texpect(pipeConsumer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'video/VP8',\n\t\t\tpayloadType: 101,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tmimeType: 'video/rtx',\n\t\t\tpayloadType: 102,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: { apt: 101 },\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n\texpect(pipeConsumer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension',\n\t\t\tid: 7,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 8,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tid: 9,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time',\n\t\t\tid: 10,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay',\n\t\t\tid: 11,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:mediasoup:params:rtp-hdrext:packet-id',\n\t\t\tid: 12,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\n\texpect(pipeConsumer.type).toBe('pipe');\n\texpect(pipeConsumer.paused).toBe(false);\n\texpect(pipeConsumer.producerPaused).toBe(false);\n\texpect(pipeConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 10,\n\t\tproducerScores: [],\n\t});\n\texpect(pipeConsumer.appData).toEqual({});\n}, 2000);\n\ntest('pipeTransport.connect() with valid SRTP parameters succeeds', async () => {\n\tconst pipeTransport = await ctx.router1!.createPipeTransport({\n\t\tlistenIp: '127.0.0.1',\n\t\tenableSrtp: true,\n\t});\n\n\texpect(typeof pipeTransport.srtpParameters).toBe('object');\n\t// The master length of AEAD_AES_256_GCM.\n\texpect(pipeTransport.srtpParameters?.keyBase64.length).toBe(60);\n\n\t// Valid srtpParameters.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AEAD_AES_256_GCM',\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('pipeTransport.connect() with srtpParameters fails if enableSrtp is unset', async () => {\n\tconst pipeTransport = await ctx.router1!.createPipeTransport({\n\t\tlistenInfo: {\n\t\t\tprotocol: 'udp',\n\t\t\tip: '127.0.0.1',\n\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t},\n\t\tenableRtx: true,\n\t});\n\n\texpect(pipeTransport.srtpParameters).toBeUndefined();\n\n\t// No SRTP enabled so passing srtpParameters must fail.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AEAD_AES_256_GCM',\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// No SRTP enabled so passing srtpParameters (even if invalid) must fail.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: 'invalid',\n\t\t})\n\t).rejects.toThrow(TypeError);\n});\n\ntest('pipeTransport.connect() with invalid srtpParameters fails', async () => {\n\tconst pipeTransport = await ctx.router1!.createPipeTransport({\n\t\tlistenIp: '127.0.0.1',\n\t\tenableSrtp: true,\n\t});\n\n\texpect(typeof pipeTransport.id).toBe('string');\n\texpect(typeof pipeTransport.srtpParameters).toBe('object');\n\t// The master length of AEAD_AES_256_GCM.\n\texpect(pipeTransport.srtpParameters?.keyBase64.length).toBe(60);\n\n\t// Missing srtpParameters.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: 1,\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Missing srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: {\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Missing srtpParameters.keyBase64.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AEAD_AES_256_GCM',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tcryptoSuite: 'FOO',\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tcryptoSuite: 123,\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.keyBase64.\n\tawait expect(\n\t\tpipeTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AEAD_AES_256_GCM',\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tkeyBase64: [],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('router.createPipeTransport() with fixed port succeeds', async () => {\n\tconst port = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst pipeTransport = await ctx.router1!.createPipeTransport({\n\t\tlistenInfo: { protocol: 'udp', ip: '127.0.0.1', port },\n\t});\n\n\texpect(pipeTransport.tuple.localPort).toEqual(port);\n}, 2000);\n\ntest('transport.consume() for a pipe Producer succeeds', async () => {\n\tconst { pipeProducer } = await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trouter: ctx.router2!,\n\t});\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: pipeProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(typeof videoConsumer.id).toBe('string');\n\texpect(videoConsumer.closed).toBe(false);\n\texpect(videoConsumer.kind).toBe('video');\n\texpect(typeof videoConsumer.rtpParameters).toBe('object');\n\texpect(videoConsumer.rtpParameters.mid).toBe('0');\n\texpect(videoConsumer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'video/VP8',\n\t\t\tpayloadType: 101,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {},\n\t\t\trtcpFeedback: [\n\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t\t{ type: 'transport-cc', parameter: '' },\n\t\t\t],\n\t\t},\n\t\t{\n\t\t\tmimeType: 'video/rtx',\n\t\t\tpayloadType: 102,\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\tapt: 101,\n\t\t\t},\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n\texpect(videoConsumer.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\tid: 4,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01',\n\t\t\tid: 5,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\texpect(videoConsumer.rtpParameters.encodings?.length).toBe(1);\n\texpect(typeof videoConsumer.rtpParameters.encodings![0]!.ssrc).toBe('number');\n\texpect(typeof videoConsumer.rtpParameters.encodings![0]!.rtx).toBe('object');\n\texpect(typeof videoConsumer.rtpParameters.encodings![0]!.rtx?.ssrc).toBe(\n\t\t'number'\n\t);\n\texpect(videoConsumer.rtpParameters.msid).toBe('aaaa-bbbb');\n\texpect(videoConsumer.type).toBe('simulcast');\n\texpect(videoConsumer.paused).toBe(false);\n\texpect(videoConsumer.producerPaused).toBe(false);\n\texpect(videoConsumer.score).toEqual({\n\t\tscore: 10,\n\t\tproducerScore: 0,\n\t\tproducerScores: [0, 0, 0],\n\t});\n\texpect(videoConsumer.appData).toEqual({});\n}, 2000);\n\ntest('producer.pause() and producer.resume() are transmitted to pipe Consumer', async () => {\n\tawait ctx.videoProducer!.pause();\n\n\t// We need to obtain the pipeProducer to await for its 'puase' and 'resume'\n\t// events, otherwise we may get errors like this:\n\t// InvalidStateError: Channel closed, pending request aborted [method:PRODUCER_PAUSE, id:8]\n\t// See related fixed issue:\n\t// https://github.com/versatica/mediasoup/issues/1374\n\tconst { pipeProducer: pipeVideoProducer } = await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trouter: ctx.router2!,\n\t});\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: pipeVideoProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(ctx.videoProducer!.paused).toBe(true);\n\texpect(videoConsumer.producerPaused).toBe(true);\n\texpect(videoConsumer.paused).toBe(false);\n\n\t// NOTE: Let's use a Promise since otherwise there may be race conditions\n\t// between events and await lines below.\n\tconst promise1 = enhancedOnce<ConsumerEvents>(\n\t\tvideoConsumer,\n\t\t'producerresume'\n\t);\n\tconst promise2 = enhancedOnce<ProducerObserverEvents>(\n\t\tpipeVideoProducer!.observer,\n\t\t'resume'\n\t);\n\n\tawait ctx.videoProducer!.resume();\n\tawait Promise.all([promise1, promise2]);\n\n\texpect(videoConsumer.producerPaused).toBe(false);\n\texpect(videoConsumer.paused).toBe(false);\n\texpect(pipeVideoProducer!.paused).toBe(false);\n\n\tconst promise3 = enhancedOnce<ConsumerEvents>(videoConsumer, 'producerpause');\n\tconst promise4 = enhancedOnce<ProducerObserverEvents>(\n\t\tpipeVideoProducer!.observer,\n\t\t'pause'\n\t);\n\n\tawait ctx.videoProducer!.pause();\n\tawait Promise.all([promise3, promise4]);\n\n\texpect(videoConsumer.producerPaused).toBe(true);\n\texpect(videoConsumer.paused).toBe(false);\n\texpect(pipeVideoProducer!.paused).toBe(true);\n}, 2000);\n\ntest('producer.close() is transmitted to pipe Consumer', async () => {\n\tconst { pipeProducer } = await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trouter: ctx.router2!,\n\t});\n\n\tconst videoConsumer = await ctx.webRtcTransport2!.consume({\n\t\tproducerId: pipeProducer!.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\tctx.videoProducer!.close();\n\n\texpect(ctx.videoProducer!.closed).toBe(true);\n\n\tif (!videoConsumer.closed) {\n\t\tawait enhancedOnce<ConsumerEvents>(videoConsumer, 'producerclose');\n\t}\n\n\texpect(videoConsumer.closed).toBe(true);\n}, 2000);\n\ntest('router.pipeToRouter() with keepId: true fails if both Routers belong to the same Worker', async () => {\n\tconst router1bis = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tawait expect(\n\t\tctx.router1!.pipeToRouter({\n\t\t\tproducerId: ctx.videoProducer!.id,\n\t\t\trouter: router1bis,\n\t\t\t// Default value is true.\n\t\t\tkeepId: true,\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('router.pipeToRouter() with keepId: false does not fail if both Routers belong to the same Worker', async () => {\n\tconst router1bis = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tconst { pipeProducer } = await ctx.router1!.pipeToRouter({\n\t\tproducerId: ctx.videoProducer!.id,\n\t\trouter: router1bis,\n\t\tkeepId: false,\n\t});\n\n\texpect(pipeProducer!.id).not.toBe(ctx.videoProducer!.id);\n}, 2000);\n\ntest('router.pipeToRouter() succeeds with data', async () => {\n\tconst { pipeDataConsumer, pipeDataProducer } =\n\t\t(await ctx.router1!.pipeToRouter({\n\t\t\tdataProducerId: ctx.dataProducer!.id,\n\t\t\trouter: ctx.router2!,\n\t\t})) as {\n\t\t\tpipeDataConsumer: mediasoup.types.DataConsumer;\n\t\t\tpipeDataProducer: mediasoup.types.DataProducer;\n\t\t};\n\n\tconst dump1 = await ctx.router1!.dump();\n\n\t// There should be two Transports in router1:\n\t// - WebRtcTransport for audioProducer, videoProducer and dataProducer.\n\t// - PipeTransport between router1 and router2.\n\texpect(dump1.transportIds.length).toBe(2);\n\n\tconst dump2 = await ctx.router2!.dump();\n\n\t// There should be two Transports in router2:\n\t// - WebRtcTransport for audioConsumer, videoConsumer and dataConsumer.\n\t// - PipeTransport between router2 and router1.\n\texpect(dump2.transportIds.length).toBe(2);\n\n\texpect(typeof pipeDataConsumer.id).toBe('string');\n\texpect(pipeDataConsumer.closed).toBe(false);\n\texpect(pipeDataConsumer.type).toBe('sctp');\n\texpect(typeof pipeDataConsumer.sctpStreamParameters).toBe('object');\n\texpect(typeof pipeDataConsumer.sctpStreamParameters?.streamId).toBe('number');\n\texpect(pipeDataConsumer.sctpStreamParameters?.ordered).toBe(false);\n\texpect(pipeDataConsumer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000);\n\texpect(pipeDataConsumer.sctpStreamParameters?.maxRetransmits).toBeUndefined();\n\texpect(pipeDataConsumer.label).toBe('foo');\n\texpect(pipeDataConsumer.protocol).toBe('bar');\n\n\texpect(pipeDataProducer.id).toBe(ctx.dataProducer!.id);\n\texpect(pipeDataProducer.closed).toBe(false);\n\texpect(pipeDataProducer.type).toBe('sctp');\n\texpect(typeof pipeDataProducer.sctpStreamParameters).toBe('object');\n\texpect(typeof pipeDataProducer.sctpStreamParameters?.streamId).toBe('number');\n\texpect(pipeDataProducer.sctpStreamParameters?.ordered).toBe(false);\n\texpect(pipeDataProducer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000);\n\texpect(pipeDataProducer.sctpStreamParameters?.maxRetransmits).toBeUndefined();\n\texpect(pipeDataProducer.label).toBe('foo');\n\texpect(pipeDataProducer.protocol).toBe('bar');\n}, 2000);\n\ntest('transport.dataConsume() for a pipe DataProducer succeeds', async () => {\n\tconst { pipeDataProducer } = await ctx.router1!.pipeToRouter({\n\t\tdataProducerId: ctx.dataProducer!.id,\n\t\trouter: ctx.router2!,\n\t});\n\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: pipeDataProducer!.id,\n\t});\n\n\texpect(typeof dataConsumer.id).toBe('string');\n\texpect(dataConsumer.closed).toBe(false);\n\texpect(dataConsumer.type).toBe('sctp');\n\texpect(typeof dataConsumer.sctpStreamParameters).toBe('object');\n\texpect(typeof dataConsumer.sctpStreamParameters?.streamId).toBe('number');\n\texpect(dataConsumer.sctpStreamParameters?.ordered).toBe(false);\n\texpect(dataConsumer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000);\n\texpect(dataConsumer.sctpStreamParameters?.maxRetransmits).toBeUndefined();\n\texpect(dataConsumer.label).toBe('foo');\n\texpect(dataConsumer.protocol).toBe('bar');\n}, 2000);\n\ntest('dataProducer.close() is transmitted to pipe DataConsumer', async () => {\n\tconst { pipeDataProducer } = await ctx.router1!.pipeToRouter({\n\t\tdataProducerId: ctx.dataProducer!.id,\n\t\trouter: ctx.router2!,\n\t});\n\n\tconst dataConsumer = await ctx.webRtcTransport2!.consumeData({\n\t\tdataProducerId: pipeDataProducer!.id,\n\t});\n\n\tctx.dataProducer!.close();\n\n\texpect(ctx.dataProducer!.closed).toBe(true);\n\n\tif (!dataConsumer.closed) {\n\t\tawait enhancedOnce<DataConsumerEvents>(dataConsumer, 'dataproducerclose');\n\t}\n\n\texpect(dataConsumer.closed).toBe(true);\n}, 2000);\n\ntest('router.pipeToRouter() called twice generates a single PipeTransport pair', async () => {\n\tconst routerA = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tconst routerB = await ctx.worker2!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tconst transportA1 = await routerA.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tconst transportA2 = await routerA.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tconst audioProducerA1 = await transportA1.produce(ctx.audioProducerOptions);\n\tconst audioProducerA2 = await transportA2.produce(ctx.audioProducerOptions);\n\n\tawait Promise.all([\n\t\trouterA.pipeToRouter({\n\t\t\tproducerId: audioProducerA1.id,\n\t\t\trouter: routerB,\n\t\t}),\n\t\trouterA.pipeToRouter({\n\t\t\tproducerId: audioProducerA2.id,\n\t\t\trouter: routerB,\n\t\t}),\n\t]);\n\n\tconst dump1 = await routerA.dump();\n\n\t// There should be 3 Transports in routerA:\n\t// - WebRtcTransport for audioProducerA1 and audioProducerA2.\n\t// - PipeTransport between routerA and routerB.\n\texpect(dump1.transportIds.length).toBe(3);\n\n\tconst dump2 = await routerB.dump();\n\n\t// There should be 1 Transport in routerB:\n\t// - PipeTransport between routerA and routerB.\n\texpect(dump2.transportIds.length).toBe(1);\n}, 2000);\n\ntest('router.pipeToRouter() called in two Routers passing one to each other as argument generates a single PipeTransport pair', async () => {\n\tconst routerA = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tconst routerB = await ctx.worker2!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\tconst transportA = await routerA.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tconst transportB = await routerB.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tconst audioProducerA = await transportA.produce(ctx.audioProducerOptions);\n\tconst audioProducerB = await transportB.produce(ctx.audioProducerOptions);\n\tconst pipeTransportsA = new Map();\n\tconst pipeTransportsB = new Map();\n\n\trouterA.observer.on('newtransport', transport => {\n\t\tif (transport.constructor.name !== 'PipeTransportImpl') {\n\t\t\treturn;\n\t\t}\n\n\t\tpipeTransportsA.set(transport.id, transport);\n\t\ttransport.observer.on('close', () => pipeTransportsA.delete(transport.id));\n\t});\n\n\trouterB.observer.on('newtransport', transport => {\n\t\tif (transport.constructor.name !== 'PipeTransportImpl') {\n\t\t\treturn;\n\t\t}\n\n\t\tpipeTransportsB.set(transport.id, transport);\n\t\ttransport.observer.on('close', () => pipeTransportsB.delete(transport.id));\n\t});\n\n\tawait Promise.all([\n\t\trouterA.pipeToRouter({\n\t\t\tproducerId: audioProducerA.id,\n\t\t\trouter: routerB,\n\t\t}),\n\t\trouterB.pipeToRouter({\n\t\t\tproducerId: audioProducerB.id,\n\t\t\trouter: routerA,\n\t\t}),\n\t]);\n\n\t// There should be a single PipeTransport in each Router and they must be\n\t// connected.\n\n\texpect(pipeTransportsA.size).toBe(1);\n\texpect(pipeTransportsB.size).toBe(1);\n\n\tconst pipeTransportA = Array.from(pipeTransportsA.values())[0];\n\tconst pipeTransportB = Array.from(pipeTransportsB.values())[0];\n\n\texpect(pipeTransportA.tuple.localPort).toBe(pipeTransportB.tuple.remotePort);\n\texpect(pipeTransportB.tuple.localPort).toBe(pipeTransportA.tuple.remotePort);\n\n\trouterA.close();\n\n\texpect(pipeTransportsA.size).toBe(0);\n\texpect(pipeTransportsB.size).toBe(0);\n}, 2000);\n\ntest('router.pipeToRouter() with neither producerId nor dataProducerId fails', async () => {\n\tconst router1bis = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tawait expect(\n\t\tctx.router1!.pipeToRouter({\n\t\t\trouter: router1bis,\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('router.pipeToRouter() with both producerId and dataProducerId fails', async () => {\n\tconst router1bis = await ctx.worker1!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tawait expect(\n\t\tctx.router1!.pipeToRouter({\n\t\t\tproducerId: '1234',\n\t\t\tdataProducerId: '5678',\n\t\t\trouter: router1bis,\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-PlainTransport.ts",
    "content": "import * as os from 'node:os';\nimport { pickPort } from 'pick-port';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, PlainTransportEvents } from '../types';\nimport * as utils from '../utils';\n\nconst IS_WINDOWS = os.platform() === 'win32';\nconst USE_BUILD_IN_SCTP_STACK = false;\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t\trtcpFeedback: [], // Will be ignored.\n\t\t},\n\t]),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker({\n\t\tuseBuiltInSctpStack: USE_BUILD_IN_SCTP_STACK,\n\t});\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('router.createPlainTransport() succeeds', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenInfo: {\n\t\t\tprotocol: 'udp',\n\t\t\tip: '127.0.0.1',\n\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t},\n\t});\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\ttransportIds: [plainTransport.id],\n\t});\n\n\tconst onObserverNewTransport = jest.fn();\n\n\tctx.router!.observer.once('newtransport', onObserverNewTransport);\n\n\t// Create a separate transport here.\n\tconst plainTransport2 = await ctx.router!.createPlainTransport({\n\t\tlistenInfo: {\n\t\t\tprotocol: 'udp',\n\t\t\tip: '127.0.0.1',\n\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t},\n\t\tenableSctp: true,\n\t\tappData: { foo: 'bar' },\n\t});\n\n\texpect(onObserverNewTransport).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewTransport).toHaveBeenCalledWith(plainTransport2);\n\texpect(typeof plainTransport2.id).toBe('string');\n\texpect(plainTransport2.closed).toBe(false);\n\texpect(plainTransport2.type).toBe('plain');\n\texpect(plainTransport2.appData).toEqual({ foo: 'bar' });\n\texpect(typeof plainTransport2.tuple).toBe('object');\n\t// @deprecated Use tuple.localAddress instead.\n\texpect(plainTransport2.tuple.localIp).toBe('9.9.9.1');\n\texpect(plainTransport2.tuple.localAddress).toBe('9.9.9.1');\n\texpect(typeof plainTransport2.tuple.localPort).toBe('number');\n\texpect(plainTransport2.tuple.protocol).toBe('udp');\n\texpect(plainTransport2.rtcpTuple).toBeUndefined();\n\texpect(plainTransport2.sctpParameters).toMatchObject({\n\t\tport: 5000,\n\t\t// NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the\n\t\t// transport is ignored.\n\t\tOS: USE_BUILD_IN_SCTP_STACK ? 65535 : 1024,\n\t\tMIS: USE_BUILD_IN_SCTP_STACK ? 65535 : 1024,\n\t\tmaxMessageSize: 262144,\n\t});\n\texpect(plainTransport2.sctpState).toBe('new');\n\texpect(plainTransport2.srtpParameters).toBeUndefined();\n\n\tconst dump1 = await plainTransport2.dump();\n\n\texpect(dump1.id).toBe(plainTransport2.id);\n\texpect(dump1.producerIds).toEqual([]);\n\texpect(dump1.consumerIds).toEqual([]);\n\texpect(dump1.tuple).toEqual(plainTransport2.tuple);\n\texpect(dump1.rtcpTuple).toEqual(plainTransport2.rtcpTuple);\n\texpect(dump1.sctpParameters).toEqual(plainTransport2.sctpParameters);\n\texpect(dump1.sctpState).toBe('new');\n\texpect(dump1.recvRtpHeaderExtensions).toBeDefined();\n\texpect(typeof dump1.rtpListener).toBe('object');\n\n\tplainTransport2.close();\n\texpect(plainTransport2.closed).toBe(true);\n\n\tconst anotherTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t});\n\n\texpect(typeof anotherTransport).toBe('object');\n\n\tconst rtpPort = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst rtcpPort = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst transport2 = await ctx.router!.createPlainTransport({\n\t\tlistenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtpPort },\n\t\trtcpListenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtcpPort },\n\t});\n\n\texpect(typeof transport2.id).toBe('string');\n\texpect(transport2.closed).toBe(false);\n\texpect(transport2.appData).toEqual({});\n\texpect(typeof transport2.tuple).toBe('object');\n\t// @deprecated Use tuple.localAddress instead.\n\texpect(transport2.tuple.localIp).toBe('127.0.0.1');\n\texpect(transport2.tuple.localAddress).toBe('127.0.0.1');\n\texpect(transport2.tuple.localPort).toBe(rtpPort);\n\texpect(transport2.tuple.protocol).toBe('udp');\n\texpect(typeof transport2.rtcpTuple).toBe('object');\n\t// @deprecated Use tuple.localAddress instead.\n\texpect(transport2.rtcpTuple?.localIp).toBe('127.0.0.1');\n\texpect(transport2.rtcpTuple?.localAddress).toBe('127.0.0.1');\n\texpect(transport2.rtcpTuple?.localPort).toBe(rtcpPort);\n\texpect(transport2.rtcpTuple?.protocol).toBe('udp');\n\texpect(transport2.sctpParameters).toBeUndefined();\n\texpect(transport2.sctpState).toBeUndefined();\n\n\tconst dump2 = await transport2.dump();\n\n\texpect(dump2.id).toBe(transport2.id);\n\texpect(dump2.tuple).toEqual(transport2.tuple);\n\texpect(dump2.rtcpTuple).toEqual(transport2.rtcpTuple);\n\texpect(dump2.sctpState).toBeUndefined();\n}, 2000);\n\ntest('router.createPlainTransport() with wrong arguments rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(ctx.router!.createPlainTransport({})).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createPlainTransport({\n\t\t\tlistenInfo: {\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tportRange: { min: 4000, max: 3000 },\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createPlainTransport({ listenIp: '123' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createPlainTransport({ listenIp: ['127.0.0.1'] })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createPlainTransport({\n\t\t\tlistenInfo: { protocol: 'tcp', ip: '127.0.0.1' },\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createPlainTransport({\n\t\t\tlistenInfo: { protocol: 'udp', ip: '127.0.0.1' },\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tappData: 'NOT-AN-OBJECT',\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('router.createPlainTransport() with enableSrtp succeeds', async () => {\n\t// Use default cryptoSuite: 'AES_CM_128_HMAC_SHA1_80'.\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t\tenableSrtp: true,\n\t});\n\n\texpect(typeof plainTransport.id).toBe('string');\n\texpect(typeof plainTransport.srtpParameters).toBe('object');\n\texpect(plainTransport.srtpParameters?.cryptoSuite).toBe(\n\t\t'AES_CM_128_HMAC_SHA1_80'\n\t);\n\texpect(plainTransport.srtpParameters?.keyBase64.length).toBe(40);\n\n\t// Missing srtpParameters.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: 1,\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Missing srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: {\n\t\t\t\tkeyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Missing srtpParameters.keyBase64.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AES_CM_128_HMAC_SHA1_80',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tcryptoSuite: 'FOO',\n\t\t\t\tkeyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.cryptoSuite.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tcryptoSuite: 123,\n\t\t\t\tkeyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid srtpParameters.keyBase64.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AES_CM_128_HMAC_SHA1_80',\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tkeyBase64: [],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Valid srtpParameters. And let's update the crypto suite.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AEAD_AES_256_GCM',\n\t\t\t\tkeyBase64:\n\t\t\t\t\t'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=',\n\t\t\t},\n\t\t})\n\t).resolves.toBeUndefined();\n\n\texpect(plainTransport.srtpParameters?.cryptoSuite).toBe('AEAD_AES_256_GCM');\n\texpect(plainTransport.srtpParameters?.keyBase64.length).toBe(60);\n}, 2000);\n\ntest('router.createPlainTransport() with non bindable IP rejects with Error', async () => {\n\tawait expect(\n\t\tctx.router!.createPlainTransport({ listenIp: '8.8.8.8' })\n\t).rejects.toThrow(Error);\n}, 2000);\n\nif (!IS_WINDOWS) {\n\ttest('two transports binding to the same IP:port with udpReusePort flag succeed', async () => {\n\t\tconst multicastIp = '224.0.0.1';\n\t\tconst port = await pickPort({\n\t\t\ttype: 'udp',\n\t\t\tip: multicastIp,\n\t\t\treserveTimeout: 0,\n\t\t});\n\n\t\tawait expect(\n\t\t\tctx.router!.createPlainTransport({\n\t\t\t\tlistenInfo: {\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: multicastIp,\n\t\t\t\t\tport: port,\n\t\t\t\t\t// NOTE: ipv6Only flag will be ignored since ip is IPv4.\n\t\t\t\t\tflags: { udpReusePort: true, ipv6Only: true },\n\t\t\t\t},\n\t\t\t})\n\t\t).resolves.toBeDefined();\n\n\t\tawait expect(\n\t\t\tctx.router!.createPlainTransport({\n\t\t\t\tlistenInfo: {\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: multicastIp,\n\t\t\t\t\tport: port,\n\t\t\t\t\tflags: { udpReusePort: true },\n\t\t\t\t},\n\t\t\t})\n\t\t).resolves.toBeDefined();\n\t}, 2000);\n\n\ttest('two transports binding to the same IP:port without udpReusePort flag fail', async () => {\n\t\tconst multicastIp = '224.0.0.1';\n\t\tconst port = await pickPort({\n\t\t\ttype: 'udp',\n\t\t\tip: multicastIp,\n\t\t\treserveTimeout: 0,\n\t\t});\n\n\t\tawait expect(\n\t\t\tctx.router!.createPlainTransport({\n\t\t\t\tlistenInfo: {\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: multicastIp,\n\t\t\t\t\tport: port,\n\t\t\t\t\tflags: { udpReusePort: false },\n\t\t\t\t},\n\t\t\t})\n\t\t).resolves.toBeDefined();\n\n\t\tawait expect(\n\t\t\tctx.router!.createPlainTransport({\n\t\t\t\tlistenInfo: {\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: multicastIp,\n\t\t\t\t\tport: port,\n\t\t\t\t\tflags: { udpReusePort: false },\n\t\t\t\t},\n\t\t\t})\n\t\t).rejects.toThrow();\n\t}, 2000);\n}\n\ntest('plainTransport.getStats() succeeds', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t});\n\n\tconst stats = await plainTransport.getStats();\n\n\texpect(Array.isArray(stats)).toBe(true);\n\texpect(stats.length).toBe(1);\n\texpect(stats[0]!.type).toBe('plain-rtp-transport');\n\texpect(stats[0]!.transportId).toBe(plainTransport.id);\n\texpect(typeof stats[0]!.timestamp).toBe('number');\n\texpect(stats[0]!.bytesReceived).toBe(0);\n\texpect(stats[0]!.recvBitrate).toBe(0);\n\texpect(stats[0]!.bytesSent).toBe(0);\n\texpect(stats[0]!.sendBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesReceived).toBe(0);\n\texpect(stats[0]!.rtpRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesSent).toBe(0);\n\texpect(stats[0]!.rtpSendBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesReceived).toBe(0);\n\texpect(stats[0]!.rtxRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesSent).toBe(0);\n\texpect(stats[0]!.rtxSendBitrate).toBe(0);\n\texpect(stats[0]!.probationBytesSent).toBe(0);\n\texpect(stats[0]!.probationSendBitrate).toBe(0);\n\texpect(typeof stats[0]!.tuple).toBe('object');\n\t// @deprecated Use tuple.localAddress instead.\n\texpect(stats[0]!.tuple.localIp).toBe('127.0.0.1');\n\texpect(stats[0]!.tuple.localAddress).toBe('127.0.0.1');\n\texpect(typeof stats[0]!.tuple.localPort).toBe('number');\n\texpect(stats[0]!.tuple.protocol).toBe('udp');\n\texpect(stats[0]!.rtcpTuple).toBeUndefined();\n}, 2000);\n\ntest('plainTransport.connect() succeeds', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t\trtcpMux: false,\n\t});\n\n\tawait expect(\n\t\tplainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 })\n\t).resolves.toBeUndefined();\n\n\t// Must fail if connected.\n\tawait expect(\n\t\tplainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 })\n\t).rejects.toThrow(Error);\n\n\texpect(plainTransport.tuple.remoteIp).toBe('1.2.3.4');\n\texpect(plainTransport.tuple.remotePort).toBe(1234);\n\texpect(plainTransport.tuple.protocol).toBe('udp');\n\texpect(plainTransport.rtcpTuple?.remoteIp).toBe('1.2.3.4');\n\texpect(plainTransport.rtcpTuple?.remotePort).toBe(1235);\n\texpect(plainTransport.rtcpTuple?.protocol).toBe('udp');\n}, 2000);\n\ntest('plainTransport.connect() with wrong arguments rejects with TypeError', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t\trtcpMux: false,\n\t});\n\n\t// No SRTP enabled so passing srtpParameters must fail.\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.2',\n\t\t\tport: 9998,\n\t\t\trtcpPort: 9999,\n\t\t\tsrtpParameters: {\n\t\t\t\tcryptoSuite: 'AES_CM_128_HMAC_SHA1_80',\n\t\t\t\tkeyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv',\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(plainTransport.connect({})).rejects.toThrow(TypeError);\n\n\tawait expect(plainTransport.connect({ ip: '::::1234' })).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\t// Must fail because transport has rtcpMux: false so rtcpPort must be given\n\t// in connect().\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.1',\n\t\t\tport: 1234,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t__rtcpPort: 1235,\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tplainTransport.connect({\n\t\t\tip: '127.0.0.1',\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t__port: 'chicken',\n\t\t\trtcpPort: 1235,\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('PlainTransport methods reject if closed', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\tplainTransport.observer.once('close', onObserverClose);\n\tplainTransport.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(plainTransport.closed).toBe(true);\n\n\tawait expect(plainTransport.dump()).rejects.toThrow(Error);\n\n\tawait expect(plainTransport.getStats()).rejects.toThrow(Error);\n\n\tawait expect(plainTransport.connect({})).rejects.toThrow(Error);\n}, 2000);\n\ntest('router.createPlainTransport() with fixed port succeeds', async () => {\n\tconst port = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenInfo: { protocol: 'udp', ip: '127.0.0.1', port },\n\t});\n\n\texpect(plainTransport.tuple.localPort).toEqual(port);\n}, 2000);\n\ntest('PlainTransport emits \"routerclose\" if Router is closed', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\tplainTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<PlainTransportEvents>(\n\t\tplainTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.router!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(plainTransport.closed).toBe(true);\n}, 2000);\n\ntest('PlainTransport emits \"routerclose\" if Worker is closed', async () => {\n\tconst plainTransport = await ctx.router!.createPlainTransport({\n\t\tlistenIp: '127.0.0.1',\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\tplainTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<PlainTransportEvents>(\n\t\tplainTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(plainTransport.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-Producer.ts",
    "content": "import * as flatbuffers from 'flatbuffers';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, ProducerEvents } from '../types';\nimport type { ProducerImpl } from '../Producer';\nimport { UnsupportedError } from '../errors';\nimport * as utils from '../utils';\nimport {\n\tNotification,\n\tBody as NotificationBody,\n\tEvent,\n} from '../fbs/notification';\nimport * as FbsProducer from '../fbs/producer';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\taudioProducerOptions: mediasoup.types.ProducerOptions;\n\tvideoProducerOptions: mediasoup.types.ProducerOptions;\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\twebRtcTransport1?: mediasoup.types.WebRtcTransport;\n\twebRtcTransport2?: mediasoup.types.WebRtcTransport;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tfoo: '111',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t\trtcpFeedback: [], // Will be ignored.\n\t\t},\n\t]),\n\taudioProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tuseinbandfec: 1,\n\t\t\t\t\t\tusedtx: 1,\n\t\t\t\t\t\tfoo: 222.222,\n\t\t\t\t\t\tbar: '333',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tid: 12,\n\t\t\t\t},\n\t\t\t],\n\t\t\t// Missing encodings on purpose.\n\t\t\trtcp: {\n\t\t\t\tcname: 'audio-1',\n\t\t\t},\n\t\t},\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n\tvideoProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'video',\n\t\trtpParameters: {\n\t\t\tmid: 'VIDEO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t},\n\t\t\t\t\trtcpFeedback: [\n\t\t\t\t\t\t{ type: 'nack' },\n\t\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t\t{ type: 'goog-remb' },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\tpayloadType: 113,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: { apt: 112 },\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\t\tid: 13,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [\n\t\t\t\t{ ssrc: 22222222, rtx: { ssrc: 22222223 }, scalabilityMode: 'L1T3' },\n\t\t\t\t{ ssrc: 22222224, rtx: { ssrc: 22222225 } },\n\t\t\t\t{ ssrc: 22222226, rtx: { ssrc: 22222227 } },\n\t\t\t\t{ ssrc: 22222228, rtx: { ssrc: 22222229 } },\n\t\t\t],\n\t\t\trtcp: {\n\t\t\t\tcname: 'video-1',\n\t\t\t},\n\t\t},\n\t\tappData: { foo: 1, bar: '2' },\n\t}),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n\tctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n\tctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t});\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('webRtcTransport1.produce() succeeds', async () => {\n\tconst onObserverNewProducer = jest.fn();\n\n\tctx.webRtcTransport1!.observer.once('newproducer', onObserverNewProducer);\n\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\texpect(onObserverNewProducer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewProducer).toHaveBeenCalledWith(audioProducer);\n\texpect(typeof audioProducer.id).toBe('string');\n\texpect(audioProducer.closed).toBe(false);\n\texpect(audioProducer.kind).toBe('audio');\n\texpect(typeof audioProducer.rtpParameters).toBe('object');\n\texpect(audioProducer.type).toBe('simple');\n\t// Private API.\n\texpect(typeof audioProducer.consumableRtpParameters).toBe('object');\n\texpect(audioProducer.paused).toBe(false);\n\texpect(audioProducer.score).toEqual([]);\n\texpect(audioProducer.appData).toEqual({ foo: 1, bar: '2' });\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\tmapProducerIdConsumerIds: [{ key: audioProducer.id, values: [] }],\n\t\tmapConsumerIdProducerId: [],\n\t});\n\n\tawait expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport1!.id,\n\t\tproducerIds: [audioProducer.id],\n\t\tconsumerIds: [],\n\t});\n}, 2000);\n\ntest('webRtcTransport2.produce() succeeds', async () => {\n\tconst onObserverNewProducer = jest.fn();\n\n\tctx.webRtcTransport2!.observer.once('newproducer', onObserverNewProducer);\n\n\tconst videoProducer = await ctx.webRtcTransport2!.produce(\n\t\tctx.videoProducerOptions\n\t);\n\n\texpect(onObserverNewProducer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewProducer).toHaveBeenCalledWith(videoProducer);\n\texpect(typeof videoProducer.id).toBe('string');\n\texpect(videoProducer.closed).toBe(false);\n\texpect(videoProducer.kind).toBe('video');\n\texpect(typeof videoProducer.rtpParameters).toBe('object');\n\texpect(videoProducer.type).toBe('simulcast');\n\t// Private API.\n\texpect(typeof videoProducer.consumableRtpParameters).toBe('object');\n\texpect(videoProducer.paused).toBe(false);\n\texpect(videoProducer.score).toEqual([]);\n\texpect(videoProducer.appData).toEqual({ foo: 1, bar: '2' });\n\n\tconst dump = await ctx.router!.dump();\n\n\texpect(dump.mapProducerIdConsumerIds).toEqual(\n\t\texpect.arrayContaining([{ key: videoProducer.id, values: [] }])\n\t);\n\n\texpect(dump.mapConsumerIdProducerId.length).toBe(0);\n\n\tawait expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport2!.id,\n\t\tproducerIds: [videoProducer.id],\n\t\tconsumerIds: [],\n\t});\n}, 2000);\n\ntest('webRtcTransport1.produce() without header extensions and rtcp succeeds', async () => {\n\tconst onObserverNewProducer = jest.fn();\n\n\tctx.webRtcTransport1!.observer.once('newproducer', onObserverNewProducer);\n\n\tconst audioProducer = await ctx.webRtcTransport1!.produce({\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO2',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tuseinbandfec: 1,\n\t\t\t\t\t\tusedtx: 1,\n\t\t\t\t\t\tfoo: 222.222,\n\t\t\t\t\t\tbar: '333',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tappData: { foo: 1, bar: '2' },\n\t});\n\n\texpect(onObserverNewProducer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewProducer).toHaveBeenCalledWith(audioProducer!);\n\texpect(typeof audioProducer!.id).toBe('string');\n\texpect(audioProducer!.closed).toBe(false);\n\texpect(audioProducer!.kind).toBe('audio');\n\texpect(typeof audioProducer!.rtpParameters).toBe('object');\n\texpect(audioProducer!.type).toBe('simple');\n\t// Private API.\n\texpect(typeof audioProducer!.consumableRtpParameters).toBe('object');\n\texpect(audioProducer!.paused).toBe(false);\n\texpect(audioProducer!.score).toEqual([]);\n\texpect(audioProducer!.appData).toEqual({ foo: 1, bar: '2' });\n\n\taudioProducer.close();\n}, 2000);\n\ntest('webRtcTransport1.produce() with wrong arguments rejects with TypeError', async () => {\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tkind: 'chicken',\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\trtpParameters: {},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'audio',\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\trtpParameters: {},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Invalid ssrc.\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [],\n\t\t\t\theaderExtensions: [],\n\t\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\t\tencodings: [{ ssrc: '1111' }],\n\t\t\t\trtcp: { cname: 'qwerty' },\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Missing or empty rtpParameters.encodings.\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'video',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\t\tpayloadType: 113,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: { apt: 112 },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\theaderExtensions: [],\n\t\t\t\tencodings: [],\n\t\t\t\trtcp: { cname: 'qwerty' },\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\t// Wrong apt in RTX codec.\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\t\tpayloadType: 113,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: { apt: 111 },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\theaderExtensions: [],\n\t\t\t\tencodings: [{ ssrc: 6666, rtx: { ssrc: 6667 } }],\n\t\t\t\trtcp: {\n\t\t\t\t\tcname: 'video-1',\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('webRtcTransport1.produce() with unsupported codecs rejects with UnsupportedError', async () => {\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'audio/ISAC',\n\t\t\t\t\t\tpayloadType: 108,\n\t\t\t\t\t\tclockRate: 32000,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\theaderExtensions: [],\n\t\t\t\tencodings: [{ ssrc: 1111 }],\n\t\t\t\trtcp: { cname: 'audio' },\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n\n\t// Invalid H264 profile-level-id.\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'video',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t\t'profile-level-id': 'CHICKEN',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\t\t\tpayloadType: 113,\n\t\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\t\tparameters: { apt: 112 },\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\theaderExtensions: [],\n\t\t\t\tencodings: [{ ssrc: 6666, rtx: { ssrc: 6667 } }],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n}, 2000);\n\ntest('transport.produce() with already used MID or SSRC rejects with Error', async () => {\n\tconst audioProducerOptions: mediasoup.types.ProducerOptions = {\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 2,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [{ ssrc: 33333333 }],\n\t\t},\n\t};\n\n\tconst videoProducerOptions: mediasoup.types.ProducerOptions = {\n\t\tkind: 'video',\n\t\trtpParameters: {\n\t\t\tmid: 'VIDEO2',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'video/h264',\n\t\t\t\t\tpayloadType: 112,\n\t\t\t\t\tclockRate: 90000,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t],\n\t\t\tencodings: [{ ssrc: 22222222 }],\n\t\t\trtcp: {\n\t\t\t\tcname: 'video-1',\n\t\t\t},\n\t\t},\n\t};\n\n\tawait ctx.webRtcTransport1!.produce(audioProducerOptions);\n\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce(audioProducerOptions)\n\t).rejects.toThrow(Error);\n\n\tawait ctx.webRtcTransport2!.produce(videoProducerOptions);\n\n\tawait expect(\n\t\tctx.webRtcTransport2!.produce(videoProducerOptions)\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('transport.produce() with no MID and with single encoding without RID or SSRC rejects with Error', async () => {\n\tawait expect(\n\t\tctx.webRtcTransport1!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\t\t\tpayloadType: 111,\n\t\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\t\tchannels: 2,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tencodings: [{}],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('producer.dump() succeeds', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst dump1 = await audioProducer.dump();\n\n\texpect(dump1.id).toBe(audioProducer.id);\n\texpect(dump1.kind).toBe(audioProducer.kind);\n\texpect(typeof dump1.rtpParameters).toBe('object');\n\texpect(Array.isArray(dump1.rtpParameters.codecs)).toBe(true);\n\texpect(dump1.rtpParameters.codecs.length).toBe(1);\n\texpect(dump1.rtpParameters.codecs[0]!.mimeType).toBe('audio/opus');\n\texpect(dump1.rtpParameters.codecs[0]!.payloadType).toBe(0);\n\texpect(dump1.rtpParameters.codecs[0]!.clockRate).toBe(48000);\n\texpect(dump1.rtpParameters.codecs[0]!.channels).toBe(2);\n\texpect(dump1.rtpParameters.codecs[0]!.parameters).toEqual({\n\t\tuseinbandfec: 1,\n\t\tusedtx: 1,\n\t\tfoo: 222.222,\n\t\tbar: '333',\n\t});\n\texpect(dump1.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([]);\n\texpect(Array.isArray(dump1.rtpParameters.headerExtensions)).toBe(true);\n\texpect(dump1.rtpParameters.headerExtensions!.length).toBe(2);\n\texpect(dump1.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tid: 10,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\tid: 12,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t]);\n\texpect(Array.isArray(dump1.rtpParameters.encodings)).toBe(true);\n\texpect(dump1.rtpParameters.encodings!.length).toBe(1);\n\texpect(dump1.rtpParameters.encodings![0]).toEqual(\n\t\texpect.objectContaining({\n\t\t\tcodecPayloadType: 0,\n\t\t})\n\t);\n\texpect(dump1.type).toBe('simple');\n\n\tconst videoProducer = await ctx.webRtcTransport2!.produce(\n\t\tctx.videoProducerOptions\n\t);\n\n\tconst dump2 = await videoProducer.dump();\n\n\texpect(dump2.id).toBe(videoProducer.id);\n\texpect(dump2.kind).toBe(videoProducer.kind);\n\texpect(typeof dump2.rtpParameters).toBe('object');\n\texpect(Array.isArray(dump2.rtpParameters.codecs)).toBe(true);\n\texpect(dump2.rtpParameters.codecs.length).toBe(2);\n\texpect(dump2.rtpParameters.codecs[0]!.mimeType).toBe('video/H264');\n\texpect(dump2.rtpParameters.codecs[0]!.payloadType).toBe(112);\n\texpect(dump2.rtpParameters.codecs[0]!.clockRate).toBe(90000);\n\texpect(dump2.rtpParameters.codecs[0]!.channels).toBeUndefined();\n\texpect(dump2.rtpParameters.codecs[0]!.parameters).toEqual({\n\t\t'packetization-mode': 1,\n\t\t'profile-level-id': '4d0032',\n\t});\n\texpect(dump2.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([\n\t\t{ type: 'nack' },\n\t\t{ type: 'nack', parameter: 'pli' },\n\t\t{ type: 'goog-remb' },\n\t]);\n\texpect(dump2.rtpParameters.codecs[1]!.mimeType).toBe('video/rtx');\n\texpect(dump2.rtpParameters.codecs[1]!.payloadType).toBe(113);\n\texpect(dump2.rtpParameters.codecs[1]!.clockRate).toBe(90000);\n\texpect(dump2.rtpParameters.codecs[1]!.channels).toBeUndefined();\n\texpect(dump2.rtpParameters.codecs[1]!.parameters).toEqual({ apt: 112 });\n\texpect(dump2.rtpParameters.codecs[1]!.rtcpFeedback).toEqual([]);\n\texpect(Array.isArray(dump2.rtpParameters.headerExtensions)).toBe(true);\n\texpect(dump2.rtpParameters.headerExtensions!.length).toBe(2);\n\texpect(dump2.rtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tid: 10,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 13,\n\t\t\tparameters: {},\n\t\t\tencrypt: false,\n\t\t},\n\t]);\n\texpect(Array.isArray(dump2.rtpParameters.encodings)).toBe(true);\n\texpect(dump2.rtpParameters.encodings!.length).toBe(4);\n\texpect(dump2.rtpParameters.encodings).toMatchObject([\n\t\t{\n\t\t\tcodecPayloadType: 112,\n\t\t\tssrc: 22222222,\n\t\t\trtx: { ssrc: 22222223 },\n\t\t\tscalabilityMode: 'L1T3',\n\t\t},\n\t\t{ codecPayloadType: 112, ssrc: 22222224, rtx: { ssrc: 22222225 } },\n\t\t{ codecPayloadType: 112, ssrc: 22222226, rtx: { ssrc: 22222227 } },\n\t\t{ codecPayloadType: 112, ssrc: 22222228, rtx: { ssrc: 22222229 } },\n\t]);\n\texpect(dump2.type).toBe('simulcast');\n}, 2000);\n\ntest('producer.getStats() succeeds', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst videoProducer = await ctx.webRtcTransport2!.produce(\n\t\tctx.videoProducerOptions\n\t);\n\n\tawait expect(audioProducer.getStats()).resolves.toEqual([]);\n\n\tawait expect(videoProducer.getStats()).resolves.toEqual([]);\n}, 2000);\n\ntest('producer.pause() and resume() succeed', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst onObserverPause = jest.fn();\n\tconst onObserverResume = jest.fn();\n\n\taudioProducer.observer.on('pause', onObserverPause);\n\taudioProducer.observer.on('resume', onObserverResume);\n\n\tawait audioProducer.pause();\n\texpect(audioProducer.paused).toBe(true);\n\n\tawait expect(audioProducer.dump()).resolves.toMatchObject({ paused: true });\n\n\tawait audioProducer.resume();\n\texpect(audioProducer.paused).toBe(false);\n\n\tawait expect(audioProducer.dump()).resolves.toMatchObject({ paused: false });\n\n\t// Even if we don't await for pause()/resume() completion, the observer must\n\t// fire 'pause' and 'resume' events if state was the opposite.\n\tvoid audioProducer.pause();\n\tvoid audioProducer.resume();\n\tvoid audioProducer.pause();\n\tvoid audioProducer.pause();\n\tvoid audioProducer.pause();\n\tawait audioProducer.resume();\n\n\texpect(onObserverPause).toHaveBeenCalledTimes(3);\n\texpect(onObserverResume).toHaveBeenCalledTimes(3);\n}, 2000);\n\ntest('producer.pause() and resume() emit events', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst promises = [];\n\tconst events: string[] = [];\n\n\taudioProducer.observer.once('resume', () => {\n\t\tevents.push('resume');\n\t});\n\n\taudioProducer.observer.once('pause', () => {\n\t\tevents.push('pause');\n\t});\n\n\tpromises.push(audioProducer.pause());\n\tpromises.push(audioProducer.resume());\n\n\tawait Promise.all(promises);\n\n\texpect(events).toEqual(['pause', 'resume']);\n\texpect(audioProducer.paused).toBe(false);\n}, 2000);\n\ntest('producer.enableTraceEvent() succeed', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tawait audioProducer.enableTraceEvent(['rtp', 'pli']);\n\n\tconst dump1 = await audioProducer.dump();\n\n\texpect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli']));\n\n\tawait audioProducer.enableTraceEvent();\n\n\tconst dump2 = await audioProducer.dump();\n\n\texpect(dump2.traceEventTypes).toEqual(expect.arrayContaining([]));\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait audioProducer.enableTraceEvent(['nack', 'FOO', 'fir']);\n\n\tconst dump3 = await audioProducer.dump();\n\n\texpect(dump3.traceEventTypes).toEqual(\n\t\texpect.arrayContaining(['nack', 'fir'])\n\t);\n\n\tawait audioProducer.enableTraceEvent();\n\n\tconst dump4 = await audioProducer.dump();\n\n\texpect(dump4.traceEventTypes).toEqual(expect.arrayContaining([]));\n}, 2000);\n\ntest('producer.enableTraceEvent() with wrong arguments rejects with TypeError', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(audioProducer.enableTraceEvent(123)).rejects.toThrow(TypeError);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(audioProducer.enableTraceEvent('rtp')).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\taudioProducer.enableTraceEvent(['fir', 123.123])\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('Producer emits \"score\"', async () => {\n\tconst videoProducer = await ctx.webRtcTransport2!.produce(\n\t\tctx.videoProducerOptions\n\t);\n\n\t// API not exposed in the interface.\n\tconst channel = (videoProducer as ProducerImpl).channelForTesting;\n\tconst onScore = jest.fn();\n\n\tvideoProducer.on('score', onScore);\n\n\t// Simulate a 'score' notification coming through the channel.\n\tconst builder = new flatbuffers.Builder();\n\tconst producerScoreNotification = new FbsProducer.ScoreNotificationT([\n\t\tnew FbsProducer.ScoreT(\n\t\t\t/* encodingIdx */ 0,\n\t\t\t/* ssrc */ 11,\n\t\t\t/* rid */ undefined,\n\t\t\t/* score */ 10\n\t\t),\n\t\tnew FbsProducer.ScoreT(\n\t\t\t/* encodingIdx */ 1,\n\t\t\t/* ssrc */ 22,\n\t\t\t/* rid */ undefined,\n\t\t\t/* score */ 9\n\t\t),\n\t]);\n\tconst notificationOffset = Notification.createNotification(\n\t\tbuilder,\n\t\tbuilder.createString(videoProducer.id),\n\t\tEvent.PRODUCER_SCORE,\n\t\tNotificationBody.Producer_ScoreNotification,\n\t\tproducerScoreNotification.pack(builder)\n\t);\n\n\tbuilder.finish(notificationOffset);\n\n\tconst notification = Notification.getRootAsNotification(\n\t\tnew flatbuffers.ByteBuffer(builder.asUint8Array())\n\t);\n\n\tchannel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification);\n\tchannel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification);\n\tchannel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification);\n\n\texpect(onScore).toHaveBeenCalledTimes(3);\n\texpect(videoProducer.score).toEqual([\n\t\t{ ssrc: 11, rid: undefined, score: 10, encodingIdx: 0 },\n\t\t{ ssrc: 22, rid: undefined, score: 9, encodingIdx: 1 },\n\t]);\n}, 2000);\n\ntest('producer.close() succeeds', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst onObserverClose = jest.fn();\n\n\taudioProducer.observer.once('close', onObserverClose);\n\taudioProducer.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(audioProducer.closed).toBe(true);\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\tmapProducerIdConsumerIds: [],\n\t\tmapConsumerIdProducerId: [],\n\t});\n\n\tawait expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({\n\t\tid: ctx.webRtcTransport1!.id,\n\t\tproducerIds: [],\n\t\tconsumerIds: [],\n\t});\n}, 2000);\n\ntest('Producer methods reject if closed', async () => {\n\tconst audioProducer = await ctx.webRtcTransport1!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\taudioProducer.close();\n\n\tawait expect(audioProducer.dump()).rejects.toThrow(Error);\n\tawait expect(audioProducer.getStats()).rejects.toThrow(Error);\n\tawait expect(audioProducer.pause()).rejects.toThrow(Error);\n\tawait expect(audioProducer.resume()).rejects.toThrow(Error);\n}, 2000);\n\ntest('Producer emits \"transportclose\" if Transport is closed', async () => {\n\tconst videoProducer = await ctx.webRtcTransport2!.produce(\n\t\tctx.videoProducerOptions\n\t);\n\n\tconst onObserverClose = jest.fn();\n\n\tvideoProducer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<ProducerEvents>(videoProducer, 'transportclose');\n\n\tctx.webRtcTransport2!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(videoProducer.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-Router.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerImpl } from '../Worker';\nimport type { WorkerEvents, RouterEvents } from '../types';\nimport { InvalidStateError, UnsupportedError } from '../errors';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tunsupportedMediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tworker?: mediasoup.types.Worker;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t},\n\t\t\trtcpFeedback: [], // Will be ignored.\n\t\t},\n\t]),\n\tunsupportedMediaCodecs: utils.deepFreeze<\n\t\tmediasoup.types.RouterRtpCodecCapability[]\n\t>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/x-aiff',\n\t\t\tclockRate: 8000,\n\t\t\tchannels: 1,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/3gpp',\n\t\t\tclockRate: 90000,\n\t\t},\n\t]),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('worker.createRouter() succeeds', async () => {\n\tconst onObserverNewRouter = jest.fn();\n\n\tctx.worker!.observer.once('newrouter', onObserverNewRouter);\n\n\tconst router = await ctx.worker!.createRouter<{ foo: number; bar?: string }>({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t\tappData: { foo: 123 },\n\t});\n\n\texpect(onObserverNewRouter).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewRouter).toHaveBeenCalledWith(router);\n\texpect(typeof router.id).toBe('string');\n\texpect(router.closed).toBe(false);\n\texpect(typeof router.rtpCapabilities).toBe('object');\n\texpect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true);\n\t// 3 codecs + 2 RTX codecs.\n\texpect(router.rtpCapabilities.codecs?.length).toBe(5);\n\texpect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true);\n\texpect(router.appData).toEqual({ foo: 123 });\n\n\texpect(() => (router.appData = { foo: 222, bar: 'BBB' })).not.toThrow();\n\n\tawait expect(ctx.worker!.dump()).resolves.toMatchObject({\n\t\tpid: ctx.worker!.pid,\n\t\twebRtcServerIds: [],\n\t\trouterIds: [router.id],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [router.id],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tawait expect(router.dump()).resolves.toMatchObject({\n\t\tid: router.id,\n\t\ttransportIds: [],\n\t\trtpObserverIds: [],\n\t\tmapProducerIdConsumerIds: {},\n\t\tmapConsumerIdProducerId: {},\n\t\tmapProducerIdObserverIds: {},\n\t\tmapDataProducerIdDataConsumerIds: {},\n\t\tmapDataConsumerIdDataProducerId: {},\n\t});\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(1);\n\n\tctx.worker!.close();\n\n\texpect(router.closed).toBe(true);\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(0);\n}, 2000);\n\ntest('worker.createRouter() with invalid codecs rejects with UnsupportedError', async () => {\n\tawait expect(\n\t\tctx.worker!.createRouter({ mediaCodecs: ctx.unsupportedMediaCodecs })\n\t).rejects.toThrow(UnsupportedError);\n}, 2000);\n\ntest('worker.createRouter() with wrong arguments rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(ctx.worker!.createRouter({ mediaCodecs: {} })).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.worker!.createRouter({ appData: 'NOT-AN-OBJECT' })\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('worker.createRouter() rejects with InvalidStateError if Worker is closed', async () => {\n\tctx.worker!.close();\n\n\tawait expect(\n\t\tctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs })\n\t).rejects.toThrow(InvalidStateError);\n}, 2000);\n\ntest('router.updateMediaCodecs() succeeds', async () => {\n\tconst router = await ctx.worker!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\texpect(typeof router.rtpCapabilities).toBe('object');\n\texpect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true);\n\t// 3 codecs + 2 RTX codecs.\n\texpect(router.rtpCapabilities.codecs?.length).toBe(5);\n\texpect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true);\n\n\trouter.updateMediaCodecs([]);\n\n\texpect(typeof router.rtpCapabilities).toBe('object');\n\texpect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true);\n\texpect(router.rtpCapabilities.codecs?.length).toBe(0);\n\texpect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true);\n}, 2000);\n\ntest('router.close() succeeds', async () => {\n\tconst router = await ctx.worker!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\trouter.observer.once('close', onObserverClose);\n\trouter.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(router.closed).toBe(true);\n}, 2000);\n\ntest('Router emits \"workerclose\" if Worker is closed', async () => {\n\tconst router = await ctx.worker!.createRouter({\n\t\tmediaCodecs: ctx.mediaCodecs,\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\trouter.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<RouterEvents>(router, 'workerclose');\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(router.closed).toBe(true);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-WebRtcServer.ts",
    "content": "import { pickPort } from 'pick-port';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerImpl } from '../Worker';\nimport type { WorkerEvents, WebRtcServerEvents } from '../types';\nimport type { WebRtcServerImpl } from '../WebRtcServer';\nimport type { RouterImpl } from '../Router';\nimport { InvalidStateError } from '../errors';\n\ntype TestContext = {\n\tworker?: mediasoup.types.Worker;\n};\n\nconst ctx: TestContext = {};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('worker.createWebRtcServer() succeeds', async () => {\n\tconst onObserverNewWebRtcServer = jest.fn();\n\n\tctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer);\n\n\tconst port1 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst port2 = await pickPort({\n\t\ttype: 'tcp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer<{ foo?: number }>({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tport: port1,\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: 'foo.bar.org',\n\t\t\t\tport: port2,\n\t\t\t},\n\t\t],\n\t\tappData: { foo: 123 },\n\t});\n\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);\n\texpect(typeof webRtcServer.id).toBe('string');\n\texpect(webRtcServer.closed).toBe(false);\n\texpect(webRtcServer.appData).toEqual({ foo: 123 });\n\n\tawait expect(ctx.worker!.dump()).resolves.toMatchObject({\n\t\tpid: ctx.worker!.pid,\n\t\twebRtcServerIds: [webRtcServer.id],\n\t\trouterIds: [],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [webRtcServer.id],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [],\n\t\tlocalIceUsernameFragments: [],\n\t\ttupleHashes: [],\n\t});\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1);\n\n\tctx.worker!.close();\n\n\texpect(webRtcServer.closed).toBe(true);\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0);\n}, 2000);\n\ntest('worker.createWebRtcServer() with portRange succeeds', async () => {\n\tconst onObserverNewWebRtcServer = jest.fn();\n\n\tctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer);\n\n\tconst port1 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst port2 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tportRange: { min: port1, max: port1 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '1.2.3.4',\n\t\t\t\tportRange: { min: port2, max: port2 },\n\t\t\t},\n\t\t],\n\t\tappData: { foo: 123 },\n\t});\n\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);\n\texpect(typeof webRtcServer.id).toBe('string');\n\texpect(webRtcServer.closed).toBe(false);\n\texpect(webRtcServer.appData).toEqual({ foo: 123 });\n\n\tawait expect(ctx.worker!.dump()).resolves.toMatchObject({\n\t\tpid: ctx.worker!.pid,\n\t\twebRtcServerIds: [webRtcServer.id],\n\t\trouterIds: [],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [webRtcServer.id],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [],\n\t\tlocalIceUsernameFragments: [],\n\t\ttupleHashes: [],\n\t});\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1);\n\n\tctx.worker!.close();\n\n\texpect(webRtcServer.closed).toBe(true);\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0);\n}, 2000);\n\ntest('worker.createWebRtcServer() without specifying port/portRange succeeds', async () => {\n\tconst onObserverNewWebRtcServer = jest.fn();\n\n\tctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer);\n\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '1.2.3.4',\n\t\t\t},\n\t\t],\n\t\tappData: { foo: 123 },\n\t});\n\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer);\n\texpect(typeof webRtcServer.id).toBe('string');\n\texpect(webRtcServer.closed).toBe(false);\n\texpect(webRtcServer.appData).toEqual({ foo: 123 });\n\n\tawait expect(ctx.worker!.dump()).resolves.toMatchObject({\n\t\tpid: ctx.worker!.pid,\n\t\twebRtcServerIds: [webRtcServer.id],\n\t\trouterIds: [],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [webRtcServer.id],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: expect.any(Number) }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: expect.any(Number) }],\n\t\twebRtcTransportIds: [],\n\t\tlocalIceUsernameFragments: [],\n\t\ttupleHashes: [],\n\t});\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1);\n\n\tctx.worker!.close();\n\n\texpect(webRtcServer.closed).toBe(true);\n\n\t// API not exposed in the interface.\n\texpect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0);\n}, 2000);\n\ntest('worker.createWebRtcServer() with wrong arguments rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(ctx.worker!.createWebRtcServer({})).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.worker!.createWebRtcServer({ listenInfos: 'NOT-AN-ARRAY' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.worker!.createWebRtcServer({ listenInfos: ['NOT-AN-OBJECT'] })\n\t).rejects.toThrow(Error);\n\n\t// Empty listenInfos so should fail.\n\tawait expect(\n\t\tctx.worker!.createWebRtcServer({ listenInfos: [] })\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('worker.createWebRtcServer() with unavailable listenInfos rejects with Error', async () => {\n\tconst worker2 = await mediasoup.createWorker();\n\tconst port1 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst port2 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\n\t// Using an unavailable listen IP.\n\tawait expect(\n\t\tctx.worker!.createWebRtcServer({\n\t\t\tlistenInfos: [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '127.0.0.1',\n\t\t\t\t\tport: port1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '1.2.3.4',\n\t\t\t\t\tport: port2,\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t).rejects.toThrow(Error);\n\n\t// Using the same UDP port in two listenInfos.\n\tawait expect(\n\t\tctx.worker!.createWebRtcServer({\n\t\t\tlistenInfos: [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '127.0.0.1',\n\t\t\t\t\tport: port1,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '127.0.0.1',\n\t\t\t\t\tannouncedAddress: '1.2.3.4',\n\t\t\t\t\tport: port1,\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t).rejects.toThrow(Error);\n\n\tawait ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tport: port1,\n\t\t\t},\n\t\t],\n\t});\n\n\t// Using the same UDP port in a second Worker.\n\tawait expect(\n\t\tworker2.createWebRtcServer({\n\t\t\tlistenInfos: [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '127.0.0.1',\n\t\t\t\t\tport: port1,\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t).rejects.toThrow(Error);\n\n\tworker2.close();\n}, 2000);\n\ntest('worker.createWebRtcServer() rejects with InvalidStateError if Worker is closed', async () => {\n\tctx.worker!.close();\n\n\tconst port = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\n\tawait expect(\n\t\tctx.worker!.createWebRtcServer({\n\t\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }],\n\t\t})\n\t).rejects.toThrow(InvalidStateError);\n}, 2000);\n\ntest('webRtcServer.close() succeeds', async () => {\n\tconst port = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }],\n\t});\n\tconst onObserverClose = jest.fn();\n\n\twebRtcServer.observer.once('close', onObserverClose);\n\twebRtcServer.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(webRtcServer.closed).toBe(true);\n}, 2000);\n\ntest('WebRtcServer emits \"workerclose\" if Worker is closed', async () => {\n\tconst port = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [{ protocol: 'tcp', ip: '127.0.0.1', port }],\n\t});\n\tconst onObserverClose = jest.fn();\n\n\twebRtcServer.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<WebRtcServerEvents>(webRtcServer, 'workerclose');\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(webRtcServer.closed).toBe(true);\n}, 2000);\n\ntest('router.createWebRtcTransport() with webRtcServer succeeds and transport is closed', async () => {\n\tconst port1 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst port2 = await pickPort({\n\t\ttype: 'tcp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', port: port1 },\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: 'media1.foo.org',\n\t\t\t\texposeInternalIp: true,\n\t\t\t\tport: port2,\n\t\t\t},\n\t\t],\n\t});\n\n\tconst onObserverWebRtcTransportHandled = jest.fn();\n\tconst onObserverWebRtcTransportUnhandled = jest.fn();\n\n\twebRtcServer.observer.once(\n\t\t'webrtctransporthandled',\n\t\tonObserverWebRtcTransportHandled\n\t);\n\twebRtcServer.observer.once(\n\t\t'webrtctransportunhandled',\n\t\tonObserverWebRtcTransportUnhandled\n\t);\n\n\tconst router = await ctx.worker!.createRouter();\n\n\tconst onObserverNewTransport = jest.fn();\n\n\trouter.observer.once('newtransport', onObserverNewTransport);\n\n\tconst transport = await router.createWebRtcTransport({\n\t\twebRtcServer,\n\t\t// Let's disable UDP so resulting ICE candidates should only contain TCP.\n\t\tenableUdp: false,\n\t\tappData: { foo: 'bar' },\n\t});\n\n\tawait expect(router.dump()).resolves.toMatchObject({\n\t\ttransportIds: [transport.id],\n\t});\n\n\texpect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1);\n\texpect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport);\n\texpect(onObserverNewTransport).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewTransport).toHaveBeenCalledWith(transport);\n\texpect(typeof transport.id).toBe('string');\n\texpect(transport.closed).toBe(false);\n\texpect(transport.appData).toEqual({ foo: 'bar' });\n\n\tconst iceCandidates = transport.iceCandidates;\n\n\texpect(iceCandidates.length).toBe(2);\n\n\texpect(iceCandidates[0]!.address).toBe('media1.foo.org');\n\texpect(iceCandidates[0]!.ip).toBe('media1.foo.org');\n\texpect(iceCandidates[0]!.port).toBe(port2);\n\texpect(iceCandidates[0]!.protocol).toBe('tcp');\n\texpect(iceCandidates[0]!.type).toBe('host');\n\texpect(iceCandidates[0]!.tcpType).toBe('passive');\n\texpect(iceCandidates[1]!.address).toBe('127.0.0.1');\n\texpect(iceCandidates[1]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[1]!.port).toBe(port2);\n\texpect(iceCandidates[1]!.protocol).toBe('tcp');\n\texpect(iceCandidates[1]!.type).toBe('host');\n\texpect(iceCandidates[1]!.tcpType).toBe('passive');\n\n\texpect(iceCandidates[0]!.priority).toBeGreaterThan(\n\t\ticeCandidates[1]!.priority\n\t);\n\n\texpect(transport.iceState).toBe('new');\n\texpect(transport.iceSelectedTuple).toBeUndefined();\n\n\t// API not exposed in the interface.\n\texpect(\n\t\t(webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size\n\t).toBe(1);\n\t// API not exposed in the interface.\n\texpect((router as RouterImpl).transportsForTesting.size).toBe(1);\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [transport.id],\n\t\tlocalIceUsernameFragments: [\n\t\t\t{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },\n\t\t],\n\t\ttupleHashes: [],\n\t});\n\n\ttransport.close();\n\n\texpect(transport.closed).toBe(true);\n\texpect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1);\n\texpect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport);\n\t// API not exposed in the interface.\n\texpect(\n\t\t(webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size\n\t).toBe(0);\n\t// API not exposed in the interface.\n\texpect((router as RouterImpl).transportsForTesting.size).toBe(0);\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [],\n\t\tlocalIceUsernameFragments: [],\n\t\ttupleHashes: [],\n\t});\n}, 2000);\n\ntest('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer is closed', async () => {\n\tconst port1 = await pickPort({\n\t\ttype: 'udp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst port2 = await pickPort({\n\t\ttype: 'tcp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst webRtcServer = await ctx.worker!.createWebRtcServer({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', port: port1 },\n\t\t\t{ protocol: 'tcp', ip: '127.0.0.1', port: port2 },\n\t\t],\n\t});\n\n\tconst onObserverWebRtcTransportHandled = jest.fn();\n\tconst onObserverWebRtcTransportUnhandled = jest.fn();\n\n\twebRtcServer.observer.once(\n\t\t'webrtctransporthandled',\n\t\tonObserverWebRtcTransportHandled\n\t);\n\twebRtcServer.observer.once(\n\t\t'webrtctransportunhandled',\n\t\tonObserverWebRtcTransportUnhandled\n\t);\n\n\tconst router = await ctx.worker!.createRouter();\n\tconst transport = await router.createWebRtcTransport({\n\t\twebRtcServer,\n\t\tappData: { foo: 'bar' },\n\t});\n\n\texpect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1);\n\texpect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport);\n\n\tawait expect(router.dump()).resolves.toMatchObject({\n\t\ttransportIds: [transport.id],\n\t});\n\n\texpect(typeof transport.id).toBe('string');\n\texpect(transport.closed).toBe(false);\n\texpect(transport.appData).toEqual({ foo: 'bar' });\n\n\tconst iceCandidates = transport.iceCandidates;\n\n\texpect(iceCandidates.length).toBe(2);\n\texpect(iceCandidates[0]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[0]!.port).toBe(port1);\n\texpect(iceCandidates[0]!.protocol).toBe('udp');\n\texpect(iceCandidates[0]!.type).toBe('host');\n\texpect(iceCandidates[0]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[1]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[1]!.port).toBe(port2);\n\texpect(iceCandidates[1]!.protocol).toBe('tcp');\n\texpect(iceCandidates[1]!.type).toBe('host');\n\texpect(iceCandidates[1]!.tcpType).toBe('passive');\n\n\texpect(transport.iceState).toBe('new');\n\texpect(transport.iceSelectedTuple).toBeUndefined();\n\n\t// API not exposed in the interface.\n\texpect(\n\t\t(webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size\n\t).toBe(1);\n\t// API not exposed in the interface.\n\texpect((router as RouterImpl).transportsForTesting.size).toBe(1);\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [transport.id],\n\t\tlocalIceUsernameFragments: [\n\t\t\t{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },\n\t\t],\n\t\ttupleHashes: [],\n\t});\n\n\t// Let's restart ICE in the transport so it should add a new entry in\n\t// localIceUsernameFragments in the WebRtcServer.\n\tawait transport.restartIce();\n\n\tawait expect(webRtcServer.dump()).resolves.toMatchObject({\n\t\tid: webRtcServer.id,\n\t\tudpSockets: [{ ip: '127.0.0.1', port: port1 }],\n\t\ttcpServers: [{ ip: '127.0.0.1', port: port2 }],\n\t\twebRtcTransportIds: [transport.id],\n\t\tlocalIceUsernameFragments: [\n\t\t\t{ /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id },\n\t\t\t{ /* localIceUsernameFragment: yyy, */ webRtcTransportId: transport.id },\n\t\t],\n\t\ttupleHashes: [],\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\twebRtcServer.observer.once('close', onObserverClose);\n\n\tconst onListenServerClose = jest.fn();\n\n\ttransport.once('listenserverclose', onListenServerClose);\n\n\twebRtcServer.close();\n\n\texpect(webRtcServer.closed).toBe(true);\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(onListenServerClose).toHaveBeenCalledTimes(1);\n\texpect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1);\n\texpect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport);\n\texpect(transport.closed).toBe(true);\n\texpect(transport.iceState).toBe('closed');\n\texpect(transport.iceSelectedTuple).toBe(undefined);\n\texpect(transport.dtlsState).toBe('closed');\n\texpect(transport.sctpState).toBe(undefined);\n\t// API not exposed in the interface.\n\texpect(\n\t\t(webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size\n\t).toBe(0);\n\t// API not exposed in the interface.\n\texpect((router as RouterImpl).transportsForTesting.size).toBe(0);\n\n\tawait expect(ctx.worker!.dump()).resolves.toMatchObject({\n\t\tpid: ctx.worker!.pid,\n\t\twebRtcServerIds: [],\n\t\trouterIds: [router.id],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [router.id],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tawait expect(router.dump()).resolves.toMatchObject({\n\t\tid: router.id,\n\t\ttransportIds: [],\n\t});\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-WebRtcTransport.ts",
    "content": "import { pickPort } from 'pick-port';\nimport * as flatbuffers from 'flatbuffers';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents, WebRtcTransportEvents } from '../types';\nimport type { WebRtcTransportImpl } from '../WebRtcTransport';\nimport type { TransportTuple } from '../TransportTypes';\nimport { serializeProtocol } from '../Transport';\nimport * as utils from '../utils';\nimport {\n\tNotification,\n\tBody as NotificationBody,\n\tEvent,\n} from '../fbs/notification';\nimport * as FbsTransport from '../fbs/transport';\nimport * as FbsWebRtcTransport from '../fbs/web-rtc-transport';\n\nconst USE_BUILD_IN_SCTP_STACK = false;\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RouterRtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t]),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker({\n\t\tuseBuiltInSctpStack: USE_BUILD_IN_SCTP_STACK,\n\t});\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('router.createWebRtcTransport() succeeds', async () => {\n\tconst onObserverNewTransport = jest.fn();\n\n\tctx.router!.observer.once('newtransport', onObserverNewTransport);\n\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\t// |exposeInternalIp| will generate an extra ICE candidate with |ip|\n\t\t\t\t// value.\n\t\t\t\texposeInternalIp: true,\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '0.0.0.0',\n\t\t\t\tannouncedAddress: 'foo1.bar.org',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '0.0.0.0',\n\t\t\t\tannouncedAddress: 'foo2.bar.org',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: undefined,\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t\t{\n\t\t\t\tprotocol: 'tcp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: undefined,\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t],\n\t\tenableTcp: true,\n\t\tpreferUdp: true,\n\t\tenableSctp: true,\n\t\tnumSctpStreams: { OS: 2048, MIS: 4096 },\n\t\tmaxSctpMessageSize: 1000000,\n\t\tappData: { foo: 'bar' },\n\t});\n\n\tawait expect(ctx.router!.dump()).resolves.toMatchObject({\n\t\ttransportIds: [webRtcTransport.id],\n\t});\n\n\texpect(onObserverNewTransport).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewTransport).toHaveBeenCalledWith(webRtcTransport);\n\texpect(typeof webRtcTransport.id).toBe('string');\n\texpect(webRtcTransport.closed).toBe(false);\n\texpect(webRtcTransport.type).toBe('webrtc');\n\texpect(webRtcTransport.appData).toEqual({ foo: 'bar' });\n\texpect(webRtcTransport.iceRole).toBe('controlled');\n\texpect(typeof webRtcTransport.iceParameters).toBe('object');\n\texpect(webRtcTransport.iceParameters.iceLite).toBe(true);\n\texpect(typeof webRtcTransport.iceParameters.usernameFragment).toBe('string');\n\texpect(typeof webRtcTransport.iceParameters.password).toBe('string');\n\texpect(webRtcTransport.sctpParameters).toMatchObject({\n\t\tport: 5000,\n\t\t// NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the\n\t\t// transport is ignored.\n\t\tOS: USE_BUILD_IN_SCTP_STACK ? 65535 : 2048,\n\t\tMIS: USE_BUILD_IN_SCTP_STACK ? 65535 : 4096,\n\t\tmaxMessageSize: 1000000,\n\t});\n\texpect(Array.isArray(webRtcTransport.iceCandidates)).toBe(true);\n\texpect(webRtcTransport.iceCandidates.length).toBe(7);\n\n\tconst iceCandidates = webRtcTransport.iceCandidates;\n\n\texpect(iceCandidates[0]!.ip).toBe('9.9.9.1');\n\texpect(iceCandidates[0]!.protocol).toBe('udp');\n\texpect(iceCandidates[0]!.type).toBe('host');\n\texpect(iceCandidates[0]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[1]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[1]!.protocol).toBe('udp');\n\texpect(iceCandidates[1]!.type).toBe('host');\n\texpect(iceCandidates[1]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[2]!.ip).toBe('9.9.9.1');\n\texpect(iceCandidates[2]!.protocol).toBe('tcp');\n\texpect(iceCandidates[2]!.type).toBe('host');\n\texpect(iceCandidates[2]!.tcpType).toBe('passive');\n\texpect(iceCandidates[3]!.ip).toBe('foo1.bar.org');\n\texpect(iceCandidates[3]!.protocol).toBe('udp');\n\texpect(iceCandidates[3]!.type).toBe('host');\n\texpect(iceCandidates[3]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[4]!.ip).toBe('foo2.bar.org');\n\texpect(iceCandidates[4]!.protocol).toBe('tcp');\n\texpect(iceCandidates[4]!.type).toBe('host');\n\texpect(iceCandidates[4]!.tcpType).toBe('passive');\n\texpect(iceCandidates[5]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[5]!.protocol).toBe('udp');\n\texpect(iceCandidates[5]!.type).toBe('host');\n\texpect(iceCandidates[5]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[6]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[6]!.protocol).toBe('tcp');\n\texpect(iceCandidates[6]!.type).toBe('host');\n\texpect(iceCandidates[6]!.tcpType).toBe('passive');\n\n\texpect(iceCandidates[0]!.priority).toBeGreaterThan(\n\t\ticeCandidates[1]!.priority\n\t);\n\texpect(iceCandidates[1]!.priority).toBeGreaterThan(\n\t\ticeCandidates[2]!.priority\n\t);\n\texpect(iceCandidates[2]!.priority).toBeGreaterThan(\n\t\ticeCandidates[3]!.priority\n\t);\n\texpect(iceCandidates[3]!.priority).toBeGreaterThan(\n\t\ticeCandidates[4]!.priority\n\t);\n\texpect(iceCandidates[4]!.priority).toBeGreaterThan(\n\t\ticeCandidates[5]!.priority\n\t);\n\texpect(iceCandidates[5]!.priority).toBeGreaterThan(\n\t\ticeCandidates[6]!.priority\n\t);\n\n\texpect(webRtcTransport.iceState).toBe('new');\n\texpect(webRtcTransport.iceSelectedTuple).toBeUndefined();\n\texpect(typeof webRtcTransport.dtlsParameters).toBe('object');\n\texpect(Array.isArray(webRtcTransport.dtlsParameters.fingerprints)).toBe(true);\n\texpect(webRtcTransport.dtlsParameters.role).toBe('auto');\n\texpect(webRtcTransport.dtlsState).toBe('new');\n\texpect(webRtcTransport.dtlsRemoteCert).toBeUndefined();\n\texpect(webRtcTransport.sctpState).toBe('new');\n\n\tconst dump = await webRtcTransport.dump();\n\n\texpect(dump.id).toBe(webRtcTransport.id);\n\texpect(dump.producerIds).toEqual([]);\n\texpect(dump.consumerIds).toEqual([]);\n\texpect(dump.iceRole).toBe(webRtcTransport.iceRole);\n\texpect(dump.iceParameters).toEqual(webRtcTransport.iceParameters);\n\texpect(dump.iceCandidates).toEqual(webRtcTransport.iceCandidates);\n\texpect(dump.iceState).toBe(webRtcTransport.iceState);\n\texpect(dump.iceSelectedTuple).toEqual(webRtcTransport.iceSelectedTuple);\n\texpect(dump.dtlsParameters).toEqual(webRtcTransport.dtlsParameters);\n\texpect(dump.dtlsState).toBe(webRtcTransport.dtlsState);\n\texpect(dump.sctpParameters).toEqual(webRtcTransport.sctpParameters);\n\texpect(dump.sctpState).toBe(webRtcTransport.sctpState);\n\texpect(dump.recvRtpHeaderExtensions).toBeDefined();\n\texpect(typeof dump.rtpListener).toBe('object');\n\n\twebRtcTransport.close();\n\n\texpect(webRtcTransport.closed).toBe(true);\n}, 2000);\n\ntest('router.createWebRtcTransport() with deprecated listenIps succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenIps: [{ ip: '127.0.0.1', announcedIp: undefined }],\n\t\tenableUdp: true,\n\t\tenableTcp: true,\n\t\tpreferUdp: false,\n\t\tinitialAvailableOutgoingBitrate: 1000000,\n\t});\n\n\texpect(Array.isArray(webRtcTransport.iceCandidates)).toBe(true);\n\texpect(webRtcTransport.iceCandidates.length).toBe(2);\n\n\tconst iceCandidates = webRtcTransport.iceCandidates;\n\n\texpect(iceCandidates[0]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[0]!.protocol).toBe('udp');\n\texpect(iceCandidates[0]!.type).toBe('host');\n\texpect(iceCandidates[0]!.tcpType).toBeUndefined();\n\texpect(iceCandidates[1]!.ip).toBe('127.0.0.1');\n\texpect(iceCandidates[1]!.protocol).toBe('tcp');\n\texpect(iceCandidates[1]!.type).toBe('host');\n\texpect(iceCandidates[1]!.tcpType).toBe('passive');\n\texpect(iceCandidates[0]!.priority).toBeGreaterThan(\n\t\ticeCandidates[1]!.priority\n\t);\n}, 2000);\n\ntest('router.createWebRtcTransport() with fixed port succeeds', async () => {\n\tconst port = await pickPort({\n\t\ttype: 'tcp',\n\t\tip: '127.0.0.1',\n\t\treserveTimeout: 0,\n\t});\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t// NOTE: udpReusePort flag will be ignored since protocol is TCP.\n\t\t\t{ protocol: 'tcp', ip: '127.0.0.1', port, flags: { udpReusePort: true } },\n\t\t],\n\t});\n\n\texpect(webRtcTransport.iceCandidates[0]!.port).toEqual(port);\n}, 2000);\n\ntest('router.createWebRtcTransport() with portRange succeeds', async () => {\n\tconst portRange = { min: 11111, max: 11112 };\n\n\tconst webRtcTransport1 = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }],\n\t});\n\n\tconst iceCandidate1 = webRtcTransport1.iceCandidates[0]!;\n\n\texpect(iceCandidate1.ip).toBe('127.0.0.1');\n\texpect(\n\t\ticeCandidate1.port >= portRange.min && iceCandidate1.port <= portRange.max\n\t).toBe(true);\n\texpect(iceCandidate1.protocol).toBe('udp');\n\n\tconst webRtcTransport2 = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }],\n\t});\n\n\tconst iceCandidate2 = webRtcTransport2.iceCandidates[0]!;\n\n\texpect(iceCandidate2.ip).toBe('127.0.0.1');\n\texpect(\n\t\ticeCandidate1.port >= portRange.min && iceCandidate1.port <= portRange.max\n\t).toBe(true);\n\texpect(iceCandidate2.protocol).toBe('udp');\n\n\t// No more available ports so it must fail.\n\tawait expect(\n\t\tctx.router!.createWebRtcTransport({\n\t\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }],\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('router.createWebRtcTransport() with wrong arguments rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(ctx.router!.createWebRtcTransport({})).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\tctx.router!.createWebRtcTransport({\n\t\t\tlistenInfos: [\n\t\t\t\t{\n\t\t\t\t\tprotocol: 'udp',\n\t\t\t\t\tip: '127.0.0.1',\n\t\t\t\t\tportRange: { min: 4000, max: 3000 },\n\t\t\t\t},\n\t\t\t],\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createWebRtcTransport({ listenIps: [123] })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createWebRtcTransport({ listenInfos: '127.0.0.1' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tctx.router!.createWebRtcTransport({ listenIps: '127.0.0.1' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createWebRtcTransport({\n\t\t\tlistenIps: ['127.0.0.1'],\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tappData: 'NOT-AN-OBJECT',\n\t\t})\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tctx.router!.createWebRtcTransport({\n\t\t\tlistenIps: ['127.0.0.1'],\n\t\t\tenableSctp: true,\n\t\t\t// @ts-expect-error --- Testing purposes.\n\t\t\tnumSctpStreams: 'foo',\n\t\t})\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('router.createWebRtcTransport() with non bindable IP rejects with Error', async () => {\n\tawait expect(\n\t\tctx.router!.createWebRtcTransport({\n\t\t\tlistenInfos: [\n\t\t\t\t{ protocol: 'udp', ip: '8.8.8.8', portRange: { min: 2000, max: 3000 } },\n\t\t\t],\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n\ntest('webRtcTransport.getStats() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t],\n\t});\n\n\tconst stats = await webRtcTransport.getStats();\n\n\texpect(Array.isArray(stats)).toBe(true);\n\texpect(stats.length).toBe(1);\n\texpect(stats[0]!.type).toBe('webrtc-transport');\n\texpect(stats[0]!.transportId).toBe(webRtcTransport.id);\n\texpect(typeof stats[0]!.timestamp).toBe('number');\n\texpect(stats[0]!.iceRole).toBe('controlled');\n\texpect(stats[0]!.iceState).toBe('new');\n\texpect(stats[0]!.dtlsState).toBe('new');\n\texpect(stats[0]!.sctpState).toBeUndefined();\n\texpect(stats[0]!.bytesReceived).toBe(0);\n\texpect(stats[0]!.recvBitrate).toBe(0);\n\texpect(stats[0]!.bytesSent).toBe(0);\n\texpect(stats[0]!.sendBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesReceived).toBe(0);\n\texpect(stats[0]!.rtpRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtpBytesSent).toBe(0);\n\texpect(stats[0]!.rtpSendBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesReceived).toBe(0);\n\texpect(stats[0]!.rtxRecvBitrate).toBe(0);\n\texpect(stats[0]!.rtxBytesSent).toBe(0);\n\texpect(stats[0]!.rtxSendBitrate).toBe(0);\n\texpect(stats[0]!.probationBytesSent).toBe(0);\n\texpect(stats[0]!.probationSendBitrate).toBe(0);\n\texpect(stats[0]!.iceSelectedTuple).toBeUndefined();\n\texpect(stats[0]!.maxIncomingBitrate).toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.connect() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t],\n\t});\n\n\tconst dtlsRemoteParameters: mediasoup.types.DtlsParameters = {\n\t\tfingerprints: [\n\t\t\t{\n\t\t\t\talgorithm: 'sha-256',\n\t\t\t\tvalue:\n\t\t\t\t\t'82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD',\n\t\t\t},\n\t\t],\n\t\trole: 'client',\n\t};\n\n\tawait expect(\n\t\twebRtcTransport.connect({\n\t\t\tdtlsParameters: dtlsRemoteParameters,\n\t\t})\n\t).resolves.toBeUndefined();\n\n\t// Must fail if connected.\n\tawait expect(\n\t\twebRtcTransport.connect({\n\t\t\tdtlsParameters: dtlsRemoteParameters,\n\t\t})\n\t).rejects.toThrow(Error);\n\n\texpect(webRtcTransport.dtlsParameters.role).toBe('server');\n}, 2000);\n\ntest('webRtcTransport.connect() with wrong arguments rejects with TypeError', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t],\n\t});\n\n\tlet dtlsRemoteParameters: mediasoup.types.DtlsParameters;\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(webRtcTransport.connect({})).rejects.toThrow(TypeError);\n\n\tdtlsRemoteParameters = {\n\t\tfingerprints: [\n\t\t\t{\n\t\t\t\t// @ts-expect-error --- Testing purposes..\n\t\t\t\talgorithm: 'sha-256000',\n\t\t\t\tvalue:\n\t\t\t\t\t'82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD',\n\t\t\t},\n\t\t],\n\t\trole: 'client',\n\t};\n\n\tawait expect(\n\t\twebRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })\n\t).rejects.toThrow(TypeError);\n\n\tdtlsRemoteParameters = {\n\t\tfingerprints: [\n\t\t\t{\n\t\t\t\talgorithm: 'sha-256',\n\t\t\t\tvalue:\n\t\t\t\t\t'82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD',\n\t\t\t},\n\t\t],\n\t\t// @ts-expect-error --- Testing purposes.\n\t\trole: 'chicken',\n\t};\n\n\tawait expect(\n\t\twebRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })\n\t).rejects.toThrow(TypeError);\n\n\tdtlsRemoteParameters = {\n\t\tfingerprints: [],\n\t\trole: 'client',\n\t};\n\n\tawait expect(\n\t\twebRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\twebRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters })\n\t).rejects.toThrow(TypeError);\n\n\texpect(webRtcTransport.dtlsParameters.role).toBe('auto');\n}, 2000);\n\ntest('webRtcTransport.setMaxIncomingBitrate() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{\n\t\t\t\tprotocol: 'udp',\n\t\t\t\tip: '127.0.0.1',\n\t\t\t\tannouncedAddress: '9.9.9.1',\n\t\t\t\tportRange: { min: 2000, max: 3000 },\n\t\t\t},\n\t\t],\n\t});\n\n\tawait expect(\n\t\twebRtcTransport.setMaxIncomingBitrate(1000000)\n\t).resolves.toBeUndefined();\n\n\t// Remove limit.\n\tawait expect(\n\t\twebRtcTransport.setMaxIncomingBitrate(0)\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.setMaxOutgoingBitrate() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tawait expect(\n\t\twebRtcTransport.setMaxOutgoingBitrate(2000000)\n\t).resolves.toBeUndefined();\n\n\t// Remove limit.\n\tawait expect(\n\t\twebRtcTransport.setMaxOutgoingBitrate(0)\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.setMinOutgoingBitrate() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tawait expect(\n\t\twebRtcTransport.setMinOutgoingBitrate(100000)\n\t).resolves.toBeUndefined();\n\n\t// Remove limit.\n\tawait expect(\n\t\twebRtcTransport.setMinOutgoingBitrate(0)\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.setMaxOutgoingBitrate() fails if value is lower than current min limit', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tawait expect(\n\t\twebRtcTransport.setMinOutgoingBitrate(3000000)\n\t).resolves.toBeUndefined();\n\n\tawait expect(webRtcTransport.setMaxOutgoingBitrate(2000000)).rejects.toThrow(\n\t\tError\n\t);\n\n\t// Remove limit.\n\tawait expect(\n\t\twebRtcTransport.setMinOutgoingBitrate(0)\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.setMinOutgoingBitrate() fails if value is higher than current max limit', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tawait expect(\n\t\twebRtcTransport.setMaxOutgoingBitrate(2000000)\n\t).resolves.toBeUndefined();\n\n\tawait expect(webRtcTransport.setMinOutgoingBitrate(3000000)).rejects.toThrow(\n\t\tError\n\t);\n\n\t// Remove limit.\n\tawait expect(\n\t\twebRtcTransport.setMaxOutgoingBitrate(0)\n\t).resolves.toBeUndefined();\n}, 2000);\n\ntest('webRtcTransport.restartIce() succeeds', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tconst previousIceUsernameFragment =\n\t\twebRtcTransport.iceParameters.usernameFragment;\n\tconst previousIcePassword = webRtcTransport.iceParameters.password;\n\n\tawait expect(webRtcTransport.restartIce()).resolves.toMatchObject({\n\t\tusernameFragment: expect.any(String),\n\t\tpassword: expect.any(String),\n\t\ticeLite: true,\n\t});\n\n\texpect(typeof webRtcTransport.iceParameters.usernameFragment).toBe('string');\n\texpect(typeof webRtcTransport.iceParameters.password).toBe('string');\n\texpect(webRtcTransport.iceParameters.usernameFragment).not.toBe(\n\t\tpreviousIceUsernameFragment\n\t);\n\texpect(webRtcTransport.iceParameters.password).not.toBe(previousIcePassword);\n}, 2000);\n\ntest('transport.enableTraceEvent() succeed', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait webRtcTransport.enableTraceEvent(['foo', 'probation']);\n\tawait expect(webRtcTransport.dump()).resolves.toMatchObject({\n\t\ttraceEventTypes: ['probation'],\n\t});\n\n\tawait webRtcTransport.enableTraceEvent();\n\tawait expect(webRtcTransport.dump()).resolves.toMatchObject({\n\t\ttraceEventTypes: [],\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait webRtcTransport.enableTraceEvent(['probation', 'FOO', 'bwe', 'BAR']);\n\tawait expect(webRtcTransport.dump()).resolves.toMatchObject({\n\t\ttraceEventTypes: ['probation', 'bwe'],\n\t});\n\n\tawait webRtcTransport.enableTraceEvent();\n\tawait expect(webRtcTransport.dump()).resolves.toMatchObject({\n\t\ttraceEventTypes: [],\n\t});\n}, 2000);\n\ntest('transport.enableTraceEvent() with wrong arguments rejects with TypeError', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(webRtcTransport.enableTraceEvent(123)).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(webRtcTransport.enableTraceEvent('probation')).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\twebRtcTransport.enableTraceEvent(['probation', 123.123])\n\t).rejects.toThrow(TypeError);\n}, 2000);\n\ntest('WebRtcTransport events succeed', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\t// API not exposed in the interface.\n\tconst channel = (webRtcTransport as WebRtcTransportImpl).channelForTesting;\n\tconst onIceStateChange = jest.fn();\n\n\twebRtcTransport.on('icestatechange', onIceStateChange);\n\n\t// Simulate a 'iceselectedtuplechange' notification coming through the\n\t// channel.\n\tconst builder = new flatbuffers.Builder();\n\tconst iceStateChangeNotification =\n\t\tnew FbsWebRtcTransport.IceStateChangeNotificationT(\n\t\t\tFbsWebRtcTransport.IceState.COMPLETED\n\t\t);\n\n\tlet notificationOffset = Notification.createNotification(\n\t\tbuilder,\n\t\tbuilder.createString(webRtcTransport.id),\n\t\tEvent.WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n\t\tNotificationBody.WebRtcTransport_IceStateChangeNotification,\n\t\ticeStateChangeNotification.pack(builder)\n\t);\n\n\tbuilder.finish(notificationOffset);\n\n\tlet notification = Notification.getRootAsNotification(\n\t\tnew flatbuffers.ByteBuffer(builder.asUint8Array())\n\t);\n\n\tchannel.emit(\n\t\twebRtcTransport.id,\n\t\tEvent.WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n\t\tnotification\n\t);\n\n\texpect(onIceStateChange).toHaveBeenCalledTimes(1);\n\texpect(onIceStateChange).toHaveBeenCalledWith('completed');\n\texpect(webRtcTransport.iceState).toBe('completed');\n\n\tbuilder.clear();\n\n\tconst onIceSelectedTuple = jest.fn();\n\tconst iceSelectedTuple: TransportTuple = {\n\t\t// @deprecated Use localAddress.\n\t\tlocalIp: '1.1.1.1',\n\t\tlocalAddress: '1.1.1.1',\n\t\tlocalPort: 1111,\n\t\tremoteIp: '2.2.2.2',\n\t\tremotePort: 2222,\n\t\tprotocol: 'udp',\n\t};\n\n\twebRtcTransport.on('iceselectedtuplechange', onIceSelectedTuple);\n\n\t// Simulate a 'icestatechange' notification coming through the channel.\n\tconst iceSelectedTupleChangeNotification =\n\t\tnew FbsWebRtcTransport.IceSelectedTupleChangeNotificationT(\n\t\t\tnew FbsTransport.TupleT(\n\t\t\t\ticeSelectedTuple.localAddress,\n\t\t\t\ticeSelectedTuple.localPort,\n\t\t\t\ticeSelectedTuple.remoteIp,\n\t\t\t\ticeSelectedTuple.remotePort,\n\t\t\t\tserializeProtocol(iceSelectedTuple.protocol)\n\t\t\t)\n\t\t);\n\n\tnotificationOffset = Notification.createNotification(\n\t\tbuilder,\n\t\tbuilder.createString(webRtcTransport.id),\n\t\tEvent.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE,\n\t\tNotificationBody.WebRtcTransport_IceSelectedTupleChangeNotification,\n\t\ticeSelectedTupleChangeNotification.pack(builder)\n\t);\n\n\tbuilder.finish(notificationOffset);\n\n\tnotification = Notification.getRootAsNotification(\n\t\tnew flatbuffers.ByteBuffer(builder.asUint8Array())\n\t);\n\n\tchannel.emit(\n\t\twebRtcTransport.id,\n\t\tEvent.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE,\n\t\tnotification\n\t);\n\n\texpect(onIceSelectedTuple).toHaveBeenCalledTimes(1);\n\texpect(onIceSelectedTuple).toHaveBeenCalledWith(iceSelectedTuple);\n\texpect(webRtcTransport.iceSelectedTuple).toEqual(iceSelectedTuple);\n\n\tbuilder.clear();\n\n\tconst onDtlsStateChange = jest.fn();\n\n\twebRtcTransport.on('dtlsstatechange', onDtlsStateChange);\n\n\t// Simulate a 'dtlsstatechange' notification coming through the channel.\n\tconst dtlsStateChangeNotification =\n\t\tnew FbsWebRtcTransport.DtlsStateChangeNotificationT(\n\t\t\tFbsWebRtcTransport.DtlsState.CONNECTING\n\t\t);\n\n\tnotificationOffset = Notification.createNotification(\n\t\tbuilder,\n\t\tbuilder.createString(webRtcTransport.id),\n\t\tEvent.WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\tNotificationBody.WebRtcTransport_DtlsStateChangeNotification,\n\t\tdtlsStateChangeNotification.pack(builder)\n\t);\n\n\tbuilder.finish(notificationOffset);\n\n\tnotification = Notification.getRootAsNotification(\n\t\tnew flatbuffers.ByteBuffer(builder.asUint8Array())\n\t);\n\n\tchannel.emit(\n\t\twebRtcTransport.id,\n\t\tEvent.WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\tnotification\n\t);\n\n\texpect(onDtlsStateChange).toHaveBeenCalledTimes(1);\n\texpect(onDtlsStateChange).toHaveBeenCalledWith('connecting');\n\texpect(webRtcTransport.dtlsState).toBe('connecting');\n}, 2000);\n\ntest('WebRtcTransport methods reject if closed', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\twebRtcTransport.observer.once('close', onObserverClose);\n\twebRtcTransport.close();\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(webRtcTransport.closed).toBe(true);\n\texpect(webRtcTransport.iceState).toBe('closed');\n\texpect(webRtcTransport.iceSelectedTuple).toBeUndefined();\n\texpect(webRtcTransport.dtlsState).toBe('closed');\n\texpect(webRtcTransport.sctpState).toBeUndefined();\n\n\tawait expect(webRtcTransport.dump()).rejects.toThrow(Error);\n\n\tawait expect(webRtcTransport.getStats()).rejects.toThrow(Error);\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(webRtcTransport.connect({})).rejects.toThrow(Error);\n\n\tawait expect(webRtcTransport.setMaxIncomingBitrate(200000)).rejects.toThrow(\n\t\tError\n\t);\n\n\tawait expect(webRtcTransport.setMaxOutgoingBitrate(200000)).rejects.toThrow(\n\t\tError\n\t);\n\n\tawait expect(webRtcTransport.setMinOutgoingBitrate(100000)).rejects.toThrow(\n\t\tError\n\t);\n\n\tawait expect(webRtcTransport.restartIce()).rejects.toThrow(Error);\n}, 2000);\n\ntest('WebRtcTransport emits \"routerclose\" if Router is closed', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenIps: ['127.0.0.1'],\n\t\tenableSctp: true,\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\twebRtcTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<WebRtcTransportEvents>(\n\t\twebRtcTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.router!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(webRtcTransport.closed).toBe(true);\n\texpect(webRtcTransport.iceState).toBe('closed');\n\texpect(webRtcTransport.iceSelectedTuple).toBeUndefined();\n\texpect(webRtcTransport.dtlsState).toBe('closed');\n\texpect(webRtcTransport.sctpState).toBe('closed');\n}, 2000);\n\ntest('WebRtcTransport emits \"routerclose\" if Worker is closed', async () => {\n\tconst webRtcTransport = await ctx.router!.createWebRtcTransport({\n\t\tlistenInfos: [\n\t\t\t{ protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } },\n\t\t],\n\t});\n\n\tconst onObserverClose = jest.fn();\n\n\twebRtcTransport.observer.once('close', onObserverClose);\n\n\tconst promise = enhancedOnce<WebRtcTransportEvents>(\n\t\twebRtcTransport,\n\t\t'routerclose'\n\t);\n\n\tctx.worker!.close();\n\tawait promise;\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(webRtcTransport.closed).toBe(true);\n\texpect(webRtcTransport.iceState).toBe('closed');\n\texpect(webRtcTransport.iceSelectedTuple).toBeUndefined();\n\texpect(webRtcTransport.dtlsState).toBe('closed');\n\texpect(webRtcTransport.sctpState).toBeUndefined();\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-Worker.ts",
    "content": "import * as os from 'node:os';\nimport * as process from 'node:process';\nimport * as path from 'node:path';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents } from '../types';\nimport { InvalidStateError } from '../errors';\n\ntest('mediasoup.workerBin matches mediasoup-worker absolute path', () => {\n\tconst workerBin = process.env['MEDIASOUP_WORKER_BIN']\n\t\t? process.env['MEDIASOUP_WORKER_BIN']\n\t\t: process.env['MEDIASOUP_BUILDTYPE'] === 'Debug'\n\t\t\t? path.join(\n\t\t\t\t\t__dirname,\n\t\t\t\t\t'..',\n\t\t\t\t\t'..',\n\t\t\t\t\t'..',\n\t\t\t\t\t'worker',\n\t\t\t\t\t'out',\n\t\t\t\t\t'Debug',\n\t\t\t\t\t'mediasoup-worker'\n\t\t\t\t)\n\t\t\t: path.join(\n\t\t\t\t\t__dirname,\n\t\t\t\t\t'..',\n\t\t\t\t\t'..',\n\t\t\t\t\t'..',\n\t\t\t\t\t'worker',\n\t\t\t\t\t'out',\n\t\t\t\t\t'Release',\n\t\t\t\t\t'mediasoup-worker'\n\t\t\t\t);\n\n\texpect(mediasoup.workerBin).toBe(workerBin);\n});\n\ntest('mediasoup.createWorker() succeeds', async () => {\n\tconst onObserverNewWorker = jest.fn();\n\n\tmediasoup.observer.once('newworker', onObserverNewWorker);\n\n\tconst worker1 = await mediasoup.createWorker();\n\n\texpect(onObserverNewWorker).toHaveBeenCalledTimes(1);\n\texpect(onObserverNewWorker).toHaveBeenCalledWith(worker1);\n\texpect(worker1.constructor.name).toBe('WorkerImpl');\n\texpect(typeof worker1.pid).toBe('number');\n\texpect(worker1.closed).toBe(false);\n\texpect(worker1.subprocessClosed).toBe(false);\n\texpect(worker1.died).toBe(false);\n\n\tworker1.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker1, 'subprocessclose');\n\n\texpect(worker1.closed).toBe(true);\n\texpect(worker1.subprocessClosed).toBe(true);\n\texpect(worker1.died).toBe(false);\n\n\tconst worker2 = await mediasoup.createWorker<{ foo: number; bar?: string }>({\n\t\tlogLevel: 'debug',\n\t\tlogTags: ['info'],\n\t\trtcMinPort: 0,\n\t\trtcMaxPort: 9999,\n\t\tdtlsCertificateFile: path.join(__dirname, 'data', 'dtls-cert.pem'),\n\t\tdtlsPrivateKeyFile: path.join(__dirname, 'data', 'dtls-key.pem'),\n\t\tlibwebrtcFieldTrials: 'WebRTC-Bwe-AlrLimitedBackoff/Disabled/',\n\t\tdisableLiburing: true,\n\t\tappData: { foo: 456 },\n\t});\n\n\texpect(worker2.constructor.name).toBe('WorkerImpl');\n\texpect(typeof worker2.pid).toBe('number');\n\texpect(worker2.closed).toBe(false);\n\texpect(worker2.subprocessClosed).toBe(false);\n\texpect(worker2.died).toBe(false);\n\texpect(worker2.appData).toEqual({ foo: 456 });\n\n\tworker2.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker2, 'subprocessclose');\n\n\texpect(worker2.closed).toBe(true);\n\texpect(worker2.subprocessClosed).toBe(true);\n\texpect(worker2.died).toBe(false);\n}, 2000);\n\ntest('mediasoup.createWorker() with wrong settings rejects with TypeError', async () => {\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(mediasoup.createWorker({ logLevel: 'chicken' })).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tawait expect(\n\t\tmediasoup.createWorker({ rtcMinPort: 1000, rtcMaxPort: 999 })\n\t).rejects.toThrow(TypeError);\n\n\t// Port is from 0 to 65535.\n\tawait expect(\n\t\tmediasoup.createWorker({ rtcMinPort: 1000, rtcMaxPort: 65536 })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tmediasoup.createWorker({ dtlsCertificateFile: '/notfound/cert.pem' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\tmediasoup.createWorker({ dtlsPrivateKeyFile: '/notfound/priv.pem' })\n\t).rejects.toThrow(TypeError);\n\n\tawait expect(\n\t\t// @ts-expect-error --- Testing purposes.\n\t\tmediasoup.createWorker({ appData: 'NOT-AN-OBJECT' })\n\t).rejects.toBeInstanceOf(TypeError);\n}, 2000);\n\ntest('mediasoup.createWorker() with wrong `workerBin` rejects with Error', async () => {\n\tawait expect(\n\t\tmediasoup.createWorker({ workerBin: '/tmp/foo/mediasoup-worker' })\n\t).rejects.toBeInstanceOf(Error);\n}, 2000);\n\ntest('worker.updateSettings() succeeds', async () => {\n\tconst worker = await mediasoup.createWorker();\n\n\tawait expect(\n\t\tworker.updateSettings({ logLevel: 'debug', logTags: ['ice'] })\n\t).resolves.toBeUndefined();\n\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n}, 2000);\n\ntest('worker.updateSettings() with wrong settings rejects with TypeError', async () => {\n\tconst worker = await mediasoup.createWorker();\n\n\t// @ts-expect-error --- Testing purposes.\n\tawait expect(worker.updateSettings({ logLevel: 'chicken' })).rejects.toThrow(\n\t\tTypeError\n\t);\n\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n}, 2000);\n\ntest('worker.updateSettings() rejects with InvalidStateError if closed', async () => {\n\tconst worker = await mediasoup.createWorker();\n\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n\n\tawait expect(worker.updateSettings({ logLevel: 'error' })).rejects.toThrow(\n\t\tInvalidStateError\n\t);\n}, 2000);\n\ntest('worker.dump() succeeds', async () => {\n\tconst worker = await mediasoup.createWorker({\n\t\t// Just for testing purposes. This does nothing since by default\n\t\t// `mediasoup.workerBin` is used.\n\t\tworkerBin: mediasoup.workerBin,\n\t});\n\n\tawait expect(worker.dump()).resolves.toMatchObject({\n\t\tpid: worker.pid,\n\t\twebRtcServerIds: [],\n\t\trouterIds: [],\n\t\tchannelMessageHandlers: {\n\t\t\tchannelRequestHandlers: [],\n\t\t\tchannelNotificationHandlers: [],\n\t\t},\n\t});\n\n\tworker.close();\n}, 2000);\n\ntest('worker.dump() rejects with InvalidStateError if closed', async () => {\n\tconst worker = await mediasoup.createWorker();\n\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n\n\tawait expect(worker.dump()).rejects.toThrow(InvalidStateError);\n}, 2000);\n\ntest('worker.getResourceUsage() succeeds', async () => {\n\tconst worker = await mediasoup.createWorker();\n\n\tawait expect(worker.getResourceUsage()).resolves.toMatchObject({});\n\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n}, 2000);\n\ntest('worker.close() succeeds', async () => {\n\tconst worker = await mediasoup.createWorker({ logLevel: 'warn' });\n\tconst onObserverClose = jest.fn();\n\n\tworker.observer.once('close', onObserverClose);\n\tworker.close();\n\n\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(worker.closed).toBe(true);\n\texpect(worker.subprocessClosed).toBe(true);\n\texpect(worker.died).toBe(false);\n}, 2000);\n\ntest('Worker emits \"died\" if mediasoup-worker process died unexpectedly', async () => {\n\tlet onDied: ReturnType<typeof jest.fn>;\n\tlet onObserverClose: ReturnType<typeof jest.fn>;\n\n\tconst worker1 = await mediasoup.createWorker({ logLevel: 'warn' });\n\n\tonDied = jest.fn();\n\tonObserverClose = jest.fn();\n\n\tworker1.observer.once('close', onObserverClose);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tworker1.on('died', () => {\n\t\t\tonDied();\n\n\t\t\tif (onObserverClose.mock.calls.length > 0) {\n\t\t\t\treject(\n\t\t\t\t\tnew Error('observer \"close\" event emitted before worker \"died\" event')\n\t\t\t\t);\n\t\t\t} else if (worker1.closed) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(new Error('worker.closed is false'));\n\t\t\t}\n\t\t});\n\n\t\tprocess.kill(worker1.pid, 'SIGINT');\n\t});\n\n\texpect(onDied).toHaveBeenCalledTimes(1);\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(worker1.closed).toBe(true);\n\texpect(worker1.subprocessClosed).toBe(true);\n\texpect(worker1.died).toBe(true);\n\n\tconst worker2 = await mediasoup.createWorker({ logLevel: 'warn' });\n\n\tonDied = jest.fn();\n\tonObserverClose = jest.fn();\n\n\tworker2.observer.once('close', onObserverClose);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tworker2.on('died', () => {\n\t\t\tonDied();\n\n\t\t\tif (onObserverClose.mock.calls.length > 0) {\n\t\t\t\treject(\n\t\t\t\t\tnew Error('observer \"close\" event emitted before worker \"died\" event')\n\t\t\t\t);\n\t\t\t} else if (worker2.closed) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(new Error('worker.closed is false'));\n\t\t\t}\n\t\t});\n\n\t\tprocess.kill(worker2.pid, 'SIGTERM');\n\t});\n\n\texpect(onDied).toHaveBeenCalledTimes(1);\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(worker2.closed).toBe(true);\n\texpect(worker2.subprocessClosed).toBe(true);\n\texpect(worker2.died).toBe(true);\n\n\tconst worker3 = await mediasoup.createWorker({ logLevel: 'warn' });\n\n\tonDied = jest.fn();\n\tonObserverClose = jest.fn();\n\n\tworker3.observer.once('close', onObserverClose);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tworker3.on('died', () => {\n\t\t\tonDied();\n\n\t\t\tif (onObserverClose.mock.calls.length > 0) {\n\t\t\t\treject(\n\t\t\t\t\tnew Error('observer \"close\" event emitted before worker \"died\" event')\n\t\t\t\t);\n\t\t\t} else if (worker3.closed) {\n\t\t\t\tresolve();\n\t\t\t} else {\n\t\t\t\treject(new Error('worker.closed is false'));\n\t\t\t}\n\t\t});\n\n\t\tprocess.kill(worker3.pid, 'SIGKILL');\n\t});\n\n\texpect(onDied).toHaveBeenCalledTimes(1);\n\texpect(onObserverClose).toHaveBeenCalledTimes(1);\n\texpect(worker3.closed).toBe(true);\n\texpect(worker3.subprocessClosed).toBe(true);\n\texpect(worker3.died).toBe(true);\n}, 5000);\n\n// Windows doesn't have some signals such as SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2\n// so we just skip this test in Windows.\nif (os.platform() !== 'win32') {\n\ttest('mediasoup-worker process ignores PIPE, HUP, ALRM, USR1 and USR2 signals', async () => {\n\t\tconst worker = await mediasoup.createWorker({ logLevel: 'warn' });\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tworker.on('died', reject);\n\n\t\t\tprocess.kill(worker.pid, 'SIGPIPE');\n\t\t\tprocess.kill(worker.pid, 'SIGHUP');\n\t\t\tprocess.kill(worker.pid, 'SIGALRM');\n\t\t\tprocess.kill(worker.pid, 'SIGUSR1');\n\t\t\tprocess.kill(worker.pid, 'SIGUSR2');\n\n\t\t\tsetTimeout(() => {\n\t\t\t\texpect(worker.closed).toBe(false);\n\t\t\t\texpect(worker.subprocessClosed).toBe(false);\n\t\t\t\texpect(worker.died).toBe(false);\n\n\t\t\t\tworker.on('subprocessclose', resolve);\n\t\t\t\tworker.close();\n\t\t\t}, 2000);\n\t\t});\n\t}, 4000);\n}\n"
  },
  {
    "path": "node/src/test/test-mediasoup.ts",
    "content": "import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { enhancedOnce } from '../enhancedEvents';\nimport * as mediasoup from '../';\nimport type { WorkerEvents } from '../types';\n\nconst PKG = JSON.parse(\n\tfs.readFileSync(path.join(__dirname, '..', '..', '..', 'package.json'), {\n\t\tencoding: 'utf-8',\n\t})\n);\n\nconst { version, getSupportedRtpCapabilities, parseScalabilityMode } =\n\tmediasoup;\n\ntest('mediasoup.version matches version field in package.json', () => {\n\texpect(version).toBe(PKG.version);\n});\n\ntest('mediasoup.setLoggerEventListeners() succeeds', async () => {\n\tconst onDebug = jest.fn();\n\n\tmediasoup.setLogEventListeners({\n\t\tondebug: onDebug,\n\t\tonwarn: undefined,\n\t\tonerror: undefined,\n\t});\n\n\tconst worker = await mediasoup.createWorker();\n\n\tworker.close();\n\n\texpect(onDebug).toHaveBeenCalled();\n\n\tif (worker.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(worker, 'subprocessclose');\n\t}\n}, 2000);\n\ntest('mediasoup.getSupportedRtpCapabilities() returns the mediasoup RTP capabilities', () => {\n\tconst rtpCapabilities = getSupportedRtpCapabilities();\n\n\texpect(typeof rtpCapabilities).toBe('object');\n\n\t// Mangle retrieved codecs to check that, if called again,\n\t// getSupportedRtpCapabilities() returns a cloned object.\n\t// @ts-expect-error --- Testing purposes.\n\trtpCapabilities.codecs = 'bar';\n\n\tconst rtpCapabilities2 = getSupportedRtpCapabilities();\n\n\texpect(rtpCapabilities2).not.toEqual(rtpCapabilities);\n});\n\ntest('mediasoup.parseScalabilityMode() succeeds', () => {\n\texpect(parseScalabilityMode('L1T3')).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 3,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('L3T2_KEY')).toEqual({\n\t\tspatialLayers: 3,\n\t\ttemporalLayers: 2,\n\t\tksvc: true,\n\t});\n\n\texpect(parseScalabilityMode('S2T3')).toEqual({\n\t\tspatialLayers: 2,\n\t\ttemporalLayers: 3,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('foo')).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 1,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode(undefined)).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 1,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('S0T3')).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 1,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('S1T0')).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 1,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('L20T3')).toEqual({\n\t\tspatialLayers: 20,\n\t\ttemporalLayers: 3,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('S200T3')).toEqual({\n\t\tspatialLayers: 1,\n\t\ttemporalLayers: 1,\n\t\tksvc: false,\n\t});\n\n\texpect(parseScalabilityMode('L4T7_KEY_SHIFT')).toEqual({\n\t\tspatialLayers: 4,\n\t\ttemporalLayers: 7,\n\t\tksvc: true,\n\t});\n});\n"
  },
  {
    "path": "node/src/test/test-multiopus.ts",
    "content": "import * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents } from '../types';\nimport { UnsupportedError } from '../errors';\nimport * as utils from '../utils';\n\ntype TestContext = {\n\tmediaCodecs: mediasoup.types.RtpCodecCapability[];\n\taudioProducerOptions: mediasoup.types.ProducerOptions;\n\tconsumerDeviceCapabilities: mediasoup.types.RtpCapabilities;\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\twebRtcTransport?: mediasoup.types.WebRtcTransport;\n};\n\nconst ctx: TestContext = {\n\tmediaCodecs: utils.deepFreeze<mediasoup.types.RtpCodecCapability[]>([\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tpreferredPayloadType: 100,\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 6,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\tnum_streams: 4,\n\t\t\t\tcoupled_streams: 2,\n\t\t\t},\n\t\t},\n\t]),\n\taudioProducerOptions: utils.deepFreeze<mediasoup.types.ProducerOptions>({\n\t\tkind: 'audio',\n\t\trtpParameters: {\n\t\t\tmid: 'AUDIO',\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/multiopus',\n\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 6,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tuseinbandfec: 1,\n\t\t\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\t\t\tnum_streams: 4,\n\t\t\t\t\t\tcoupled_streams: 2,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tid: 10,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tid: 12,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t}),\n\tconsumerDeviceCapabilities: utils.deepFreeze<mediasoup.types.RtpCapabilities>(\n\t\t{\n\t\t\tcodecs: [\n\t\t\t\t{\n\t\t\t\t\tmimeType: 'audio/multiopus',\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\tchannels: 6,\n\t\t\t\t\tparameters: {\n\t\t\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\t\t\tnum_streams: 4,\n\t\t\t\t\t\tcoupled_streams: 2,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\theaderExtensions: [\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\t\tpreferredId: 1,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time',\n\t\t\t\t\tpreferredId: 4,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tkind: 'audio',\n\t\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\t\tpreferredId: 10,\n\t\t\t\t\tpreferredEncrypt: false,\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t),\n};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker();\n\tctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs });\n\tctx.webRtcTransport = await ctx.router.createWebRtcTransport({\n\t\tlistenInfos: [{ protocol: 'udp', ip: '127.0.0.1' }],\n\t});\n});\n\nafterEach(async () => {\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('produce() and consume() succeed', async () => {\n\tconst audioProducer = await ctx.webRtcTransport!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\texpect(audioProducer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tpayloadType: 0,\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 6,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\tnum_streams: 4,\n\t\t\t\tcoupled_streams: 2,\n\t\t\t},\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n\n\texpect(\n\t\tctx.router!.canConsume({\n\t\t\tproducerId: audioProducer.id,\n\t\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t\t})\n\t).toBe(true);\n\n\tconst audioConsumer = await ctx.webRtcTransport!.consume({\n\t\tproducerId: audioProducer.id,\n\t\trtpCapabilities: ctx.consumerDeviceCapabilities,\n\t});\n\n\texpect(audioConsumer.rtpParameters.codecs).toEqual([\n\t\t{\n\t\t\tmimeType: 'audio/multiopus',\n\t\t\tpayloadType: 100,\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 6,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\tnum_streams: 4,\n\t\t\t\tcoupled_streams: 2,\n\t\t\t},\n\t\t\trtcpFeedback: [],\n\t\t},\n\t]);\n}, 2000);\n\ntest('fails to produce wrong parameters', async () => {\n\tawait expect(\n\t\tctx.webRtcTransport!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tmid: 'AUDIO',\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'audio/multiopus',\n\t\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\t\tchannels: 6,\n\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\t\t\t\tnum_streams: 2,\n\t\t\t\t\t\t\tcoupled_streams: 2,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n\n\tawait expect(\n\t\tctx.webRtcTransport!.produce({\n\t\t\tkind: 'audio',\n\t\t\trtpParameters: {\n\t\t\t\tmid: 'AUDIO',\n\t\t\t\tcodecs: [\n\t\t\t\t\t{\n\t\t\t\t\t\tmimeType: 'audio/multiopus',\n\t\t\t\t\t\tpayloadType: 0,\n\t\t\t\t\t\tclockRate: 48000,\n\t\t\t\t\t\tchannels: 6,\n\t\t\t\t\t\tparameters: {\n\t\t\t\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\t\t\t\tnum_streams: 4,\n\t\t\t\t\t\t\tcoupled_streams: 1,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t})\n\t).rejects.toThrow(UnsupportedError);\n}, 2000);\n\ntest('fails to consume wrong channels', async () => {\n\tconst audioProducer = await ctx.webRtcTransport!.produce(\n\t\tctx.audioProducerOptions\n\t);\n\n\tconst localConsumerDeviceCapabilities: mediasoup.types.RtpCapabilities = {\n\t\tcodecs: [\n\t\t\t{\n\t\t\t\tmimeType: 'audio/multiopus',\n\t\t\t\tkind: 'audio',\n\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\tclockRate: 48000,\n\t\t\t\tchannels: 8,\n\t\t\t\tparameters: {\n\t\t\t\t\tchannel_mapping: '0,4,1,2,3,5',\n\t\t\t\t\tnum_streams: 4,\n\t\t\t\t\tcoupled_streams: 2,\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t};\n\n\texpect(\n\t\t!ctx.router!.canConsume({\n\t\t\tproducerId: audioProducer.id,\n\t\t\trtpCapabilities: localConsumerDeviceCapabilities,\n\t\t})\n\t).toBe(true);\n\n\tawait expect(\n\t\tctx.webRtcTransport!.consume({\n\t\t\tproducerId: audioProducer.id,\n\t\t\trtpCapabilities: localConsumerDeviceCapabilities,\n\t\t})\n\t).rejects.toThrow(Error);\n}, 2000);\n"
  },
  {
    "path": "node/src/test/test-ortc.ts",
    "content": "import * as mediasoup from '../';\nimport * as ortc from '../ortc';\nimport { UnsupportedError } from '../errors';\n\ntest('generateRouterRtpCapabilities() succeeds', () => {\n\tconst mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t\tparameters: {\n\t\t\t\tuseinbandfec: 1,\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/VP8',\n\t\t\tpreferredPayloadType: 125, // Let's force it.\n\t\t\tclockRate: 90000,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'profile-level-id': '42e01f',\n\t\t\t\tfoo: 'bar',\n\t\t\t},\n\t\t\trtcpFeedback: [], // Will be ignored.\n\t\t},\n\t];\n\n\tconst rtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs);\n\n\texpect(rtpCapabilities.codecs?.length).toBe(5);\n\n\t// opus.\n\texpect(rtpCapabilities.codecs?.[0]).toEqual({\n\t\tkind: 'audio',\n\t\tmimeType: 'audio/opus',\n\t\tpreferredPayloadType: 100, // 100 is the first available dynamic PT.\n\t\tclockRate: 48000,\n\t\tchannels: 2,\n\t\tparameters: {\n\t\t\tuseinbandfec: 1,\n\t\t\tfoo: 'bar',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'transport-cc', parameter: '' },\n\t\t],\n\t});\n\n\t// VP8.\n\texpect(rtpCapabilities.codecs?.[1]).toEqual({\n\t\tkind: 'video',\n\t\tmimeType: 'video/VP8',\n\t\tpreferredPayloadType: 125,\n\t\tclockRate: 90000,\n\t\tparameters: {},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t\t{ type: 'transport-cc', parameter: '' },\n\t\t],\n\t});\n\n\t// VP8 RTX.\n\texpect(rtpCapabilities.codecs?.[2]).toEqual({\n\t\tkind: 'video',\n\t\tmimeType: 'video/rtx',\n\t\tpreferredPayloadType: 101, // 101 is the second available dynamic PT.\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\tapt: 125,\n\t\t},\n\t\trtcpFeedback: [],\n\t});\n\n\t// H264.\n\texpect(rtpCapabilities.codecs?.[3]).toEqual({\n\t\tkind: 'video',\n\t\tmimeType: 'video/H264',\n\t\tpreferredPayloadType: 102, // 102 is the third available dynamic PT.\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\t// Since packetization-mode param was not included in the H264 codec\n\t\t\t// and it's default value is 0, it's not added by ortc file.\n\t\t\t// 'packetization-mode'      : 0,\n\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t'profile-level-id': '42e01f',\n\t\t\tfoo: 'bar',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t\t{ type: 'transport-cc', parameter: '' },\n\t\t],\n\t});\n\n\t// H264 RTX.\n\texpect(rtpCapabilities.codecs?.[4]).toEqual({\n\t\tkind: 'video',\n\t\tmimeType: 'video/rtx',\n\t\tpreferredPayloadType: 103,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\tapt: 102,\n\t\t},\n\t\trtcpFeedback: [],\n\t});\n});\n\ntest('generateRouterRtpCapabilities() with unsupported codecs throws UnsupportedError', () => {\n\tlet mediaCodecs: mediasoup.types.RouterRtpCodecCapability[];\n\n\tmediaCodecs = [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/chicken',\n\t\t\tclockRate: 8000,\n\t\t\tchannels: 4,\n\t\t},\n\t];\n\n\texpect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow(\n\t\tUnsupportedError\n\t);\n\n\tmediaCodecs = [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 1,\n\t\t},\n\t];\n\n\texpect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow(\n\t\tUnsupportedError\n\t);\n});\n\ntest('generateRouterRtpCapabilities() with too many codecs throws', () => {\n\tconst mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [];\n\n\tfor (let i = 0; i < 100; ++i) {\n\t\tmediaCodecs.push({\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t});\n\t}\n\n\texpect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow(\n\t\t'cannot allocate'\n\t);\n});\n\ntest('getProducerRtpParametersMapping(), getConsumableRtpParameters(), getConsumerRtpParameters() and getPipeConsumerRtpParameters() succeed', () => {\n\tconst mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'level-asymmetry-allowed': 1,\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\tbar: 'lalala',\n\t\t\t},\n\t\t},\n\t];\n\n\tconst routerRtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs);\n\n\tconst rtpParameters: mediasoup.types.RtpParameters = {\n\t\tcodecs: [\n\t\t\t{\n\t\t\t\tmimeType: 'video/H264',\n\t\t\t\tpayloadType: 111,\n\t\t\t\tclockRate: 90000,\n\t\t\t\tparameters: {\n\t\t\t\t\tfoo: 1234,\n\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t},\n\t\t\t\trtcpFeedback: [\n\t\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t{ type: 'goog-remb', parameter: '' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\tpayloadType: 112,\n\t\t\t\tclockRate: 90000,\n\t\t\t\tparameters: {\n\t\t\t\t\tapt: 111,\n\t\t\t\t},\n\t\t\t\trtcpFeedback: [],\n\t\t\t},\n\t\t],\n\t\theaderExtensions: [\n\t\t\t{\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\tid: 1,\n\t\t\t},\n\t\t\t{\n\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\tid: 2,\n\t\t\t},\n\t\t],\n\t\tencodings: [\n\t\t\t{\n\t\t\t\tssrc: 11111111,\n\t\t\t\trtx: { ssrc: 11111112 },\n\t\t\t\tmaxBitrate: 111111,\n\t\t\t\tscalabilityMode: 'L1T3',\n\t\t\t},\n\t\t\t{\n\t\t\t\tssrc: 21111111,\n\t\t\t\trtx: { ssrc: 21111112 },\n\t\t\t\tmaxBitrate: 222222,\n\t\t\t\tscalabilityMode: 'L1T3',\n\t\t\t},\n\t\t\t{\n\t\t\t\trid: 'high',\n\t\t\t\tmaxBitrate: 333333,\n\t\t\t\tscalabilityMode: 'L1T3',\n\t\t\t},\n\t\t],\n\t\trtcp: {\n\t\t\tcname: 'qwerty1234',\n\t\t},\n\t};\n\n\tconst rtpMapping = ortc.getProducerRtpParametersMapping(\n\t\trtpParameters,\n\t\trouterRtpCapabilities\n\t);\n\n\texpect(rtpMapping.codecs).toEqual([\n\t\t{ payloadType: 111, mappedPayloadType: 101 },\n\t\t{ payloadType: 112, mappedPayloadType: 102 },\n\t]);\n\n\texpect(rtpMapping.encodings[0]!.ssrc).toBe(11111111);\n\texpect(rtpMapping.encodings[0]!.rid).toBeUndefined();\n\texpect(typeof rtpMapping.encodings[0]!.mappedSsrc).toBe('number');\n\texpect(rtpMapping.encodings[1]!.ssrc).toBe(21111111);\n\texpect(rtpMapping.encodings[1]!.rid).toBeUndefined();\n\texpect(typeof rtpMapping.encodings[1]!.mappedSsrc).toBe('number');\n\texpect(rtpMapping.encodings[2]!.ssrc).toBeUndefined();\n\texpect(rtpMapping.encodings[2]!.rid).toBe('high');\n\texpect(typeof rtpMapping.encodings[2]!.mappedSsrc).toBe('number');\n\n\tconst consumableRtpParameters = ortc.getConsumableRtpParameters(\n\t\t'video',\n\t\trtpParameters,\n\t\trouterRtpCapabilities,\n\t\trtpMapping\n\t);\n\n\texpect(consumableRtpParameters.codecs[0]!.mimeType).toBe('video/H264');\n\texpect(consumableRtpParameters.codecs[0]!.payloadType).toBe(101);\n\texpect(consumableRtpParameters.codecs[0]!.clockRate).toBe(90000);\n\texpect(consumableRtpParameters.codecs[0]!.parameters).toEqual({\n\t\tfoo: 1234,\n\t\t'packetization-mode': 1,\n\t\t'profile-level-id': '4d0032',\n\t});\n\n\texpect(consumableRtpParameters.codecs[1]!.mimeType).toBe('video/rtx');\n\texpect(consumableRtpParameters.codecs[1]!.payloadType).toBe(102);\n\texpect(consumableRtpParameters.codecs[1]!.clockRate).toBe(90000);\n\texpect(consumableRtpParameters.codecs[1]!.parameters).toEqual({ apt: 101 });\n\n\texpect(consumableRtpParameters.encodings?.[0]).toEqual({\n\t\tssrc: rtpMapping.encodings[0]!.mappedSsrc,\n\t\tmaxBitrate: 111111,\n\t\tscalabilityMode: 'L1T3',\n\t});\n\texpect(consumableRtpParameters.encodings?.[1]).toEqual({\n\t\tssrc: rtpMapping.encodings[1]!.mappedSsrc,\n\t\tmaxBitrate: 222222,\n\t\tscalabilityMode: 'L1T3',\n\t});\n\texpect(consumableRtpParameters.encodings?.[2]).toEqual({\n\t\tssrc: rtpMapping.encodings[2]!.mappedSsrc,\n\t\tmaxBitrate: 333333,\n\t\tscalabilityMode: 'L1T3',\n\t});\n\n\texpect(consumableRtpParameters.rtcp).toEqual({\n\t\tcname: rtpParameters.rtcp?.cname,\n\t\treducedSize: true,\n\t});\n\n\tconst remoteRtpCapabilities: mediasoup.types.RtpCapabilities = {\n\t\tcodecs: [\n\t\t\t{\n\t\t\t\tkind: 'audio',\n\t\t\t\tmimeType: 'audio/opus',\n\t\t\t\tpreferredPayloadType: 100,\n\t\t\t\tclockRate: 48000,\n\t\t\t\tchannels: 2,\n\t\t\t\tparameters: {},\n\t\t\t\trtcpFeedback: [],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\tmimeType: 'video/H264',\n\t\t\t\tpreferredPayloadType: 101,\n\t\t\t\tclockRate: 90000,\n\t\t\t\tparameters: {\n\t\t\t\t\t'packetization-mode': 1,\n\t\t\t\t\t'profile-level-id': '4d0032',\n\t\t\t\t\tbaz: 'LOLOLO',\n\t\t\t\t},\n\t\t\t\trtcpFeedback: [\n\t\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t\t\t{ type: 'foo', parameter: 'FOO' },\n\t\t\t\t],\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\tmimeType: 'video/rtx',\n\t\t\t\tpreferredPayloadType: 102,\n\t\t\t\tclockRate: 90000,\n\t\t\t\tparameters: {\n\t\t\t\t\tapt: 101,\n\t\t\t\t},\n\t\t\t\trtcpFeedback: [],\n\t\t\t},\n\t\t],\n\t\theaderExtensions: [\n\t\t\t{\n\t\t\t\tkind: 'audio',\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\tpreferredId: 1,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\t\tpreferredId: 1,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',\n\t\t\t\tpreferredId: 2,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'audio',\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level',\n\t\t\t\tpreferredId: 6,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\t\tpreferredId: 8,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t\t{\n\t\t\t\tkind: 'video',\n\t\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\t\tpreferredId: 9,\n\t\t\t\tpreferredEncrypt: false,\n\t\t\t\tdirection: 'sendrecv',\n\t\t\t},\n\t\t],\n\t};\n\n\tconst consumerRtpParameters = ortc.getConsumerRtpParameters({\n\t\tconsumableRtpParameters,\n\t\tremoteRtpCapabilities,\n\t\tpipe: false,\n\t\tenableRtx: true,\n\t});\n\n\texpect(consumerRtpParameters.codecs.length).toEqual(2);\n\texpect(consumerRtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'video/H264',\n\t\tpayloadType: 101,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\tfoo: 1234,\n\t\t\t'packetization-mode': 1,\n\t\t\t'profile-level-id': '4d0032',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'foo', parameter: 'FOO' },\n\t\t],\n\t});\n\texpect(consumerRtpParameters.codecs[1]).toEqual({\n\t\tmimeType: 'video/rtx',\n\t\tpayloadType: 102,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\tapt: 101,\n\t\t},\n\t\trtcpFeedback: [],\n\t});\n\n\texpect(consumerRtpParameters.encodings!.length).toBe(1);\n\texpect(typeof consumerRtpParameters.encodings![0]!.ssrc).toBe('number');\n\texpect(typeof consumerRtpParameters.encodings![0]!.rtx).toBe('object');\n\texpect(typeof consumerRtpParameters.encodings![0]!.rtx?.ssrc).toBe('number');\n\texpect(consumerRtpParameters.encodings![0]!.scalabilityMode).toBe('L3T3');\n\texpect(consumerRtpParameters.encodings![0]!.maxBitrate).toBe(333333);\n\n\texpect(consumerRtpParameters.headerExtensions).toEqual([\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:sdes:mid',\n\t\t\tid: 1,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:3gpp:video-orientation',\n\t\t\tid: 8,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t\t{\n\t\t\turi: 'urn:ietf:params:rtp-hdrext:toffset',\n\t\t\tid: 9,\n\t\t\tencrypt: false,\n\t\t\tparameters: {},\n\t\t},\n\t]);\n\n\texpect(consumerRtpParameters.rtcp).toEqual({\n\t\tcname: rtpParameters.rtcp?.cname,\n\t\treducedSize: true,\n\t});\n\n\tconst pipeConsumerRtpParameters = ortc.getPipeConsumerRtpParameters({\n\t\tconsumableRtpParameters,\n\t\tenableRtx: false,\n\t});\n\n\texpect(pipeConsumerRtpParameters.codecs.length).toEqual(1);\n\texpect(pipeConsumerRtpParameters.codecs[0]).toEqual({\n\t\tmimeType: 'video/H264',\n\t\tpayloadType: 101,\n\t\tclockRate: 90000,\n\t\tparameters: {\n\t\t\tfoo: 1234,\n\t\t\t'packetization-mode': 1,\n\t\t\t'profile-level-id': '4d0032',\n\t\t},\n\t\trtcpFeedback: [\n\t\t\t{ type: 'nack', parameter: 'pli' },\n\t\t\t{ type: 'ccm', parameter: 'fir' },\n\t\t],\n\t});\n\n\texpect(pipeConsumerRtpParameters.encodings!.length).toBe(3);\n\texpect(typeof pipeConsumerRtpParameters.encodings![0]!.ssrc).toBe('number');\n\texpect(pipeConsumerRtpParameters.encodings![0]!.rtx).toBeUndefined();\n\texpect(typeof pipeConsumerRtpParameters.encodings![0]!.maxBitrate).toBe(\n\t\t'number'\n\t);\n\texpect(pipeConsumerRtpParameters.encodings![0]!.scalabilityMode).toBe('L1T3');\n\texpect(typeof pipeConsumerRtpParameters.encodings![1]!.ssrc).toBe('number');\n\texpect(pipeConsumerRtpParameters.encodings![1]!.rtx).toBeUndefined();\n\texpect(typeof pipeConsumerRtpParameters.encodings![1]!.maxBitrate).toBe(\n\t\t'number'\n\t);\n\texpect(pipeConsumerRtpParameters.encodings![1]!.scalabilityMode).toBe('L1T3');\n\texpect(typeof pipeConsumerRtpParameters.encodings![2]!.ssrc).toBe('number');\n\texpect(pipeConsumerRtpParameters.encodings![2]!.rtx).toBeUndefined();\n\texpect(typeof pipeConsumerRtpParameters.encodings![2]!.maxBitrate).toBe(\n\t\t'number'\n\t);\n\texpect(pipeConsumerRtpParameters.encodings![2]!.scalabilityMode).toBe('L1T3');\n\n\texpect(pipeConsumerRtpParameters.rtcp).toEqual({\n\t\tcname: rtpParameters.rtcp?.cname,\n\t\treducedSize: true,\n\t});\n});\n\ntest('getProducerRtpParametersMapping() with incompatible params throws UnsupportedError', () => {\n\tconst mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [\n\t\t{\n\t\t\tkind: 'audio',\n\t\t\tmimeType: 'audio/opus',\n\t\t\tclockRate: 48000,\n\t\t\tchannels: 2,\n\t\t},\n\t\t{\n\t\t\tkind: 'video',\n\t\t\tmimeType: 'video/H264',\n\t\t\tclockRate: 90000,\n\t\t\tparameters: {\n\t\t\t\t'packetization-mode': 1,\n\t\t\t\t'profile-level-id': '640032',\n\t\t\t},\n\t\t},\n\t];\n\n\tconst routerRtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs);\n\n\tconst rtpParameters = {\n\t\tcodecs: [\n\t\t\t{\n\t\t\t\tmimeType: 'video/VP8',\n\t\t\t\tpayloadType: 120,\n\t\t\t\tclockRate: 90000,\n\t\t\t\trtcpFeedback: [\n\t\t\t\t\t{ type: 'nack', parameter: '' },\n\t\t\t\t\t{ type: 'nack', parameter: 'fir' },\n\t\t\t\t],\n\t\t\t},\n\t\t],\n\t\theaderExtensions: [],\n\t\tencodings: [{ ssrc: 11111111 }],\n\t\trtcp: {\n\t\t\tcname: 'qwerty1234',\n\t\t},\n\t};\n\n\texpect(() =>\n\t\tortc.getProducerRtpParametersMapping(rtpParameters, routerRtpCapabilities)\n\t).toThrow(UnsupportedError);\n});\n"
  },
  {
    "path": "node/src/test/test-werift-sctp.ts",
    "content": "import { createSocket } from 'node:dgram';\nimport {\n\tSCTP,\n\tSCTP_STATE,\n\tWEBRTC_PPID,\n\tcreateUdpTransport as createSctpUdpTransport,\n} from 'werift-sctp';\nimport * as mediasoup from '../';\nimport { enhancedOnce } from '../enhancedEvents';\nimport type { WorkerEvents } from '../types';\n\ntype TestContext = {\n\tworker?: mediasoup.types.Worker;\n\trouter?: mediasoup.types.Router;\n\tplainTransport?: mediasoup.types.PlainTransport;\n\tdataProducer?: mediasoup.types.DataProducer;\n\tdataConsumer?: mediasoup.types.DataConsumer;\n\tsctpClient?: SCTP;\n\tsctpSendStreamId?: number;\n};\n\nconst ctx: TestContext = {};\n\nbeforeEach(async () => {\n\tctx.worker = await mediasoup.createWorker({\n\t\tdisableLiburing: true,\n\t});\n\n\tctx.router = await ctx.worker.createRouter();\n\n\tctx.plainTransport = await ctx.router.createPlainTransport({\n\t\t// https://github.com/nodejs/node/issues/14900.\n\t\tlistenIp: '127.0.0.1',\n\t\t// So we don't need to call plainTransport.connect().\n\t\tcomedia: true,\n\t\tenableSctp: true,\n\t\tnumSctpStreams: { OS: 256, MIS: 256 },\n\t});\n\n\t// Create an explicit SCTP outgoing stream id.\n\tctx.sctpSendStreamId = 123;\n\n\tctx.sctpClient = SCTP.client(\n\t\tcreateSctpUdpTransport(createSocket('udp4'), {\n\t\t\tport: ctx.plainTransport.tuple.localPort,\n\t\t\taddress: ctx.plainTransport.tuple.localAddress,\n\t\t})\n\t);\n\n\t// Create a DataProducer with the corresponding SCTP stream id.\n\tctx.dataProducer = await ctx.plainTransport.produceData({\n\t\tsctpStreamParameters: {\n\t\t\tstreamId: ctx.sctpSendStreamId,\n\t\t\tordered: true,\n\t\t},\n\t\tlabel: 'node-sctp',\n\t\tprotocol: 'foo & bar 😀😀😀',\n\t});\n\n\t// Create a DataConsumer to receive messages from the DataProducer over the\n\t// same plainTransport.\n\tctx.dataConsumer = await ctx.plainTransport.consumeData({\n\t\tdataProducerId: ctx.dataProducer.id,\n\t});\n\n\tlet connectionTimeoutTimer: NodeJS.Timeout | undefined;\n\n\tawait Promise.race([\n\t\t// Wait for SCTP to become connected in both the PlainTransport and in the\n\t\t// werift-sctp client.\n\t\tPromise.all([\n\t\t\t// Connect werift-sctp client (this resolves once SCTP is connected).\n\t\t\tctx.sctpClient.start(5000),\n\t\t\t// This resolves once connected too.\n\t\t\tctx.sctpClient.stateChanged.connected.asPromise(),\n\t\t\t// Wait for SCTP state in the mediasoup PlainTransport to be \"connected\".\n\t\t\tnew Promise<void>((resolve, reject) => {\n\t\t\t\tif (ctx.plainTransport?.sctpState === 'connected') {\n\t\t\t\t\tresolve();\n\t\t\t\t} else {\n\t\t\t\t\tctx.plainTransport?.on('sctpstatechange', state => {\n\t\t\t\t\t\tif (state === 'connected') {\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t} else if (state === 'failed' || state === 'closed') {\n\t\t\t\t\t\t\treject(\n\t\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t\t'SCTP connection in PlainTransport failed or was closed'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}),\n\t\t]),\n\t\tnew Promise<void>((resolve, reject) => {\n\t\t\tconnectionTimeoutTimer = setTimeout(\n\t\t\t\t() => reject(new Error('SCTP connection timeout')),\n\t\t\t\t3000\n\t\t\t);\n\t\t}),\n\t]);\n\n\tclearTimeout(connectionTimeoutTimer);\n}, 5000);\n\nafterEach(async () => {\n\tawait ctx.sctpClient?.stop();\n\tctx.sctpClient?.transport.close();\n\tctx.worker?.close();\n\n\tif (ctx.worker?.subprocessClosed === false) {\n\t\tawait enhancedOnce<WorkerEvents>(ctx.worker, 'subprocessclose');\n\t}\n});\n\ntest('SCTP state is connected', () => {\n\texpect(ctx.plainTransport!.sctpState).toBe('connected');\n\texpect(ctx.sctpClient!.associationState).toBe(SCTP_STATE.ESTABLISHED);\n});\n\ntest('ordered DataProducer delivers all SCTP messages to the DataConsumer', async () => {\n\tconst numMessages = 200;\n\tlet sentMessageBytes = 0;\n\tlet recvMessageBytes = 0;\n\tlet numSentMessages = 0;\n\tlet numReceivedMessages = 0;\n\n\t// It must be zero because it's the first DataConsumer on the plainTransport.\n\texpect(ctx.dataConsumer!.sctpStreamParameters?.streamId).toBe(0);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tsendNextMessage();\n\n\t\tfunction sendNextMessage(): void {\n\t\t\tconst id = ++numSentMessages;\n\t\t\tconst data = Buffer.from(String(id));\n\t\t\tlet ppid: WEBRTC_PPID;\n\n\t\t\t// Set ppid of type WebRTC DataChannel string.\n\t\t\tif (id < numMessages / 2) {\n\t\t\t\tppid = WEBRTC_PPID.STRING;\n\t\t\t}\n\t\t\t// Set ppid of type WebRTC DataChannel binary.\n\t\t\telse {\n\t\t\t\tppid = WEBRTC_PPID.BINARY;\n\t\t\t}\n\n\t\t\tvoid ctx.sctpClient!.send(ctx.sctpSendStreamId!, ppid, data);\n\n\t\t\tsentMessageBytes += data.byteLength;\n\n\t\t\tif (id < numMessages) {\n\t\t\t\tsendNextMessage();\n\t\t\t}\n\t\t}\n\n\t\tctx.sctpClient!.onReceive.subscribe(\n\t\t\t(streamId: number, ppid: WEBRTC_PPID, data: Buffer) => {\n\t\t\t\t// `streamId`  must be zero because it's the first SCTP incoming stream\n\t\t\t\t// (so first DataConsumer).\n\t\t\t\tif (streamId !== 0) {\n\t\t\t\t\treject(new Error(`streamId should be 0 but it is ${streamId}`));\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t++numReceivedMessages;\n\t\t\t\trecvMessageBytes += data.byteLength;\n\n\t\t\t\tconst id = Number(data.toString('utf8'));\n\n\t\t\t\tif (id !== numReceivedMessages) {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`id ${id} in message should match numReceivedMessages ${numReceivedMessages}`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t} else if (id === numMessages) {\n\t\t\t\t\tresolve();\n\t\t\t\t} else if (id < numMessages / 2 && ppid !== WEBRTC_PPID.STRING) {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`ppid in message with id ${id} should be ${WEBRTC_PPID.STRING} but it is ${ppid}`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t} else if (id > numMessages / 2 && ppid !== WEBRTC_PPID.BINARY) {\n\t\t\t\t\treject(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`ppid in message with id ${id} should be ${WEBRTC_PPID.BINARY} but it is ${ppid}`\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t});\n\n\texpect(numSentMessages).toBe(numMessages);\n\texpect(numReceivedMessages).toBe(numMessages);\n\texpect(recvMessageBytes).toBe(sentMessageBytes);\n\n\tawait expect(ctx.dataProducer!.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-producer',\n\t\t\tlabel: ctx.dataProducer!.label,\n\t\t\tprotocol: ctx.dataProducer!.protocol,\n\t\t\tmessagesReceived: numMessages,\n\t\t\tbytesReceived: sentMessageBytes,\n\t\t},\n\t]);\n\n\tawait expect(ctx.dataConsumer!.getStats()).resolves.toMatchObject([\n\t\t{\n\t\t\ttype: 'data-consumer',\n\t\t\tlabel: ctx.dataConsumer!.label,\n\t\t\tprotocol: ctx.dataConsumer!.protocol,\n\t\t\tmessagesSent: numMessages,\n\t\t\tbytesSent: recvMessageBytes,\n\t\t},\n\t]);\n}, 10000);\n"
  },
  {
    "path": "node/src/types.ts",
    "content": "export type * from './indexTypes';\nexport type * from './WorkerTypes';\nexport type * from './WebRtcServerTypes';\nexport type * from './RouterTypes';\nexport type * from './TransportTypes';\nexport type * from './WebRtcTransportTypes';\nexport type * from './PlainTransportTypes';\nexport type * from './PipeTransportTypes';\nexport type * from './DirectTransportTypes';\nexport type * from './ProducerTypes';\nexport type * from './ConsumerTypes';\nexport type * from './DataProducerTypes';\nexport type * from './DataConsumerTypes';\nexport type * from './RtpObserverTypes';\nexport type * from './ActiveSpeakerObserverTypes';\nexport type * from './AudioLevelObserverTypes';\nexport type * from './rtpParametersTypes';\nexport type * from './rtpStreamStatsTypes';\nexport type * from './sctpParametersTypes';\nexport type * from './srtpParametersTypes';\nexport type * from './scalabilityModesTypes';\nexport type * from './errors';\n\ntype Only<T, U> = {\n\t[P in keyof T]: T[P];\n} & {\n\t[P in keyof U]?: never;\n};\n\nexport type Either<T, U> = Only<T, U> | Only<U, T>;\n\nexport type AppData = {\n\t[key: string]: unknown;\n};\n"
  },
  {
    "path": "node/src/utils.ts",
    "content": "import { randomUUID, randomInt } from 'node:crypto';\n\n/**\n * Clones the given value.\n */\nexport function clone<T>(value: T): T {\n\tif (value === undefined) {\n\t\treturn undefined as unknown as T;\n\t} else if (Number.isNaN(value)) {\n\t\treturn NaN as unknown as T;\n\t} else if (typeof structuredClone === 'function') {\n\t\t// Available in Node >= 18.\n\t\treturn structuredClone(value);\n\t} else {\n\t\treturn JSON.parse(JSON.stringify(value));\n\t}\n}\n\n/**\n * Generates a random UUID v4.\n */\nexport function generateUUIDv4(): string {\n\treturn randomUUID();\n}\n\n/**\n * Generates a random positive integer.\n */\nexport function generateRandomNumber(): number {\n\treturn randomInt(100_000_000, 999_999_999);\n}\n\n/**\n * Make an object or array recursively immutable.\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze.\n */\nexport function deepFreeze<T>(data: T): T {\n\t// Retrieve the property names defined on object.\n\tconst propNames = Reflect.ownKeys(data as Record<string, unknown>);\n\n\t// Freeze properties before freezing self.\n\tfor (const name of propNames) {\n\t\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t\tconst value = (data as any)[name];\n\n\t\tif ((value && typeof value === 'object') || typeof value === 'function') {\n\t\t\tdeepFreeze(value);\n\t\t}\n\t}\n\n\treturn Object.freeze(data);\n}\n"
  },
  {
    "path": "npm-scripts.mjs",
    "content": "import * as process from 'node:process';\nimport * as os from 'node:os';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport fetch from 'node-fetch';\nimport * as tar from 'tar';\nimport pkg from './package.json' with { type: 'json' };\n\nconst IS_WINDOWS = os.platform() === 'win32';\nconst MAYOR_VERSION = pkg.version.split('.')[0];\nconst PYTHON = getPython();\nconst PIP_INVOKE_DIR = path.resolve('worker/pip_invoke');\nconst WORKER_RELEASE_DIR = 'worker/out/Release';\nconst WORKER_RELEASE_BIN = IS_WINDOWS\n\t? 'mediasoup-worker.exe'\n\t: 'mediasoup-worker';\nconst WORKER_RELEASE_BIN_PATH = `${WORKER_RELEASE_DIR}/${WORKER_RELEASE_BIN}`;\nconst WORKER_PREBUILD_DIR = 'worker/prebuild';\nconst GH_OWNER = 'versatica';\nconst GH_REPO = 'mediasoup';\n\n// Paths for ESLint to check.\nconst ESLINT_PATHS = [\n\t'eslint.config.mjs',\n\t'jest.config.mjs',\n\t'knip.config.mjs',\n\t'node/src',\n\t'npm-scripts.mjs',\n\t'worker/scripts',\n];\n\n// Paths for ESLint to ignore.\nconst ESLINT_IGNORE_PATHS = ['node/src/fbs'];\n\n// Paths for Prettier to check/write.\n// NOTE: Prettier ignores paths in .gitignore so we don't need to care about\n// node/src/fbs.\nconst PRETTIER_PATHS = [\n\t'CHANGELOG.md',\n\t'CONTRIBUTING.md',\n\t'README.md',\n\t'doc',\n\t'eslint.config.mjs',\n\t'jest.config.mjs',\n\t'knip.config.mjs',\n\t'node/src',\n\t'npm-scripts.mjs',\n\t'package.json',\n\t'tsconfig.json',\n\t'worker/scripts',\n];\n\nconst task = process.argv[2];\nconst taskArgs = process.argv.slice(3).join(' ');\n\n// PYTHONPATH env must be updated now so all invoke calls below will find the\n// pip invoke module.\nif (process.env.PYTHONPATH) {\n\tif (IS_WINDOWS) {\n\t\tprocess.env.PYTHONPATH = `${PIP_INVOKE_DIR};${process.env.PYTHONPATH}`;\n\t} else {\n\t\tprocess.env.PYTHONPATH = `${PIP_INVOKE_DIR}:${process.env.PYTHONPATH}`;\n\t}\n} else {\n\tprocess.env.PYTHONPATH = PIP_INVOKE_DIR;\n}\n\nvoid run();\n\nasync function run() {\n\tlogInfo(taskArgs ? `[args:\"${taskArgs}\"]` : '');\n\n\tswitch (task) {\n\t\t// As per NPM documentation (https://docs.npmjs.com/cli/v9/using-npm/scripts)\n\t\t// `prepare` script:\n\t\t//\n\t\t// - Runs BEFORE the package is packed, i.e. during `npm publish` and\n\t\t//   `npm pack`.\n\t\t// - Runs on local `npm install` without any arguments.\n\t\t// - NOTE: If a package being installed through git contains a `prepare`\n\t\t//   script, its dependencies and devDependencies will be installed, and\n\t\t//   the `prepare` script will be run, before the package is packaged and\n\t\t//   installed.\n\t\t//\n\t\t// So here we generate flatbuffers definitions for TypeScript and compile\n\t\t// TypeScript to JavaScript.\n\t\tcase 'prepare': {\n\t\t\tawait flatcNode();\n\t\t\tbuildTypescript({ force: false });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'postinstall': {\n\t\t\t// If the user/app provides us with a custom mediasoup-worker binary then\n\t\t\t// don't do anything.\n\t\t\tif (process.env.MEDIASOUP_WORKER_BIN) {\n\t\t\t\tlogInfo('MEDIASOUP_WORKER_BIN environment variable given, skipping');\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// If MEDIASOUP_LOCAL_DEV is given, or if MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD\n\t\t\t// env is given, or if mediasoup package is being installed via git+ssh\n\t\t\t// (instead of via npm), and if MEDIASOUP_FORCE_PREBUILT_WORKER_DOWNLOAD\n\t\t\t// env is not set, then skip mediasoup-worker prebuilt download.\n\t\t\telse if (\n\t\t\t\t(process.env.MEDIASOUP_LOCAL_DEV ||\n\t\t\t\t\tprocess.env.MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD ||\n\t\t\t\t\tprocess.env.npm_package_resolved?.startsWith('git+ssh://')) &&\n\t\t\t\t!process.env.MEDIASOUP_FORCE_WORKER_PREBUILT_DOWNLOAD\n\t\t\t) {\n\t\t\t\tlogInfo(\n\t\t\t\t\t'skipping mediasoup-worker prebuilt download, building it locally'\n\t\t\t\t);\n\n\t\t\t\tbuildWorker();\n\n\t\t\t\tif (!process.env.MEDIASOUP_LOCAL_DEV) {\n\t\t\t\t\tcleanWorkerArtifacts();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Attempt to download a prebuilt binary. Fallback to building locally.\n\t\t\telse if (!(await downloadPrebuiltWorker())) {\n\t\t\t\tlogInfo(\n\t\t\t\t\t`couldn't fetch any mediasoup-worker prebuilt binary, building it locally`\n\t\t\t\t);\n\n\t\t\t\tbuildWorker();\n\n\t\t\t\tif (!process.env.MEDIASOUP_LOCAL_DEV) {\n\t\t\t\t\tcleanWorkerArtifacts();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'typescript:build': {\n\t\t\tbuildTypescript({ force: true });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'typescript:watch': {\n\t\t\twatchTypescript();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'worker:build': {\n\t\t\tbuildWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'worker:prebuild-name': {\n\t\t\tgetWorkerPrebuildTarName();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'worker:prebuild': {\n\t\t\tawait prebuildWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'lint:node': {\n\t\t\tlintNode();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'lint:worker': {\n\t\t\tlintWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'format:node': {\n\t\t\tformatNode();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'format:worker': {\n\t\t\tformatWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'tidy:worker': {\n\t\t\ttidyWorker({ fix: false });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'tidy:worker:fix': {\n\t\t\ttidyWorker({ fix: true });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'flatc:node': {\n\t\t\tawait flatcNode();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'flatc:worker': {\n\t\t\tflatcWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'test:node': {\n\t\t\ttestNode();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'test:worker': {\n\t\t\ttestWorker();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'coverage:node': {\n\t\t\tcoverageNode();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'release:check': {\n\t\t\tawait checkRelease();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'release': {\n\t\t\tawait release();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault: {\n\t\t\tlogError('unknown task');\n\n\t\t\texitWithError();\n\t\t}\n\t}\n}\n\nfunction getPython() {\n\tlet python = process.env.PYTHON;\n\n\tif (!python) {\n\t\ttry {\n\t\t\texecSync('python3 --version', { stdio: ['ignore', 'ignore', 'ignore'] });\n\n\t\t\tpython = 'python3';\n\t\t} catch (error) {\n\t\t\tpython = 'python';\n\t\t}\n\t}\n\n\treturn python;\n}\n\nfunction getWorkerPrebuildTarName() {\n\tlet workerPrebuildTarName = `mediasoup-worker-${pkg.version}-${os.platform()}-${os.arch()}`;\n\n\t// In Linux we want to know about kernel version since kernel >= 6 supports\n\t// io-uring.\n\tif (os.platform() === 'linux') {\n\t\tconst kernelMajorVersion = Number(os.release().split('.')[0]);\n\n\t\tworkerPrebuildTarName += `-kernel${kernelMajorVersion}`;\n\t}\n\n\tworkerPrebuildTarName = `${workerPrebuildTarName}.tgz`;\n\n\tlogInfo(\n\t\t`getWorkerPrebuildTarName() [workerPrebuildTarName:${workerPrebuildTarName}]`\n\t);\n\n\treturn workerPrebuildTarName;\n}\n\nfunction installInvoke() {\n\tif (fs.existsSync(PIP_INVOKE_DIR)) {\n\t\treturn;\n\t}\n\n\tlogInfo('installInvoke()');\n\n\t// Install pip invoke into custom location, so we don't depend on system-wide\n\t// installation.\n\texecuteCmd(\n\t\t`\"${PYTHON}\" -m pip install --upgrade --no-user --target \"${PIP_INVOKE_DIR}\" invoke`\n\t);\n}\n\nfunction deleteNodeLib() {\n\tif (!fs.existsSync('node/lib')) {\n\t\treturn;\n\t}\n\n\tlogInfo('deleteNodeLib()');\n\n\tfs.rmSync('node/lib', { recursive: true, force: true });\n}\n\nfunction buildTypescript({ force }) {\n\tif (!force && fs.existsSync('node/lib')) {\n\t\treturn;\n\t}\n\n\tlogInfo(`buildTypescript() [force:${force}]`);\n\n\tdeleteNodeLib();\n\n\texecuteCmd(`tsc ${taskArgs}`);\n}\n\nfunction watchTypescript() {\n\tlogInfo('watchTypescript()');\n\n\tdeleteNodeLib();\n\n\texecuteCmd(`tsc --watch ${taskArgs}`);\n}\n\nfunction buildWorker() {\n\tlogInfo('buildWorker()');\n\n\tinstallInvoke();\n\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker mediasoup-worker`);\n}\n\nfunction cleanWorkerArtifacts() {\n\tlogInfo('cleanWorkerArtifacts()');\n\n\tinstallInvoke();\n\n\t// Clean build artifacts except `mediasoup-worker`.\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker clean-build`);\n\n\t// Clean downloaded dependencies.\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker clean-subprojects`);\n\n\t// Clean PIP/Meson/Ninja.\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker clean-pip`);\n}\n\nfunction lintNode() {\n\tlogInfo('lintNode()');\n\n\t// Ensure there are no rules that are unnecessary or conflict with Prettier\n\t// rules.\n\texecuteCmd('eslint-config-prettier eslint.config.mjs');\n\n\tconst eslintIgnorePatternArgs = ESLINT_IGNORE_PATHS.map(\n\t\tentry => `--ignore-pattern ${entry}`\n\t).join(' ');\n\tconst eslintFiles = ESLINT_PATHS.join(' ');\n\n\texecuteCmd(\n\t\t`eslint -c eslint.config.mjs --max-warnings 0 ${eslintIgnorePatternArgs} ${eslintFiles}`\n\t);\n\n\tconst prettierFiles = PRETTIER_PATHS.join(' ');\n\n\texecuteCmd(`prettier --check ${prettierFiles}`);\n\n\texecuteCmd('knip --config knip.config.mjs --treat-config-hints-as-errors');\n}\n\nfunction lintWorker() {\n\tlogInfo('lintWorker()');\n\n\tinstallInvoke();\n\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker lint`);\n}\n\nfunction formatNode() {\n\tlogInfo('formatNode()');\n\n\tconst prettierFiles = PRETTIER_PATHS.join(' ');\n\n\texecuteCmd(`prettier --write ${prettierFiles}`);\n}\n\nfunction formatWorker() {\n\tlogInfo('formatWorker()');\n\n\tinstallInvoke();\n\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker format`);\n}\n\nfunction tidyWorker({ fix }) {\n\tlogInfo(`tidyWorker() [fix:${fix}]`);\n\n\tinstallInvoke();\n\n\tif (fix) {\n\t\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker tidy-fix`);\n\t} else {\n\t\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker tidy`);\n\t}\n}\n\nasync function flatcNode() {\n\tlogInfo('flatcNode()');\n\n\t// NOTE: Load dep on demand since it's a devDependency.\n\tconst ini = await import('ini');\n\n\tinstallInvoke();\n\n\t// Build flatc if needed.\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker flatc`);\n\n\tconst buildType = process.env.MEDIASOUP_BUILDTYPE || 'Release';\n\tconst extension = IS_WINDOWS ? '.exe' : '';\n\tconst flatbuffersWrapFilePath = path.join(\n\t\t'worker',\n\t\t'subprojects',\n\t\t'flatbuffers.wrap'\n\t);\n\tconst flatbuffersWrap = ini.parse(\n\t\tfs.readFileSync(flatbuffersWrapFilePath, {\n\t\t\tencoding: 'utf-8',\n\t\t})\n\t);\n\tconst flatbuffersDir = flatbuffersWrap['wrap-file']['directory'];\n\n\tconst flatc = path.resolve(\n\t\tpath.join(\n\t\t\t'worker',\n\t\t\t'out',\n\t\t\tbuildType,\n\t\t\t'build',\n\t\t\t'subprojects',\n\t\t\tflatbuffersDir,\n\t\t\t`flatc${extension}`\n\t\t)\n\t);\n\n\tconst out = path.resolve(path.join('node', 'src'));\n\n\tfor (const dirent of fs.readdirSync(path.join('worker', 'fbs'), {\n\t\twithFileTypes: true,\n\t})) {\n\t\tif (!dirent.isFile() || path.parse(dirent.name).ext !== '.fbs') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst filePath = path.resolve(path.join('worker', 'fbs', dirent.name));\n\n\t\texecuteCmd(\n\t\t\t`\"${flatc}\" --ts --ts-no-import-ext --gen-object-api -o \"${out}\" \"${filePath}\"`\n\t\t);\n\t}\n}\n\nfunction flatcWorker() {\n\tlogInfo('flatcWorker()');\n\n\tinstallInvoke();\n\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker flatc`);\n}\n\nfunction testNode() {\n\tlogInfo('testNode()');\n\n\texecuteCmd(`jest --silent false --detectOpenHandles ${taskArgs}`);\n}\n\nfunction testWorker() {\n\tlogInfo('testWorker()');\n\n\tinstallInvoke();\n\n\texecuteCmd(`\"${PYTHON}\" -m invoke -r worker test`);\n}\n\nfunction coverageNode() {\n\tlogInfo('coverageNode()');\n\n\texecuteCmd(`jest --coverage ${taskArgs}`);\n\texecuteCmd('open-cli coverage/lcov-report/index.html');\n}\n\nfunction installNodeDeps() {\n\tlogInfo('installNodeDeps()');\n\n\t// Install/update Node deps.\n\texecuteCmd('npm ci --ignore-scripts');\n\n\t// Update package-lock.json.\n\texecuteCmd('npm install --package-lock-only --ignore-scripts');\n\n\t// Check vulnerabilities in deps.\n\texecuteCmd('npm audit --omit dev');\n\texecuteCmd('npm audit --prefix worker/scripts');\n}\n\nasync function checkRelease() {\n\tlogInfo('checkRelease()');\n\n\tinstallNodeDeps();\n\tawait flatcNode();\n\tbuildTypescript({ force: true });\n\tbuildWorker();\n\tlintNode();\n\tlintWorker();\n\ttestNode();\n\ttestWorker();\n}\n\nasync function release() {\n\tlogInfo('release()');\n\n\tlet octokit;\n\tlet versionChanges;\n\n\ttry {\n\t\toctokit = await getOctokit();\n\t\tversionChanges = await getVersionChanges();\n\t} catch (error) {\n\t\tlogError(error.message);\n\n\t\texitWithError();\n\t}\n\n\tawait checkRelease();\n\texecuteCmd(`git commit -am '${pkg.version}'`);\n\texecuteCmd(`git tag -a ${pkg.version} -m '${pkg.version}'`);\n\texecuteCmd(`git push origin v${MAYOR_VERSION}`);\n\texecuteCmd(`git push origin '${pkg.version}'`);\n\n\tlogInfo('creating release in GitHub');\n\n\tawait octokit.repos.createRelease({\n\t\towner: GH_OWNER,\n\t\trepo: GH_REPO,\n\t\tname: pkg.version,\n\t\tbody: versionChanges,\n\t\ttag_name: pkg.version,\n\t\tdraft: false,\n\t});\n\n\texecuteInteractiveCmd('npm publish');\n}\n\nfunction ensureDir(dir) {\n\tlogInfo(`ensureDir() [dir:${dir}]`);\n\n\tif (!fs.existsSync(dir)) {\n\t\tfs.mkdirSync(dir, { recursive: true });\n\t}\n}\n\nasync function prebuildWorker() {\n\tlogInfo('prebuildWorker()');\n\n\tensureDir(WORKER_PREBUILD_DIR);\n\n\tconst workerPrebuildTar = getWorkerPrebuildTarName();\n\tconst workerPrebuildTarPath = `${WORKER_PREBUILD_DIR}/${workerPrebuildTar}`;\n\n\ttry {\n\t\tawait new Promise((resolve, reject) => {\n\t\t\t// Generate a gzip file which just contains mediasoup-worker binary\n\t\t\t// without any folder.\n\t\t\ttar\n\t\t\t\t.create(\n\t\t\t\t\t{\n\t\t\t\t\t\tcwd: WORKER_RELEASE_DIR,\n\t\t\t\t\t\tgzip: true,\n\t\t\t\t\t\tstrict: true,\n\t\t\t\t\t},\n\t\t\t\t\t[WORKER_RELEASE_BIN]\n\t\t\t\t)\n\t\t\t\t// This is needed for the case in which tar.create() fails before\n\t\t\t\t// invoking pipe() on its result.\n\t\t\t\t.on('error', reject)\n\t\t\t\t.pipe(fs.createWriteStream(workerPrebuildTarPath))\n\t\t\t\t.on('finish', resolve)\n\t\t\t\t.on('error', reject);\n\t\t});\n\t} catch (error) {\n\t\tlogError(\n\t\t\t'prebuildWorker() | failed to create mediasoup-worker prebuilt tar file:',\n\t\t\terror\n\t\t);\n\n\t\texitWithError();\n\t}\n}\n\n// Returns a Promise resolving to true if a mediasoup-worker prebuilt binary\n// was downloaded and uncompressed, false otherwise.\nasync function downloadPrebuiltWorker() {\n\tconst releaseBase =\n\t\tprocess.env.MEDIASOUP_WORKER_PREBUILT_DOWNLOAD_BASE_URL ||\n\t\t`${pkg.repository.url\n\t\t\t.replace(/^git\\+/, '')\n\t\t\t.replace(/\\.git$/, '')}/releases/download`;\n\n\tconst workerPrebuildTar = getWorkerPrebuildTarName();\n\tconst workerPrebuildTarUrl = `${releaseBase}/${pkg.version}/${workerPrebuildTar}`;\n\n\tlogInfo(\n\t\t`downloadPrebuiltWorker() [workerPrebuildTarUrl:${workerPrebuildTarUrl}]`\n\t);\n\n\tensureDir(WORKER_PREBUILD_DIR);\n\n\tlet res;\n\n\ttry {\n\t\tres = await fetch(workerPrebuildTarUrl);\n\n\t\tif (res.status === 404) {\n\t\t\tlogInfo(\n\t\t\t\t'downloadPrebuiltWorker() | no available mediasoup-worker prebuilt binary for current architecture'\n\t\t\t);\n\n\t\t\treturn false;\n\t\t} else if (!res.ok) {\n\t\t\tlogError(\n\t\t\t\t`downloadPrebuiltWorker() | failed to download mediasoup-worker prebuilt binary: ${res.status} ${res.statusText}`\n\t\t\t);\n\n\t\t\treturn false;\n\t\t}\n\t} catch (error) {\n\t\tlogError(\n\t\t\t`downloadPrebuiltWorker() | failed to download mediasoup-worker prebuilt binary: ${error}`\n\t\t);\n\n\t\treturn false;\n\t}\n\n\tensureDir(WORKER_RELEASE_DIR);\n\n\treturn new Promise(resolve => {\n\t\t// Extract mediasoup-worker in the official mediasoup-worker path.\n\t\tres.body\n\t\t\t.pipe(\n\t\t\t\ttar.extract({\n\t\t\t\t\tcwd: WORKER_RELEASE_DIR,\n\t\t\t\t\tnewer: false,\n\t\t\t\t\tstrict: true,\n\t\t\t\t})\n\t\t\t)\n\t\t\t.on('finish', () => {\n\t\t\t\tlogInfo(\n\t\t\t\t\t'downloadPrebuiltWorker() | got mediasoup-worker prebuilt binary'\n\t\t\t\t);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Give execution permission to the binary.\n\t\t\t\t\tfs.chmodSync(WORKER_RELEASE_BIN_PATH, 0o775);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogWarn(\n\t\t\t\t\t\t`downloadPrebuiltWorker() | failed to give execution permissions to the mediasoup-worker prebuilt binary: ${error}`\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Let's confirm that the fetched mediasoup-worker prebuit binary does\n\t\t\t\t// run in current host. This is to prevent weird issues related to\n\t\t\t\t// different versions of libc in the system and so on.\n\t\t\t\t// So run mediasoup-worker without the required MEDIASOUP_VERSION env\n\t\t\t\t// and expect exit code 41 (see main.cpp).\n\n\t\t\t\tlogInfo(\n\t\t\t\t\t'downloadPrebuiltWorker() | checking fetched mediasoup-worker prebuilt binary in current host'\n\t\t\t\t);\n\n\t\t\t\ttry {\n\t\t\t\t\tconst resolvedBinPath = path.resolve(WORKER_RELEASE_BIN_PATH);\n\n\t\t\t\t\t// This will always fail on purpose, but if status code is 41 then\n\t\t\t\t\t// it's good.\n\t\t\t\t\texecSync(`\"${resolvedBinPath}\"`, {\n\t\t\t\t\t\tstdio: ['ignore', 'ignore', 'ignore'],\n\t\t\t\t\t\t// Ensure no env is passed to avoid accidents.\n\t\t\t\t\t\tenv: {},\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error.status === 41) {\n\t\t\t\t\t\tlogInfo(\n\t\t\t\t\t\t\t'downloadPrebuiltWorker() | fetched mediasoup-worker prebuilt binary is valid for current host'\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tresolve(true);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogError(\n\t\t\t\t\t\t\t`downloadPrebuiltWorker() | fetched mediasoup-worker prebuilt binary fails to run in this host [status:${error.status}]`\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tfs.unlinkSync(WORKER_RELEASE_BIN_PATH);\n\t\t\t\t\t\t} catch (error2) {}\n\n\t\t\t\t\t\tresolve(false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\t.on('error', error => {\n\t\t\t\tlogError(\n\t\t\t\t\t`downloadPrebuiltWorker() | failed to extract downloaded mediasoup-worker prebuilt binary:`,\n\t\t\t\t\terror\n\t\t\t\t);\n\n\t\t\t\tresolve(false);\n\t\t\t});\n\t});\n}\n\nasync function getOctokit() {\n\tif (!process.env.GITHUB_TOKEN) {\n\t\tthrow new Error('missing GITHUB_TOKEN environment variable');\n\t}\n\n\t// NOTE: Load dep on demand since it's a devDependency.\n\tconst { Octokit } = await import('@octokit/rest');\n\n\tconst octokit = new Octokit({\n\t\tauth: process.env.GITHUB_TOKEN,\n\t});\n\n\treturn octokit;\n}\n\nasync function getVersionChanges() {\n\tlogInfo('getVersionChanges()');\n\n\t// NOTE: Load dep on demand since it's a devDependency.\n\tconst marked = await import('marked');\n\n\tconst changelog = fs.readFileSync('./CHANGELOG.md', { encoding: 'utf-8' });\n\tconst entries = marked.lexer(changelog);\n\n\tfor (let idx = 0; idx < entries.length; ++idx) {\n\t\tconst entry = entries[idx];\n\n\t\tif (entry.type === 'heading' && entry.text === pkg.version) {\n\t\t\tconst changes = entries[idx + 1].raw;\n\n\t\t\treturn changes;\n\t\t}\n\t}\n\n\t// This should not happen (unless author forgot to update CHANGELOG).\n\tthrow new Error(\n\t\t`no entry found in CHANGELOG.md for version '${pkg.version}'`\n\t);\n}\n\nfunction executeCmd(command) {\n\tlogInfo(`executeCmd(): ${command}`);\n\n\ttry {\n\t\texecSync(command, { stdio: ['ignore', process.stdout, process.stderr] });\n\t} catch (error) {\n\t\tlogError(`executeCmd() failed, exiting: ${error}`);\n\n\t\texitWithError();\n\t}\n}\n\nfunction executeInteractiveCmd(command) {\n\tlogInfo(`executeInteractiveCmd(): ${command}`);\n\n\ttry {\n\t\texecSync(command, { stdio: 'inherit', env: process.env });\n\t} catch (error) {\n\t\tlogError(`executeInteractiveCmd() failed, exiting: ${error}`);\n\n\t\texitWithError();\n\t}\n}\n\nfunction logInfo(...args) {\n\t// eslint-disable-next-line no-console\n\tconsole.log(`npm-scripts.mjs \\x1b[36m[INFO] [${task}]\\x1b[0m`, ...args);\n}\n\nfunction logWarn(...args) {\n\t// eslint-disable-next-line no-console\n\tconsole.warn(`npm-scripts.mjs \\x1b[33m[WARN] [${task}]\\x1b\\0m`, ...args);\n}\n\nfunction logError(...args) {\n\t// eslint-disable-next-line no-console\n\tconsole.error(`npm-scripts.mjs \\x1b[31m[ERROR] [${task}]\\x1b[0m`, ...args);\n}\n\nfunction exitWithError() {\n\tprocess.exit(1);\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n\t\"name\": \"mediasoup\",\n\t\"version\": \"3.19.22\",\n\t\"description\": \"Cutting Edge WebRTC Video Conferencing\",\n\t\"contributors\": [\n\t\t\"Iñaki Baz Castillo <ibc@aliax.net> (https://inakibaz.me)\",\n\t\t\"José Luis Millán <jmillan@aliax.net> (https://github.com/jmillan)\",\n\t\t\"Nazar Mokynskyi (https://github.com/nazar-pc)\"\n\t],\n\t\"license\": \"ISC\",\n\t\"homepage\": \"https://mediasoup.org\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"git+https://github.com/versatica/mediasoup.git\"\n\t},\n\t\"funding\": {\n\t\t\"type\": \"opencollective\",\n\t\t\"url\": \"https://opencollective.com/mediasoup\"\n\t},\n\t\"main\": \"node/lib/index.js\",\n\t\"types\": \"node/lib/index.d.ts\",\n\t\"exports\": {\n\t\t\".\": {\n\t\t\t\"types\": \"./node/lib/index.d.ts\",\n\t\t\t\"default\": \"./node/lib/index.js\"\n\t\t},\n\t\t\"./types\": {\n\t\t\t\"types\": \"./node/lib/types.d.ts\",\n\t\t\t\"default\": \"./node/lib/types.js\"\n\t\t},\n\t\t\"./ortc\": {\n\t\t\t\"types\": \"./node/lib/ortc.d.ts\",\n\t\t\t\"default\": \"./node/lib/ortc.js\"\n\t\t},\n\t\t\"./extras\": {\n\t\t\t\"types\": \"./node/lib/extras.d.ts\",\n\t\t\t\"default\": \"./node/lib/extras.js\"\n\t\t}\n\t},\n\t\"files\": [\n\t\t\"LICENSE\",\n\t\t\"README.md\",\n\t\t\"node/lib\",\n\t\t\"npm-scripts.mjs\",\n\t\t\"worker/Makefile\",\n\t\t\"worker/deps/libwebrtc\",\n\t\t\"worker/fbs\",\n\t\t\"worker/fuzzer/include\",\n\t\t\"worker/fuzzer/src\",\n\t\t\"worker/include\",\n\t\t\"worker/mocks/include\",\n\t\t\"worker/mocks/src\",\n\t\t\"worker/src\",\n\t\t\"worker/scripts/*.json\",\n\t\t\"worker/scripts/*.mjs\",\n\t\t\"worker/scripts/*.py\",\n\t\t\"worker/scripts/*.sh\",\n\t\t\"worker/subprojects/*.wrap\",\n\t\t\"worker/test/include\",\n\t\t\"worker/test/src\",\n\t\t\"worker/meson.build\",\n\t\t\"worker/meson_options.txt\",\n\t\t\"worker/tasks.py\"\n\t],\n\t\"engines\": {\n\t\t\"node\": \">=22\"\n\t},\n\t\"keywords\": [\n\t\t\"webrtc\",\n\t\t\"ortc\",\n\t\t\"sfu\",\n\t\t\"nodejs\"\n\t],\n\t\"scripts\": {\n\t\t\"prepare\": \"node npm-scripts.mjs prepare\",\n\t\t\"postinstall\": \"node npm-scripts.mjs postinstall\",\n\t\t\"typescript:build\": \"node npm-scripts.mjs typescript:build\",\n\t\t\"typescript:watch\": \"node npm-scripts.mjs typescript:watch\",\n\t\t\"worker:build\": \"node npm-scripts.mjs worker:build\",\n\t\t\"worker:prebuild-name\": \"node npm-scripts.mjs worker:prebuild-name\",\n\t\t\"worker:prebuild\": \"node npm-scripts.mjs worker:prebuild\",\n\t\t\"lint\": \"node npm-scripts.mjs lint:node && node npm-scripts.mjs lint:worker\",\n\t\t\"lint:node\": \"node npm-scripts.mjs lint:node\",\n\t\t\"lint:worker\": \"node npm-scripts.mjs lint:worker\",\n\t\t\"format\": \"node npm-scripts.mjs format:node && node npm-scripts.mjs format:worker\",\n\t\t\"format:node\": \"node npm-scripts.mjs format:node\",\n\t\t\"format:worker\": \"node npm-scripts.mjs format:worker\",\n\t\t\"tidy:worker\": \"node npm-scripts.mjs tidy:worker\",\n\t\t\"tidy:worker:fix\": \"node npm-scripts.mjs tidy:worker:fix\",\n\t\t\"flatc\": \"node npm-scripts.mjs flatc:node && node npm-scripts.mjs flatc:worker\",\n\t\t\"flatc:node\": \"node npm-scripts.mjs flatc:node\",\n\t\t\"flatc:worker\": \"node npm-scripts.mjs flatc:worker\",\n\t\t\"test\": \"node npm-scripts.mjs test:node && node npm-scripts.mjs test:worker\",\n\t\t\"test:node\": \"node npm-scripts.mjs test:node\",\n\t\t\"test:worker\": \"node npm-scripts.mjs test:worker\",\n\t\t\"coverage\": \"node npm-scripts.mjs coverage:node\",\n\t\t\"coverage:node\": \"node npm-scripts.mjs coverage:node\",\n\t\t\"release:check\": \"node npm-scripts.mjs release:check\",\n\t\t\"release\": \"node npm-scripts.mjs release\"\n\t},\n\t\"dependencies\": {\n\t\t\"debug\": \"^4.4.3\",\n\t\t\"flatbuffers\": \"^25.9.23\",\n\t\t\"h264-profile-level-id\": \"^2.3.2\",\n\t\t\"node-fetch\": \"^3.3.2\",\n\t\t\"supports-color\": \"^10.2.2\",\n\t\t\"tar\": \"^7.5.15\"\n\t},\n\t\"devDependencies\": {\n\t\t\"@eslint/js\": \"^10.0.1\",\n\t\t\"@octokit/rest\": \"^22.0.1\",\n\t\t\"@types/debug\": \"^4.1.13\",\n\t\t\"@types/ini\": \"^4.1.1\",\n\t\t\"@types/jest\": \"^30.0.0\",\n\t\t\"@types/node\": \"^25.6.2\",\n\t\t\"eslint\": \"^10.3.0\",\n\t\t\"eslint-config-prettier\": \"^10.1.8\",\n\t\t\"eslint-plugin-jest\": \"^29.15.2\",\n\t\t\"eslint-plugin-prettier\": \"^5.5.5\",\n\t\t\"globals\": \"^17.6.0\",\n\t\t\"ini\": \"^6.0.0\",\n\t\t\"jest\": \"^30.4.2\",\n\t\t\"knip\": \"^6.14.0\",\n\t\t\"marked\": \"^18.0.3\",\n\t\t\"open-cli\": \"^9.0.0\",\n\t\t\"pick-port\": \"^2.2.0\",\n\t\t\"prettier\": \"^3.8.3\",\n\t\t\"ts-jest\": \"^29.4.9\",\n\t\t\"typescript\": \"^6.0.3\",\n\t\t\"typescript-eslint\": \"^8.59.3\",\n\t\t\"werift-sctp\": \"^0.0.11\"\n\t}\n}\n"
  },
  {
    "path": "rust/CHANGELOG.md",
    "content": "# Changelog\n\n### NEXT\n\n- Worker: Add `use_built_in_sctp_stack` setting (defaults to `false`) to enable mediasoup built-in SCTP stack (PR #1777).\n\n### 0.21.0\n\n- `router.pipe_producer_to_router()` and `router.pipe_data_producer_to_router()` can now connect two `Routers` in the same `Worker` if `keep_id` is set to `false` (PR #1604).\n- Updates from mediasoup TypeScript `3.18.1.=3.19.0`.\n- Ensure that the order of acquiring the `paused` and `producer_paused` locks in `consumer.rs` is consistent at all times to avoid deadlock (PR #1605).\n- Convert `WORKER_CLOSE` into a notification (PR #1729).\n\n### 0.20.0\n\n- Make `parameters` and `rtcp_feedback` optional in `RtpCodecParameters` and `RtpCodecCapability` during deserialization (PR #1597).\n- Make codec `mime_type` case insensitive during deserialization (PR #1599).\n- Only expose `data_structures`, `rtp_parameters`, `sctp_parameters` and `srtp_parameters` through the `mediasoup-types` crate (PR #1600).\n\n### 0.19.1\n\n- Fix installation in paths with spaces (PR #1596).\n- Updates from mediasoup TypeScript `3.17.1.=3.18.1`.\n\n### 0.19.0\n\n- Enable AV1 codec (PR #1563).\n- Remove H265 codec and deprecated frame-marking RTP extension (PR #1564).\n- Remove H264-SVC codec (PR #1568).\n- Add `Router::update_media_codecs()` to dynamically change Router's RTP capabilities (#1571).\n- `TransportListenInfo`: Add `expose_internal_ip` which, if set to `true` and `announced_address` is set, exposes an additional ICE candidate in `WebRtcTransport` whose IP is `listen_info.ip` rather than `listen_info.announced_address` (PR #1583).\n- Updates from mediasoup TypeScript `3.14.11.=3.17.0`.\n\n### 0.18.2\n\n- Don't log error if `close()` on an object fails because channel is closed already (PR #1560).\n- General mediasoup changes:\n  - Sign self generated DTLS certificate with SHA256 (PR #1450).\n  - `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears (PR #1459).\n  - Worker: Fix crash when using colliding `portRange` values in different transports (PR #1469).\n  - Worker: Drop VP8 packets with a higher temporal layer than the current one (PR #1009).\n  - Fix the problem of the TCC package being omitted from being sent (PR #1492).\n  - `Consumer`: Fix sequence number gap (PR #1494).\n  - Fix VP9 out of order packets forwarding (PR #1486).\n  - Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` (PR #1516).\n  - Worker: Fix encode retransmitted packets with the corresponding data (PR #1527).\n  - `SvcConsumer`: Fix K-SVC bitrate in `IncreaseLayer()` method (PR #1535).\n  - `Consumer` classes: Only drop packets in RTP sequence manager when they belong to current spatial layer (PR #1549).\n  - `Consumer` classes: Add target layer retransmission buffer to avoid PLIs/FIRs when RTP packets containing a key frame arrive out of order (PR #1550 and PR #1558).\n\n### 0.18.1\n\n- FBS: Provide proper data upon panic (#1523).\n\n### 0.18.0\n\n- Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` (PR #1516).\n- New enum variant was added in 0.17.2.\n\n### 0.17.2\n\n- Fix `PipeConsumer::get_stats()` (PR #1511).\n\n### 0.17.1\n\n- Update Rust toolchain channel to version 1.79.0 (PR #1409).\n- Updates from mediasoup TypeScript `3.14.7..=3.14.10`.\n- General mediasoup changes:\n  - Worker: Add `enable_liburing` boolean option (`true` by default) to disable `io_uring` even if it's supported by the prebuilt `mediasoup-worker` and by current host (PR #1442).\n\n### 0.17.0\n\n- Updates from mediasoup TypeScript `3.13.18..=3.14.6`.\n- General mediasoup changes:\n  - Worker: Fix crash when closing `WebRtcServer` with active `WebRtcTransports` (PR #1390).\n  - `Worker: Fix memory leak when using `WebRtcServer` with TCP enabled (PR #1389).\n  - OPUS: Fix DTX detection (PR #1357).\n  - `TransportListenInfo`: Add `portRange` (deprecate worker port range) (PR #1365).\n  - Update worker FlatBuffers to 24.3.6-1 (fix cannot set temporal layer 0) (PR #1348).\n  - Fix DTLS packets do not honor configured DTLS MTU (attempt 3) (PR #1345).\n  - Add server side ICE consent checks to detect silent WebRTC disconnections (PR #1332).\n  - `TransportListenInfo`: \"announced ip\" can also be a hostname (PR #1322).\n  - `TransportListenInfo`: Rename \"announced ip\" to \"announced address\" (PR #1324).\n\n### 0.16.0\n\n- Updates from mediasoup TypeScript `3.13.13..=3.13.17`.\n- General mediasoup changes:\n  - `TransportListenInfo.announced_ip` can also be a hostname (PR #1322).\n  - `TransportListenInfo.announced_ip` is now `announced_address`, `IceCandidate.ip` is now `IceCandidate.address` and `TransportTuple.local_ip` is not `TransportTuple.local_address` (PR #1324).\n\n### 0.15.0\n\n- Expose DataChannel string message as binary (PR #1289).\n\n### 0.14.0\n\n- Updates from mediasoup TypeScript `3.13.8..=3.13.12`.\n- Update h264-profile-level-id dependency to 0.2.0.\n- Fix docs build (PR #1271).\n- Rename `data_consumer::on_producer_resume` to `data_consumer::on_data_producer_resume` (PR #1271).\n\n### 0.13.0\n\n- Updates from mediasoup TypeScript `3.13.0..=3.13.7`.\n- General mediasoup changes:\n  - Switch from JSON based messages to `flatbuffers` (PR #1064).\n  - Enable `liburing` usage for Linux (kernel versions >= 6) (PR #1218).\n  - Add pause/resume API in `DataProducer` and `DataConsumer` (PR #1104).\n  - DataChannel subchannels feature (PR #1152).\n  - `Worker`: Make DTLS fragment stay within MTU size range (PR #1156).\n  - Replace make + Makefile with Python Invoke library + tasks.py (also fix installation under path with whitespaces) (PR #1239).\n\n### 0.12.0\n\n- Updates from mediasoup TypeScript `3.11.9..=3.12.16`.\n\n### 0.11.4\n\n- Fix consuming data producer from direct transport by data consumer on non-direct transport.\n\n### 0.11.3\n\n- Updates from mediasoup TypeScript `3.11.3..=3.11.8`.\n\n### 0.11.2\n\n- Updates from mediasoup TypeScript `3.10.11..=3.11.2`.\n\n### 0.11.1\n\n- Updates from mediasoup TypeScript `3.10.7..=3.10.10`.\n\n### 0.11.0\n\n- Updates from mediasoup TypeScript `3.10.2..=3.10.6`.\n\n### 0.10.0\n\n- Updates from mediasoup TypeScript `3.9.10..=3.10.1`.\n- `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port (PR #834, PR #845).\n- Minor API breaking changes.\n\n### 0.9.3\n\n- Fix a segfaults in tests and under multithreaded executor.\n- Fix another racy deadlock situation.\n- Expose hierarchical dependencies of ownership of Rust data structures, now it is possible to call `consumer.transport().router().worker().worker_manager()`.\n- General mediasoup changes:\n  - ICE renomination support (PR #756).\n  - Update `libuv` to 1.43.0.\n  - TCC client optimizations for faster and more stable BWE (PR #712 by @ggarber).\n  - Added support for RTP abs-capture-time header (PR #761 by @oto313).\n  - Fix VP9 kSVC forwarding logic to not forward lower unneded layers (PR #778 by @ggarber).\n  - Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate (PR #779 by @ggarber).\n  - Optimize RTP header extension handling (PR #786).\n  - `RateCalculator`: Reset optimization (PR #785).\n  - Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` (PR #788, thanks to @ggarber for exposing this issue in PR #787).\n\n### 0.9.2\n\n- Update `lru` dependency to fix security vulnerability\n\n### 0.9.1\n\n- Fix cleanup of build artifacts.\n- Make `Transport` implement `Send`.\n- Another fix to rare deadlock.\n- Improved Windows support (doesn't require MSVS activation).\n\n### 0.9.0\n\n- Fix for receiving data over payload channel.\n- Support thread initializer function for worker threads, can be used for pinning worker threads to CPU cores.\n- Significant worker communication optimizations (especially latency).\n- Switch from file descriptors to function calls when communicating with worker.\n- Various optimizations that caused minor breaking changes to public API.\n- Requests no longer have internal timeout, but they can now be cancelled, add your own timeouts on top if needed.\n- Windows support.\n- General mediasoup changes:\n  - Replaces GYP build system with fully-functional Meson build system (PR #622).\n  - `Consumer`: Modification of bitrate allocation algorithm (PR #708).\n  - Single H264/H265 codec configuration in `supportedRtpCapabilities` (PR #718).\n\n### 0.8.5\n\n- Fix types for `round_trip_time` and `bitrate_by_layer` fields `ProducerStat` and `ConsumerStat`.\n- Accumulation of worker fixes.\n\n### 0.8.4\n\n- Add Active Speaker Observer to prelude.\n- Fix consumers preventing producers from being closed (regression introduced in 0.8.3).\n\n### 0.8.3\n\n- prelude module containing traits and structs that should be sufficient for most basic mediasoup-based apps.\n- Dominant Speaker Event (PR #603 by @SteveMcFarlin).\n\n### 0.8.2\n\n- Support for optional fixed port on transports.\n\n### 0.8.1\n\n- Add convenience methods for getting information from `TransportTuple` enum, especially local IP/port.\n- Add `mid` option in `ConsumerOptions` to provide way to override MID\n- Add convenience method `ConsumerStats::consumer_stat()`.\n\n### 0.8.0\n\n- `NonClosingProducer` removed (use `PipedProducer` instead, they were identical).\n- `RtpHeaderExtensionUri::as_str()` now takes `self` instead of `&self`.\n- `kind` field of `RtpHeaderExtension` is no longer optional.\n- Refactor `ScalabilityMode` from being a string to enum, make sure layers are not zero on type system level.\n- Concrete types for info field of tracing events.\n\n### 0.7.2\n\n- Thread and memory safety fixes in mediasoup-sys.\n- macOS support.\n- `NonClosingProducer` renamed into `PipedProducer` with better docs.\n- Internal restructuring of modules for better compatibility with IDEs.\n- Feature level updated to mediasoup `3.7.6`.\n\n### 0.7.0\n\n- Switch from running C++ worker processes to worker threads using mediasoup-sys that wraps mediasoup-worker into library.\n- Simplify `WorkerManager::new()` and `WorkerManager::with_executor()` API as the result of above.\n- Support `rtxPacketsDiscarded` in `Producer` stats.\n- Enable Rust 2018 idioms warnings.\n- Make sure all public types have `Debug` implementation on them.\n- Enforce docs on public types and add missing documentation.\n- Remove `RtpCodecParametersParameters::new()` (`RtpCodecParametersParameters::default()` does the same thing).\n\n### 0.6.0\n\nInitial upstreamed release.\n"
  },
  {
    "path": "rust/Cargo.toml",
    "content": "[package]\nname = \"mediasoup\"\nversion = \"0.21.0\"\ndescription = \"Cutting Edge WebRTC Video Conferencing in Rust\"\ncategories = [\"api-bindings\", \"multimedia\", \"network-programming\"]\nauthors = [\n    \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n    \"José Luis Millán <jmillan@aliax.net>\",\n    \"Iñaki Baz Castillo <ibc@aliax.net>\"\n]\nedition = \"2021\"\nlicense = \"ISC\"\nkeywords = [\"webrtc\", \"ortc\", \"sfu\"]\ndocumentation = \"https://docs.rs/mediasoup\"\nrepository = \"https://github.com/versatica/mediasoup\"\nreadme = \"README.md\"\ninclude = [\"/benches\", \"/src\", \"/README.md\"]\n\n[package.metadata.docs.rs]\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = []\n\n[dependencies]\nasync-channel = \"1.7.1\"\nasync-executor = \"1.4.1\"\nasync-lock = \"2.6.0\"\nasync-oneshot = \"0.5.0\"\nasync-trait = \"0.1.58\"\natomic-take = \"1.0.0\"\nevent-listener-primitives = \"2.0.1\"\nfastrand = \"1.8.0\"\nfutures-lite = \"1.12.0\"\nh264-profile-level-id = \"0.2.0\"\nhash_hasher = \"2.0.3\"\nlog = \"0.4.17\"\nnohash-hasher = \"0.2.0\"\nonce_cell = \"1.16.0\"\nplanus = \"0.4.0\"\nserde_json = \"1.0.87\"\nserde_repr = \"0.1.9\"\nthiserror = \"1.0.37\"\n\n[dependencies.lru]\ndefault-features = false\nversion = \"0.8.1\"\n\n[dependencies.mediasoup-sys]\npath = \"../worker\"\nversion = \"0.11.0\"\n\n[dependencies.mediasoup-types]\npath = \"./types\"\nversion = \"0.3.0\"\n\n[dependencies.parking_lot]\nversion = \"0.12.1\"\nfeatures = [\"serde\"]\n\n[dependencies.regex]\ndefault-features = false\nfeatures = [\"std\", \"perf\"]\nversion = \"1.6.0\"\n\n[dependencies.serde]\nfeatures = [\"derive\"]\nversion = \"1.0.190\"\n\n[dependencies.uuid]\nfeatures = [\"serde\", \"v4\"]\nversion = \"1.2.1\"\n\n[dev-dependencies]\nactix = \"0.13.0\"\nactix-web-actors = \"4.2.0\"\nasync-io = \"1.10.0\"\ncriterion = \"0.4.0\"\nenv_logger = \"0.9.1\"\nportpicker = \"0.1.1\"\n\n[dev-dependencies.actix-web]\ndefault-features = false\nfeatures = [\"macros\", \"ws\"]\nversion = \"4.9.0\"\n\n[[bench]]\nname = \"direct_data\"\nharness = false\n[[bench]]\nname = \"producer\"\nharness = false\n"
  },
  {
    "path": "rust/benches/direct_data.rs",
    "content": "use criterion::{criterion_group, criterion_main, Criterion};\nuse mediasoup::prelude::*;\nuse std::borrow::Cow;\nuse std::sync::mpsc;\n\nasync fn create_data_producer_consumer_pair(\n) -> Result<(DataProducer, DataConsumer), Box<dyn std::error::Error>> {\n    let worker_manager = WorkerManager::new();\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await?;\n\n    let router = worker.create_router(RouterOptions::default()).await?;\n    let direct_transport = router\n        .create_direct_transport(DirectTransportOptions::default())\n        .await?;\n\n    let data_producer = direct_transport\n        .produce_data(DataProducerOptions::new_direct())\n        .await?;\n    let data_consumer = direct_transport\n        .consume_data(DataConsumerOptions::new_direct(data_producer.id(), None))\n        .await?;\n\n    Ok((data_producer, data_consumer))\n}\n\npub fn criterion_benchmark(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"direct_data\");\n\n    let data = std::iter::repeat_with(|| fastrand::u8(..))\n        .take(512)\n        .collect::<Vec<u8>>();\n\n    {\n        let (data_producer, data_consumer) = futures_lite::future::block_on(async {\n            create_data_producer_consumer_pair().await.unwrap()\n        });\n\n        let direct_data_producer = if let DataProducer::Direct(direct_data_producer) = data_producer\n        {\n            direct_data_producer\n        } else {\n            unreachable!()\n        };\n\n        group.bench_with_input(\"recv\", &data, |b, data| {\n            b.iter(|| {\n                let (sender, receiver) = mpsc::sync_channel(1);\n                let _handler_id = data_consumer.on_message(move |_message| {\n                    let _ = sender.send(());\n                });\n\n                let _ =\n                    direct_data_producer.send(WebRtcMessage::Binary(Cow::from(data)), None, None);\n\n                let _ = receiver.recv();\n            })\n        });\n    }\n\n    group.finish();\n}\n\ncriterion_group!(benches, criterion_benchmark);\ncriterion_main!(benches);\n"
  },
  {
    "path": "rust/benches/producer.rs",
    "content": "use criterion::{criterion_group, criterion_main, Criterion};\nuse mediasoup::prelude::*;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\nfn create_ssrc() -> u32 {\n    fastrand::u32(100_000_000..999_999_999)\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"foo\", \"111\".into())]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ]\n}\n\nasync fn init() -> (Worker, Router, WebRtcTransport, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport_1 = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (worker, router, transport_1, transport_2)\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(fastrand::u32(100_000_000..999_999_999).to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 0,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"usedtx\", 1_u32.into()),\n                    (\"foo\", \"222.222\".into()),\n                    (\"bar\", \"333\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n            // Missing encodings on purpose.\n            encodings: vec![],\n            rtcp: RtcpParameters {\n                cname: Some(\"audio-1\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    )\n}\n\nfn video_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Video,\n        RtpParameters {\n            mid: Some(fastrand::u32(100_000_000..999_999_999).to_string()),\n            codecs: vec![\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::H264,\n                    payload_type: 112,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([\n                        (\"packetization-mode\", 1_u32.into()),\n                        (\"profile-level-id\", \"4d0032\".into()),\n                    ]),\n                    rtcp_feedback: vec![\n                        RtcpFeedback::Nack,\n                        RtcpFeedback::NackPli,\n                        RtcpFeedback::GoogRemb,\n                    ],\n                },\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Rtx,\n                    payload_type: 113,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([(\"apt\", 112u32.into())]),\n                    rtcp_feedback: vec![],\n                },\n            ],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 13,\n                    encrypt: false,\n                },\n            ],\n            encodings: vec![\n                RtpEncodingParameters {\n                    ssrc: Some(create_ssrc()),\n                    rtx: Some(RtpEncodingParametersRtx {\n                        ssrc: create_ssrc(),\n                    }),\n                    scalability_mode: \"L1T3\".parse().unwrap(),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(create_ssrc()),\n                    rtx: Some(RtpEncodingParametersRtx {\n                        ssrc: create_ssrc(),\n                    }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(create_ssrc()),\n                    rtx: Some(RtpEncodingParametersRtx {\n                        ssrc: create_ssrc(),\n                    }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(create_ssrc()),\n                    rtx: Some(RtpEncodingParametersRtx {\n                        ssrc: create_ssrc(),\n                    }),\n                    ..RtpEncodingParameters::default()\n                },\n            ],\n            rtcp: RtcpParameters {\n                cname: Some(\"video-1\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    )\n}\n\npub fn criterion_benchmark(c: &mut Criterion) {\n    let mut group = c.benchmark_group(\"producer\");\n\n    {\n        let (_worker, _router, transport_1, _transport_2) =\n            futures_lite::future::block_on(async { init().await });\n\n        {\n            let audio_producer = futures_lite::future::block_on(async {\n                let (_worker, _router, transport_1, _transport_2) = init().await;\n                transport_1\n                    .produce(audio_producer_options())\n                    .await\n                    .expect(\"Failed to produce audio\")\n            });\n\n            group.bench_function(\"create/audio\", |b| {\n                b.iter(|| {\n                    let _ = futures_lite::future::block_on(async {\n                        transport_1\n                            .produce(audio_producer_options())\n                            .await\n                            .expect(\"Failed to produce audio\")\n                    });\n                })\n            });\n\n            group.bench_function(\"dump/audio\", |b| {\n                b.iter(|| {\n                    let _ = futures_lite::future::block_on(async { audio_producer.dump().await });\n                })\n            });\n\n            group.bench_function(\"stats/audio\", |b| {\n                b.iter(|| {\n                    let _ =\n                        futures_lite::future::block_on(async { audio_producer.get_stats().await });\n                })\n            });\n        }\n\n        {\n            let video_producer = futures_lite::future::block_on(async {\n                let (_worker, _router, transport_1, _transport_2) = init().await;\n                transport_1\n                    .produce(video_producer_options())\n                    .await\n                    .expect(\"Failed to produce video\")\n            });\n\n            group.bench_function(\"create/video\", |b| {\n                b.iter(|| {\n                    let _ = futures_lite::future::block_on(async {\n                        transport_1\n                            .produce(video_producer_options())\n                            .await\n                            .expect(\"Failed to produce video\")\n                    });\n                })\n            });\n\n            group.bench_function(\"dump/video\", |b| {\n                b.iter(|| {\n                    let _ = futures_lite::future::block_on(async { video_producer.dump().await });\n                })\n            });\n\n            group.bench_function(\"stats/video\", |b| {\n                b.iter(|| {\n                    let _ =\n                        futures_lite::future::block_on(async { video_producer.get_stats().await });\n                })\n            });\n        }\n    }\n\n    group.finish();\n}\n\ncriterion_group!(benches, criterion_benchmark);\ncriterion_main!(benches);\n"
  },
  {
    "path": "rust/examples/echo.rs",
    "content": "use actix::prelude::*;\nuse actix_web::web::{Data, Payload};\nuse actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};\nuse actix_web_actors::ws;\nuse mediasoup::prelude::*;\nuse mediasoup::worker::{WorkerLogLevel, WorkerLogTag};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\n/// List of codecs that SFU will accept from clients\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"useinbandfec\", 1_u32.into())]),\n            rtcp_feedback: vec![RtcpFeedback::TransportCc],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![\n                RtcpFeedback::Nack,\n                RtcpFeedback::NackPli,\n                RtcpFeedback::CcmFir,\n                RtcpFeedback::GoogRemb,\n                RtcpFeedback::TransportCc,\n            ],\n        },\n    ]\n}\n\n/// Data structure containing all the necessary information about transport options required from\n/// the server to establish transport connection on the client\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TransportOptions {\n    id: TransportId,\n    dtls_parameters: DtlsParameters,\n    ice_candidates: Vec<IceCandidate>,\n    ice_parameters: IceParameters,\n}\n\n/// Server messages sent to the client\n#[derive(Serialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\n#[allow(clippy::large_enum_variant)]\nenum ServerMessage {\n    /// Initialization message with consumer/producer transport options and Router's RTP\n    /// capabilities necessary to establish WebRTC transport connection client-side\n    #[serde(rename_all = \"camelCase\")]\n    Init {\n        consumer_transport_options: TransportOptions,\n        producer_transport_options: TransportOptions,\n        router_rtp_capabilities: RtpCapabilitiesFinalized,\n    },\n    /// Notification that producer transport was connected successfully (in case of error connection\n    /// is just dropped, in real-world application you probably want to handle it better)\n    ConnectedProducerTransport,\n    /// Notification that producer was created on the server, in this simple example client will try\n    /// to consume it right away, hence `echo` example\n    #[serde(rename_all = \"camelCase\")]\n    Produced { id: ProducerId },\n    /// Notification that consumer transport was connected successfully (in case of error connection\n    /// is just dropped, in real-world application you probably want to handle it better)\n    ConnectedConsumerTransport,\n    /// Notification that consumer was successfully created server-side, client can resume the\n    /// consumer after this\n    #[serde(rename_all = \"camelCase\")]\n    Consumed {\n        id: ConsumerId,\n        producer_id: ProducerId,\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    },\n}\n\n/// Client messages sent to the server\n#[derive(Deserialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\nenum ClientMessage {\n    /// Client-side initialization with its RTP capabilities, in this simple case we expect those to\n    /// match server Router's RTP capabilities\n    #[serde(rename_all = \"camelCase\")]\n    Init { rtp_capabilities: RtpCapabilities },\n    /// Request to connect producer transport with client-side DTLS parameters\n    #[serde(rename_all = \"camelCase\")]\n    ConnectProducerTransport { dtls_parameters: DtlsParameters },\n    /// Request to produce a new audio or video track with specified RTP parameters\n    #[serde(rename_all = \"camelCase\")]\n    Produce {\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    },\n    /// Request to connect consumer transport with client-side DTLS parameters\n    #[serde(rename_all = \"camelCase\")]\n    ConnectConsumerTransport { dtls_parameters: DtlsParameters },\n    /// Request to consume specified producer\n    #[serde(rename_all = \"camelCase\")]\n    Consume { producer_id: ProducerId },\n    /// Request to resume consumer that was previously created\n    #[serde(rename_all = \"camelCase\")]\n    ConsumerResume { id: ConsumerId },\n}\n\n/// Internal actor messages for convenience\n#[derive(Message)]\n#[rtype(result = \"()\")]\nenum InternalMessage {\n    /// Save producer in connection-specific hashmap to prevent it from being destroyed\n    SaveProducer(Producer),\n    /// Save consumer in connection-specific hashmap to prevent it from being destroyed\n    SaveConsumer(Consumer),\n    /// Stop/close the WebSocket connection\n    Stop,\n}\n\n/// Consumer/producer transports pair for the client\nstruct Transports {\n    consumer: WebRtcTransport,\n    producer: WebRtcTransport,\n}\n\n/// Actor that will represent WebSocket connection from the client, it will handle inbound and\n/// outbound WebSocket messages in JSON.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nstruct EchoConnection {\n    /// RTP capabilities received from the client\n    client_rtp_capabilities: Option<RtpCapabilities>,\n    /// Consumers associated with this client, preventing them from being destroyed\n    consumers: HashMap<ConsumerId, Consumer>,\n    /// Producers associated with this client, preventing them from being destroyed\n    producers: Vec<Producer>,\n    /// Router associated with this client, useful to get its RTP capabilities later\n    router: Router,\n    /// Consumer and producer transports associated with this client\n    transports: Transports,\n}\n\nimpl EchoConnection {\n    /// Create a new instance representing WebSocket connection\n    async fn new(worker_manager: &WorkerManager) -> Result<Self, String> {\n        let worker = worker_manager\n            .create_worker({\n                let mut settings = WorkerSettings::default();\n                settings.log_level = WorkerLogLevel::Debug;\n                settings.log_tags = vec![\n                    WorkerLogTag::Info,\n                    WorkerLogTag::Ice,\n                    WorkerLogTag::Dtls,\n                    WorkerLogTag::Rtp,\n                    WorkerLogTag::Srtp,\n                    WorkerLogTag::Rtcp,\n                    WorkerLogTag::Rtx,\n                    WorkerLogTag::Bwe,\n                    WorkerLogTag::Score,\n                    WorkerLogTag::Simulcast,\n                    WorkerLogTag::Svc,\n                    WorkerLogTag::Sctp,\n                    WorkerLogTag::Message,\n                ];\n\n                settings\n            })\n            .await\n            .map_err(|error| format!(\"Failed to create worker: {error}\"))?;\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .map_err(|error| format!(\"Failed to create router: {error}\"))?;\n\n        // We know that for echo example we'll need 2 transports, so we can create both right away.\n        // This may not be the case for real-world applications or you may create this at a\n        // different time and/or in different order.\n        let transport_options =\n            WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            }));\n        let producer_transport = router\n            .create_webrtc_transport(transport_options.clone())\n            .await\n            .map_err(|error| format!(\"Failed to create producer transport: {error}\"))?;\n\n        let consumer_transport = router\n            .create_webrtc_transport(transport_options)\n            .await\n            .map_err(|error| format!(\"Failed to create consumer transport: {error}\"))?;\n\n        Ok(Self {\n            client_rtp_capabilities: None,\n            consumers: HashMap::new(),\n            producers: vec![],\n            router,\n            transports: Transports {\n                consumer: consumer_transport,\n                producer: producer_transport,\n            },\n        })\n    }\n}\n\nimpl Actor for EchoConnection {\n    type Context = ws::WebsocketContext<Self>;\n\n    fn started(&mut self, ctx: &mut Self::Context) {\n        println!(\"WebSocket connection created\");\n\n        // We know that both consumer and producer transports will be used, so we sent server\n        // information about both in an initialization message alongside with router capabilities\n        // to the client right after WebSocket connection is established\n        let server_init_message = ServerMessage::Init {\n            consumer_transport_options: TransportOptions {\n                id: self.transports.consumer.id(),\n                dtls_parameters: self.transports.consumer.dtls_parameters(),\n                ice_candidates: self.transports.consumer.ice_candidates().clone(),\n                ice_parameters: self.transports.consumer.ice_parameters().clone(),\n            },\n            producer_transport_options: TransportOptions {\n                id: self.transports.producer.id(),\n                dtls_parameters: self.transports.producer.dtls_parameters(),\n                ice_candidates: self.transports.producer.ice_candidates().clone(),\n                ice_parameters: self.transports.producer.ice_parameters().clone(),\n            },\n            router_rtp_capabilities: self.router.rtp_capabilities().clone(),\n        };\n\n        ctx.address().do_send(server_init_message);\n    }\n\n    fn stopped(&mut self, _ctx: &mut Self::Context) {\n        println!(\"WebSocket connection closed\");\n    }\n}\n\nimpl StreamHandler<Result<ws::Message, ws::ProtocolError>> for EchoConnection {\n    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {\n        // Here we handle incoming WebSocket messages, intentionally not handling continuation\n        // messages since we know all messages will fit into a single frame, but in real-world apps\n        // you need to handle continuation frames too (`ws::Message::Continuation`)\n        match msg {\n            Ok(ws::Message::Ping(msg)) => {\n                ctx.pong(&msg);\n            }\n            Ok(ws::Message::Pong(_)) => {}\n            Ok(ws::Message::Text(text)) => match serde_json::from_str::<ClientMessage>(&text) {\n                Ok(message) => {\n                    // Parse JSON into an enum and just send it back to the actor to be processed\n                    // by another handler below, it is much more convenient to just parse it in one\n                    // place and have typed data structure everywhere else\n                    ctx.address().do_send(message);\n                }\n                Err(error) => {\n                    eprintln!(\"Failed to parse client message: {error}\\n{text}\");\n                }\n            },\n            Ok(ws::Message::Binary(bin)) => {\n                eprintln!(\"Unexpected binary message: {bin:?}\");\n            }\n            Ok(ws::Message::Close(reason)) => {\n                ctx.close(reason);\n                ctx.stop();\n            }\n            _ => ctx.stop(),\n        }\n    }\n}\n\nimpl Handler<ClientMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) {\n        match message {\n            ClientMessage::Init { rtp_capabilities } => {\n                // We need to know client's RTP capabilities, those are sent using initialization\n                // message and are stored in connection struct for future use\n                self.client_rtp_capabilities.replace(rtp_capabilities);\n            }\n            ClientMessage::ConnectProducerTransport { dtls_parameters } => {\n                let address = ctx.address();\n                let transport = self.transports.producer.clone();\n                // Establish connection for producer transport using DTLS parameters received\n                // from the client, but doing so in a background task since this handler is\n                // synchronous\n                actix::spawn(async move {\n                    match transport\n                        .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                        .await\n                    {\n                        Ok(_) => {\n                            address.do_send(ServerMessage::ConnectedProducerTransport);\n                            println!(\"Producer transport connected\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to connect producer transport: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::Produce {\n                kind,\n                rtp_parameters,\n            } => {\n                let address = ctx.address();\n                let transport = self.transports.producer.clone();\n                // Use producer transport to create a new producer on the server with given RTP\n                // parameters\n                actix::spawn(async move {\n                    match transport\n                        .produce(ProducerOptions::new(kind, rtp_parameters))\n                        .await\n                    {\n                        Ok(producer) => {\n                            let id = producer.id();\n                            address.do_send(ServerMessage::Produced { id });\n                            // Producer is stored in a hashmap since if we don't do it, it will get\n                            // destroyed as soon as its instance goes out out scope\n                            address.do_send(InternalMessage::SaveProducer(producer));\n                            println!(\"{kind:?} producer created: {id}\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to create {kind:?} producer: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::ConnectConsumerTransport { dtls_parameters } => {\n                let address = ctx.address();\n                let transport = self.transports.consumer.clone();\n                // The same as producer transport, but for consumer transport\n                actix::spawn(async move {\n                    match transport\n                        .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                        .await\n                    {\n                        Ok(_) => {\n                            address.do_send(ServerMessage::ConnectedConsumerTransport);\n                            println!(\"Consumer transport connected\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to connect consumer transport: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::Consume { producer_id } => {\n                let address = ctx.address();\n                let transport = self.transports.consumer.clone();\n                let rtp_capabilities = match self.client_rtp_capabilities.clone() {\n                    Some(rtp_capabilities) => rtp_capabilities,\n                    None => {\n                        eprintln!(\"Client should send RTP capabilities before consuming\");\n                        return;\n                    }\n                };\n                // Create consumer for given producer ID, while first making sure that RTP\n                // capabilities were sent by the client prior to that\n                actix::spawn(async move {\n                    let mut options = ConsumerOptions::new(producer_id, rtp_capabilities);\n                    options.paused = true;\n\n                    match transport.consume(options).await {\n                        Ok(consumer) => {\n                            let id = consumer.id();\n                            let kind = consumer.kind();\n                            let rtp_parameters = consumer.rtp_parameters().clone();\n                            address.do_send(ServerMessage::Consumed {\n                                id,\n                                producer_id,\n                                kind,\n                                rtp_parameters,\n                            });\n                            // Consumer is stored in a hashmap since if we don't do it, it will get\n                            // destroyed as soon as its instance goes out out scope\n                            address.do_send(InternalMessage::SaveConsumer(consumer));\n                            println!(\"{kind:?} consumer created: {id}\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to create consumer: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::ConsumerResume { id } => {\n                if let Some(consumer) = self.consumers.get(&id).cloned() {\n                    actix::spawn(async move {\n                        match consumer.resume().await {\n                            Ok(_) => {\n                                println!(\n                                    \"Successfully resumed {:?} consumer {}\",\n                                    consumer.kind(),\n                                    consumer.id(),\n                                );\n                            }\n                            Err(error) => {\n                                println!(\n                                    \"Failed to resume {:?} consumer {}: {}\",\n                                    consumer.kind(),\n                                    consumer.id(),\n                                    error,\n                                );\n                            }\n                        }\n                    });\n                }\n            }\n        }\n    }\n}\n\n/// Simple handler that will transform typed server messages into JSON and send them over to the\n/// client over WebSocket connection\nimpl Handler<ServerMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) {\n        ctx.text(serde_json::to_string(&message).unwrap());\n    }\n}\n\n/// Convenience handler for internal messages, these actions require mutable access to the\n/// connection struct and having such message handler makes it easy to use from background tasks\n/// where otherwise Mutex would have to be used instead\nimpl Handler<InternalMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) {\n        match message {\n            InternalMessage::Stop => {\n                ctx.stop();\n            }\n            InternalMessage::SaveProducer(producer) => {\n                // Retain producer to prevent it from being destroyed\n                self.producers.push(producer);\n            }\n            InternalMessage::SaveConsumer(consumer) => {\n                self.consumers.insert(consumer.id(), consumer);\n            }\n        }\n    }\n}\n\n/// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nasync fn ws_index(\n    request: HttpRequest,\n    worker_manager: Data<WorkerManager>,\n    stream: Payload,\n) -> Result<HttpResponse, Error> {\n    match EchoConnection::new(&worker_manager).await {\n        Ok(echo_server) => ws::start(echo_server, &request, stream),\n        Err(error) => {\n            eprintln!(\"{error}\");\n\n            Ok(HttpResponse::InternalServerError().finish())\n        }\n    }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    env_logger::init();\n\n    // We will reuse the same worker manager across all connections, this is more than enough for\n    // this use case\n    let worker_manager = Data::new(WorkerManager::new());\n    HttpServer::new(move || {\n        App::new()\n            .app_data(worker_manager.clone())\n            .route(\"/ws\", web::get().to(ws_index))\n    })\n    // 2 threads is plenty for this example, default is to have as many threads as CPU cores\n    .workers(2)\n    .bind(\"127.0.0.1:3000\")?\n    .run()\n    .await\n}\n"
  },
  {
    "path": "rust/examples/multiopus.rs",
    "content": "use actix::prelude::*;\nuse actix_web::web::{Data, Payload};\nuse actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};\nuse actix_web_actors::ws;\nuse mediasoup::prelude::*;\nuse mediasoup::worker::{WorkerLogLevel, WorkerLogTag};\nuse mediasoup_types::rtp_parameters::{RtpCodecParameters, RtpEncodingParameters};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\n/// List of codecs that SFU will accept from clients\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::MultiChannelOpus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(6).unwrap(),\n        parameters: RtpCodecParametersParameters::from([\n            (\"useinbandfec\", 1_u32.into()),\n            (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n            (\"num_streams\", 4_u32.into()),\n            (\"coupled_streams\", 2_u32.into()),\n        ]),\n        rtcp_feedback: vec![RtcpFeedback::TransportCc],\n    }]\n}\n\n/// Data structure containing all the necessary information about transport options required from\n/// the server to establish transport connection on the client\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TransportOptions {\n    id: TransportId,\n    dtls_parameters: DtlsParameters,\n    ice_candidates: Vec<IceCandidate>,\n    ice_parameters: IceParameters,\n}\n\n/// Server messages sent to the client\n#[derive(Serialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\nenum ServerMessage {\n    /// Initialization message with consumer transport options and Router's RTP\n    /// capabilities necessary to establish WebRTC transport connection client-side\n    #[serde(rename_all = \"camelCase\")]\n    Init {\n        consumer_transport_options: TransportOptions,\n        router_rtp_capabilities: RtpCapabilitiesFinalized,\n        rtp_producer_id: ProducerId,\n    },\n    /// Notification that consumer transport was connected successfully (in case of error connection\n    /// is just dropped, in real-world application you probably want to handle it better)\n    ConnectedConsumerTransport,\n    /// Notification that consumer was successfully created server-side, client can resume the\n    /// consumer after this\n    #[serde(rename_all = \"camelCase\")]\n    Consumed {\n        id: ConsumerId,\n        producer_id: ProducerId,\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    },\n}\n\n/// Client messages sent to the server\n#[derive(Deserialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\nenum ClientMessage {\n    /// Client-side initialization with its RTP capabilities, in this simple case we expect those to\n    /// match server Router's RTP capabilities\n    #[serde(rename_all = \"camelCase\")]\n    Init { rtp_capabilities: RtpCapabilities },\n    /// Request to connect consumer transport with client-side DTLS parameters\n    #[serde(rename_all = \"camelCase\")]\n    ConnectConsumerTransport { dtls_parameters: DtlsParameters },\n    /// Request to consume specified producer\n    #[serde(rename_all = \"camelCase\")]\n    Consume { producer_id: ProducerId },\n}\n\n/// Internal actor messages for convenience\n#[derive(Message)]\n#[rtype(result = \"()\")]\nenum InternalMessage {\n    /// Save consumer in connection-specific hashmap to prevent it from being destroyed\n    SaveConsumer(Consumer),\n    /// Stop/close the WebSocket connection\n    Stop,\n}\n\n/// Actor that will represent WebSocket connection from the client, it will handle inbound and\n/// outbound WebSocket messages in JSON.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nstruct EchoConnection {\n    /// RTP capabilities received from the client\n    client_rtp_capabilities: Option<RtpCapabilities>,\n    /// Consumers associated with this client, preventing them from being destroyed\n    consumers: HashMap<ConsumerId, Consumer>,\n    /// RTP producer associated with this client, preventing it from being destroyed and useful to\n    /// get its ID later\n    rtp_producer: Producer,\n    /// Router associated with this client, useful to get its RTP capabilities later\n    router: Router,\n    /// Consumer transport associated with this client\n    consumer_transport: WebRtcTransport,\n}\n\nimpl EchoConnection {\n    /// Create a new instance representing WebSocket connection\n    async fn new(worker_manager: &WorkerManager) -> Result<Self, String> {\n        let worker = worker_manager\n            .create_worker({\n                let mut settings = WorkerSettings::default();\n                settings.log_level = WorkerLogLevel::Debug;\n                settings.log_tags = vec![\n                    WorkerLogTag::Info,\n                    WorkerLogTag::Ice,\n                    WorkerLogTag::Dtls,\n                    WorkerLogTag::Rtp,\n                    WorkerLogTag::Srtp,\n                    WorkerLogTag::Rtcp,\n                    WorkerLogTag::Rtx,\n                    WorkerLogTag::Bwe,\n                    WorkerLogTag::Score,\n                    WorkerLogTag::Simulcast,\n                    WorkerLogTag::Svc,\n                    WorkerLogTag::Sctp,\n                    WorkerLogTag::Message,\n                ];\n\n                settings\n            })\n            .await\n            .map_err(|error| format!(\"Failed to create worker: {error}\"))?;\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .map_err(|error| format!(\"Failed to create router: {error}\"))?;\n\n        // For simplicity we will create plain transport for audio producer right away\n        let plain_transport = router\n            .create_plain_transport({\n                let mut options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                options.comedia = true;\n                options.rtcp_mux = false;\n\n                options\n            })\n            .await\n            .map_err(|error| format!(\"Failed to create plain transport: {error}\"))?;\n\n        // And creating audio producer that will be consumed over WebRTC later\n        let rtp_producer = plain_transport\n            .produce(ProducerOptions::new(\n                MediaKind::Audio,\n                RtpParameters {\n                    mid: Some(\"AUDIO\".to_string()),\n                    codecs: vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::MultiChannelOpus,\n                        payload_type: 100,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(6).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"useinbandfec\", 1_u32.into()),\n                            (\"channel_mapping\", \"0,1,4,5,2,3\".into()),\n                            (\"num_streams\", 4_u32.into()),\n                            (\"coupled_streams\", 2_u32.into()),\n                        ]),\n                        rtcp_feedback: vec![],\n                    }],\n                    encodings: vec![RtpEncodingParameters {\n                        ssrc: Some(1111),\n                        ..RtpEncodingParameters::default()\n                    }],\n                    ..RtpParameters::default()\n                },\n            ))\n            .await\n            .map_err(|error| format!(\"Failed to create audio producer: {error}\"))?;\n\n        println!(\n            \"Plain transport created:\\n  \\\n            RTP listening on {}:{}\\n  \\\n            RTCP listening on {}:{}\\n  \\\n            PT=100\\n  \\\n            SSRC=1111\",\n            plain_transport.tuple().local_address(),\n            plain_transport.tuple().local_port(),\n            plain_transport.rtcp_tuple().unwrap().local_address(),\n            plain_transport.rtcp_tuple().unwrap().local_port(),\n        );\n\n        println!(\n            \"Use following command with GStreamer (1.20+) to play a sample audio:\\n\\\n              gst-launch-1.0 \\\\\\n  \\\n                rtpbin name=rtpbin \\\\\\n  \\\n                souphttpsrc location=https://www2.iis.fraunhofer.de/AAC/ChID-BLITS-EBU-Narration.mp4 ! \\\\\\n  \\\n                queue ! \\\\\\n  \\\n                decodebin ! \\\\\\n  \\\n                audioresample ! \\\\\\n  \\\n                audioconvert ! \\\\\\n  \\\n                opusenc inband-fec=true ! \\\\\\n  \\\n                queue ! \\\\\\n  \\\n                clocksync ! \\\\\\n  \\\n                rtpopuspay pt=100 ssrc=1111 ! \\\\\\n  \\\n                rtpbin.send_rtp_sink_0 \\\\\\n  \\\n                rtpbin.send_rtp_src_0 ! udpsink host={} port={} sync=false async=false \\\\\\n  \\\n                rtpbin.send_rtcp_src_0 ! udpsink host={} port={} sync=false async=false\",\n                plain_transport.tuple().local_address(),\n                plain_transport.tuple().local_port(),\n                plain_transport.rtcp_tuple().unwrap().local_address(),\n                plain_transport.rtcp_tuple().unwrap().local_port(),\n        );\n\n        // We know that for multiopus example we'll need just consumer transport, so we can create\n        // it right away. This may not be the case for real-world applications or you may create\n        // this at a different time and/or in different order.\n        let consumer_transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .map_err(|error| format!(\"Failed to create consumer transport: {error}\"))?;\n\n        Ok(Self {\n            client_rtp_capabilities: None,\n            consumers: HashMap::new(),\n            rtp_producer,\n            router,\n            consumer_transport,\n        })\n    }\n}\n\nimpl Actor for EchoConnection {\n    type Context = ws::WebsocketContext<Self>;\n\n    fn started(&mut self, ctx: &mut Self::Context) {\n        println!(\"WebSocket connection created\");\n\n        // We know that only consumer transport will be used, so we sent server information about it\n        // in an initialization message alongside with router capabilities to the client right after\n        // WebSocket connection is established\n        let server_init_message = ServerMessage::Init {\n            consumer_transport_options: TransportOptions {\n                id: self.consumer_transport.id(),\n                dtls_parameters: self.consumer_transport.dtls_parameters(),\n                ice_candidates: self.consumer_transport.ice_candidates().clone(),\n                ice_parameters: self.consumer_transport.ice_parameters().clone(),\n            },\n            router_rtp_capabilities: self.router.rtp_capabilities().clone(),\n            rtp_producer_id: self.rtp_producer.id(),\n        };\n\n        ctx.address().do_send(server_init_message);\n    }\n\n    fn stopped(&mut self, _ctx: &mut Self::Context) {\n        println!(\"WebSocket connection closed\");\n    }\n}\n\nimpl StreamHandler<Result<ws::Message, ws::ProtocolError>> for EchoConnection {\n    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {\n        // Here we handle incoming WebSocket messages, intentionally not handling continuation\n        // messages since we know all messages will fit into a single frame, but in real-world apps\n        // you need to handle continuation frames too (`ws::Message::Continuation`)\n        match msg {\n            Ok(ws::Message::Ping(msg)) => {\n                ctx.pong(&msg);\n            }\n            Ok(ws::Message::Pong(_)) => {}\n            Ok(ws::Message::Text(text)) => match serde_json::from_str::<ClientMessage>(&text) {\n                Ok(message) => {\n                    // Parse JSON into an enum and just send it back to the actor to be processed\n                    // by another handler below, it is much more convenient to just parse it in one\n                    // place and have typed data structure everywhere else\n                    ctx.address().do_send(message);\n                }\n                Err(error) => {\n                    eprintln!(\"Failed to parse client message: {error}\\n{text}\");\n                }\n            },\n            Ok(ws::Message::Binary(bin)) => {\n                eprintln!(\"Unexpected binary message: {bin:?}\");\n            }\n            Ok(ws::Message::Close(reason)) => {\n                ctx.close(reason);\n                ctx.stop();\n            }\n            _ => ctx.stop(),\n        }\n    }\n}\n\nimpl Handler<ClientMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) {\n        match message {\n            ClientMessage::Init { rtp_capabilities } => {\n                // We need to know client's RTP capabilities, those are sent using initialization\n                // message and are stored in connection struct for future use\n                self.client_rtp_capabilities.replace(rtp_capabilities);\n            }\n            ClientMessage::ConnectConsumerTransport { dtls_parameters } => {\n                let address = ctx.address();\n                let transport = self.consumer_transport.clone();\n                // The same as producer transport, but for consumer transport\n                actix::spawn(async move {\n                    match transport\n                        .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                        .await\n                    {\n                        Ok(_) => {\n                            address.do_send(ServerMessage::ConnectedConsumerTransport);\n                            println!(\"Consumer transport connected\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to connect consumer transport: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::Consume { producer_id } => {\n                let address = ctx.address();\n                let transport = self.consumer_transport.clone();\n                let rtp_capabilities = match self.client_rtp_capabilities.clone() {\n                    Some(rtp_capabilities) => rtp_capabilities,\n                    None => {\n                        eprintln!(\"Client should send RTP capabilities before consuming\");\n                        return;\n                    }\n                };\n                // Create consumer for given producer ID, while first making sure that RTP\n                // capabilities were sent by the client prior to that\n                actix::spawn(async move {\n                    match transport\n                        .consume(ConsumerOptions::new(producer_id, rtp_capabilities))\n                        .await\n                    {\n                        Ok(consumer) => {\n                            let id = consumer.id();\n                            let kind = consumer.kind();\n                            let rtp_parameters = consumer.rtp_parameters().clone();\n                            address.do_send(ServerMessage::Consumed {\n                                id,\n                                producer_id,\n                                kind,\n                                rtp_parameters,\n                            });\n                            // Consumer is stored in a hashmap since if we don't do it, it will get\n                            // destroyed as soon as its instance goes out out scope\n                            address.do_send(InternalMessage::SaveConsumer(consumer));\n                            println!(\"{kind:?} consumer created: {id}\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to create consumer: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n        }\n    }\n}\n\n/// Simple handler that will transform typed server messages into JSON and send them over to the\n/// client over WebSocket connection\nimpl Handler<ServerMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) {\n        ctx.text(serde_json::to_string(&message).unwrap());\n    }\n}\n\n/// Convenience handler for internal messages, these actions require mutable access to the\n/// connection struct and having such message handler makes it easy to use from background tasks\n/// where otherwise Mutex would have to be used instead\nimpl Handler<InternalMessage> for EchoConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) {\n        match message {\n            InternalMessage::Stop => {\n                ctx.stop();\n            }\n            InternalMessage::SaveConsumer(consumer) => {\n                self.consumers.insert(consumer.id(), consumer);\n            }\n        }\n    }\n}\n\n/// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nasync fn ws_index(\n    request: HttpRequest,\n    worker_manager: Data<WorkerManager>,\n    stream: Payload,\n) -> Result<HttpResponse, Error> {\n    match EchoConnection::new(&worker_manager).await {\n        Ok(echo_server) => ws::start(echo_server, &request, stream),\n        Err(error) => {\n            eprintln!(\"{error}\");\n\n            Ok(HttpResponse::InternalServerError().finish())\n        }\n    }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    env_logger::init();\n\n    // We will reuse the same worker manager across all connections, this is more than enough for\n    // this use case\n    let worker_manager = Data::new(WorkerManager::new());\n    HttpServer::new(move || {\n        App::new()\n            .app_data(worker_manager.clone())\n            .route(\"/ws\", web::get().to(ws_index))\n    })\n    // 2 threads is plenty for this example, default is to have as many threads as CPU cores\n    .workers(2)\n    .bind(\"127.0.0.1:3000\")?\n    .run()\n    .await\n}\n"
  },
  {
    "path": "rust/examples/readme.md",
    "content": "# Examples\nThis directory contains examples of using mediasoup Rust library.\n\nNOTE: These examples are simplified and provided for demo purposes only, they are by no means complete or representative\nof production-grade software, use for educational purposes only and refer to documentation for all possible options.\n\nIn order to run server-side part of examples (this directory) use `cargo run --example EXAMPLE_NAME`.\n\n# Echo\nSimple echo example that receives audio+video from the client and sends audio+video back to the client via different\ntransport. Frontend part of this example is in `examples-frontend/echo` directory.\n\nCheck WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source\ncode has a bunch of comments about what is happening and where changes would be needed for production use.\n\n# Video room\nA bit more advanced example that allow multiple participants to join the same virtual room and receive audio+video from\nother participants, resulting in a simple video conferencing setup. Frontend part of this example is in\n`examples-frontend/videoroom` directory.\n\nCheck WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source\ncode has a bunch of comments about what is happening and where changes would be needed for production use.\n\n# Multiopus\nDemonstration of surround sound in WebRTC using Chromium-specific \"multiopus\" audio.\n\nThis one is very simple in a sense that it only supports one participant playing back sample audio and nothing else.\n\nNOTE: This requires GStreamer 1.20, which at the moment of writing isn't released yet. On Linux\n`docker run --rm -it --net=host restreamio/gstreamer:latest-prod-dbg` can be used to enter environment with working\nGStreamer version, or you can compile one from upstream sources.\n\nCheck WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source\ncode has a bunch of comments about what is happening and where changes would be needed for production use.\n"
  },
  {
    "path": "rust/examples/svc-simulcast.rs",
    "content": "use actix::prelude::*;\nuse actix_web::web::{Data, Payload};\nuse actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};\nuse actix_web_actors::ws;\nuse mediasoup::prelude::*;\nuse mediasoup::worker::{WorkerLogLevel, WorkerLogTag};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\n/// List of codecs that SFU will accept from clients\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"useinbandfec\", 1_u32.into())]),\n            rtcp_feedback: vec![RtcpFeedback::TransportCc],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![\n                RtcpFeedback::Nack,\n                RtcpFeedback::NackPli,\n                RtcpFeedback::CcmFir,\n                RtcpFeedback::GoogRemb,\n                RtcpFeedback::TransportCc,\n            ],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp9,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![\n                RtcpFeedback::Nack,\n                RtcpFeedback::NackPli,\n                RtcpFeedback::CcmFir,\n                RtcpFeedback::GoogRemb,\n                RtcpFeedback::TransportCc,\n            ],\n        },\n    ]\n}\n\n/// Data structure containing all the necessary information about transport options required from\n/// the server to establish transport connection on the client\n#[derive(Serialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct TransportOptions {\n    id: TransportId,\n    dtls_parameters: DtlsParameters,\n    ice_candidates: Vec<IceCandidate>,\n    ice_parameters: IceParameters,\n}\n\n/// Server messages sent to the client\n#[derive(Serialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\n#[allow(clippy::large_enum_variant)]\nenum ServerMessage {\n    /// Initialization message with consumer/producer transport options and Router's RTP\n    /// capabilities necessary to establish WebRTC transport connection client-side\n    #[serde(rename_all = \"camelCase\")]\n    Init {\n        consumer_transport_options: TransportOptions,\n        producer_transport_options: TransportOptions,\n        router_rtp_capabilities: RtpCapabilitiesFinalized,\n    },\n    /// Notification that producer transport was connected successfully (in case of error connection\n    /// is just dropped, in real-world application you probably want to handle it better)\n    ConnectedProducerTransport,\n    /// Notification that producer was created on the server, in this simple example client will try\n    /// to consume it right away\n    #[serde(rename_all = \"camelCase\")]\n    Produced { id: ProducerId },\n    /// Notification that consumer transport was connected successfully (in case of error connection\n    /// is just dropped, in real-world application you probably want to handle it better)\n    ConnectedConsumerTransport,\n    /// Notification that consumer was successfully created server-side, client can resume the\n    /// consumer after this\n    #[serde(rename_all = \"camelCase\")]\n    Consumed {\n        id: ConsumerId,\n        producer_id: ProducerId,\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    },\n}\n\n/// Client messages sent to the server\n#[derive(Deserialize, Message)]\n#[serde(tag = \"action\")]\n#[rtype(result = \"()\")]\nenum ClientMessage {\n    /// Client-side initialization with its RTP capabilities, in this simple case we expect those to\n    /// match server Router's RTP capabilities\n    #[serde(rename_all = \"camelCase\")]\n    Init { rtp_capabilities: RtpCapabilities },\n    /// Request to connect producer transport with client-side DTLS parameters\n    #[serde(rename_all = \"camelCase\")]\n    ConnectProducerTransport { dtls_parameters: DtlsParameters },\n    /// Request to produce a new audio or video track with specified RTP parameters\n    #[serde(rename_all = \"camelCase\")]\n    Produce {\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    },\n    /// Request to connect consumer transport with client-side DTLS parameters\n    #[serde(rename_all = \"camelCase\")]\n    ConnectConsumerTransport { dtls_parameters: DtlsParameters },\n    /// Request to consume specified producer\n    #[serde(rename_all = \"camelCase\")]\n    Consume { producer_id: ProducerId },\n    /// Request to resume consumer that was previously created\n    #[serde(rename_all = \"camelCase\")]\n    ConsumerResume { id: ConsumerId },\n    /// Request to set preferred spatial and temporal layers\n    #[serde(rename_all = \"camelCase\")]\n    SetConsumerPreferredLayers {\n        id: ConsumerId,\n        preferred_layers: ConsumerLayers,\n    },\n}\n\n/// Internal actor messages for convenience\n#[derive(Message)]\n#[rtype(result = \"()\")]\nenum InternalMessage {\n    /// Save producer in connection-specific hashmap to prevent it from being destroyed\n    SaveProducer(Producer),\n    /// Save consumer in connection-specific hashmap to prevent it from being destroyed\n    SaveConsumer(Consumer),\n    /// Stop/close the WebSocket connection\n    Stop,\n}\n\n/// Consumer/producer transports pair for the client\nstruct Transports {\n    consumer: WebRtcTransport,\n    producer: WebRtcTransport,\n}\n\n/// Actor that will represent WebSocket connection from the client, it will handle inbound and\n/// outbound WebSocket messages in JSON.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nstruct SvcSimulcastConnection {\n    /// RTP capabilities received from the client\n    client_rtp_capabilities: Option<RtpCapabilities>,\n    /// Consumers associated with this client, preventing them from being destroyed\n    consumers: HashMap<ConsumerId, Consumer>,\n    /// Producers associated with this client, preventing them from being destroyed\n    producers: Vec<Producer>,\n    /// Router associated with this client, useful to get its RTP capabilities later\n    router: Router,\n    /// Consumer and producer transports associated with this client\n    transports: Transports,\n}\n\nimpl SvcSimulcastConnection {\n    /// Create a new instance representing WebSocket connection\n    async fn new(worker_manager: &WorkerManager) -> Result<Self, String> {\n        let worker = worker_manager\n            .create_worker({\n                let mut settings = WorkerSettings::default();\n                settings.log_level = WorkerLogLevel::Debug;\n                settings.log_tags = vec![\n                    WorkerLogTag::Info,\n                    WorkerLogTag::Ice,\n                    WorkerLogTag::Dtls,\n                    WorkerLogTag::Rtp,\n                    WorkerLogTag::Srtp,\n                    WorkerLogTag::Rtcp,\n                    WorkerLogTag::Rtx,\n                    WorkerLogTag::Bwe,\n                    WorkerLogTag::Score,\n                    WorkerLogTag::Simulcast,\n                    WorkerLogTag::Svc,\n                    WorkerLogTag::Sctp,\n                    WorkerLogTag::Message,\n                ];\n\n                settings\n            })\n            .await\n            .map_err(|error| format!(\"Failed to create worker: {error}\"))?;\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .map_err(|error| format!(\"Failed to create router: {error}\"))?;\n\n        // We know that for svc-simulcast example we'll need 2 transports, so we can create both\n        // right away.\n        // This may not be the case for real-world applications or you may create this at a\n        // different time and/or in different order.\n        let transport_options =\n            WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            }));\n        let producer_transport = router\n            .create_webrtc_transport(transport_options.clone())\n            .await\n            .map_err(|error| format!(\"Failed to create producer transport: {error}\"))?;\n\n        let consumer_transport = router\n            .create_webrtc_transport(transport_options)\n            .await\n            .map_err(|error| format!(\"Failed to create consumer transport: {error}\"))?;\n\n        Ok(Self {\n            client_rtp_capabilities: None,\n            consumers: HashMap::new(),\n            producers: vec![],\n            router,\n            transports: Transports {\n                consumer: consumer_transport,\n                producer: producer_transport,\n            },\n        })\n    }\n}\n\nimpl Actor for SvcSimulcastConnection {\n    type Context = ws::WebsocketContext<Self>;\n\n    fn started(&mut self, ctx: &mut Self::Context) {\n        println!(\"WebSocket connection created\");\n\n        // We know that both consumer and producer transports will be used, so we sent server\n        // information about both in an initialization message alongside with router capabilities\n        // to the client right after WebSocket connection is established\n        let server_init_message = ServerMessage::Init {\n            consumer_transport_options: TransportOptions {\n                id: self.transports.consumer.id(),\n                dtls_parameters: self.transports.consumer.dtls_parameters(),\n                ice_candidates: self.transports.consumer.ice_candidates().clone(),\n                ice_parameters: self.transports.consumer.ice_parameters().clone(),\n            },\n            producer_transport_options: TransportOptions {\n                id: self.transports.producer.id(),\n                dtls_parameters: self.transports.producer.dtls_parameters(),\n                ice_candidates: self.transports.producer.ice_candidates().clone(),\n                ice_parameters: self.transports.producer.ice_parameters().clone(),\n            },\n            router_rtp_capabilities: self.router.rtp_capabilities().clone(),\n        };\n\n        ctx.address().do_send(server_init_message);\n    }\n\n    fn stopped(&mut self, _ctx: &mut Self::Context) {\n        println!(\"WebSocket connection closed\");\n    }\n}\n\nimpl StreamHandler<Result<ws::Message, ws::ProtocolError>> for SvcSimulcastConnection {\n    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {\n        // Here we handle incoming WebSocket messages, intentionally not handling continuation\n        // messages since we know all messages will fit into a single frame, but in real-world apps\n        // you need to handle continuation frames too (`ws::Message::Continuation`)\n        match msg {\n            Ok(ws::Message::Ping(msg)) => {\n                ctx.pong(&msg);\n            }\n            Ok(ws::Message::Pong(_)) => {}\n            Ok(ws::Message::Text(text)) => match serde_json::from_str::<ClientMessage>(&text) {\n                Ok(message) => {\n                    // Parse JSON into an enum and just send it back to the actor to be processed\n                    // by another handler below, it is much more convenient to just parse it in one\n                    // place and have typed data structure everywhere else\n                    ctx.address().do_send(message);\n                }\n                Err(error) => {\n                    eprintln!(\"Failed to parse client message: {text}\\n{error}\");\n                }\n            },\n            Ok(ws::Message::Binary(bin)) => {\n                eprintln!(\"Unexpected binary message: {bin:?}\");\n            }\n            Ok(ws::Message::Close(reason)) => {\n                ctx.close(reason);\n                ctx.stop();\n            }\n            _ => ctx.stop(),\n        }\n    }\n}\n\nimpl Handler<ClientMessage> for SvcSimulcastConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) {\n        match message {\n            ClientMessage::Init { rtp_capabilities } => {\n                // We need to know client's RTP capabilities, those are sent using initialization\n                // message and are stored in connection struct for future use\n                self.client_rtp_capabilities.replace(rtp_capabilities);\n            }\n            ClientMessage::SetConsumerPreferredLayers {\n                id,\n                preferred_layers,\n            } => {\n                if let Some(consumer) = self.consumers.get(&id).cloned() {\n                    actix::spawn(async move {\n                        match consumer.set_preferred_layers(preferred_layers).await {\n                            Ok(_) => {\n                                println!(\n                                    \"Successfully set preferred layers {:?} consumer {}\",\n                                    preferred_layers,\n                                    consumer.id(),\n                                );\n                            }\n                            Err(error) => {\n                                println!(\n                                    \"Failed to set preferred layers {:?} consumer {}: {}\",\n                                    preferred_layers,\n                                    consumer.id(),\n                                    error,\n                                );\n                            }\n                        }\n                    });\n                }\n            }\n            ClientMessage::ConnectProducerTransport { dtls_parameters } => {\n                let address = ctx.address();\n                let transport = self.transports.producer.clone();\n                // Establish connection for producer transport using DTLS parameters received\n                // from the client, but doing so in a background task since this handler is\n                // synchronous\n                actix::spawn(async move {\n                    match transport\n                        .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                        .await\n                    {\n                        Ok(_) => {\n                            address.do_send(ServerMessage::ConnectedProducerTransport);\n                            println!(\"Producer transport connected\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to connect producer transport: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::Produce {\n                kind,\n                rtp_parameters,\n            } => {\n                let address = ctx.address();\n                let transport = self.transports.producer.clone();\n                // Use producer transport to create a new producer on the server with given RTP\n                // parameters\n                actix::spawn(async move {\n                    match transport\n                        .produce(ProducerOptions::new(kind, rtp_parameters))\n                        .await\n                    {\n                        Ok(producer) => {\n                            let id = producer.id();\n                            address.do_send(ServerMessage::Produced { id });\n                            // Producer is stored in a hashmap since if we don't do it, it will get\n                            // destroyed as soon as its instance goes out out scope\n                            address.do_send(InternalMessage::SaveProducer(producer));\n                            println!(\"{kind:?} producer created: {id}\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to create {kind:?} producer: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::ConnectConsumerTransport { dtls_parameters } => {\n                let address = ctx.address();\n                let transport = self.transports.consumer.clone();\n                // The same as producer transport, but for consumer transport\n                actix::spawn(async move {\n                    match transport\n                        .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                        .await\n                    {\n                        Ok(_) => {\n                            address.do_send(ServerMessage::ConnectedConsumerTransport);\n                            println!(\"Consumer transport connected\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to connect consumer transport: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::Consume { producer_id } => {\n                let address = ctx.address();\n                let transport = self.transports.consumer.clone();\n                let rtp_capabilities = match self.client_rtp_capabilities.clone() {\n                    Some(rtp_capabilities) => rtp_capabilities,\n                    None => {\n                        eprintln!(\"Client should send RTP capabilities before consuming\");\n                        return;\n                    }\n                };\n                // Create consumer for given producer ID, while first making sure that RTP\n                // capabilities were sent by the client prior to that\n                actix::spawn(async move {\n                    let mut options = ConsumerOptions::new(producer_id, rtp_capabilities);\n                    options.paused = true;\n\n                    match transport.consume(options).await {\n                        Ok(consumer) => {\n                            let id = consumer.id();\n                            let kind = consumer.kind();\n                            let rtp_parameters = consumer.rtp_parameters().clone();\n                            address.do_send(ServerMessage::Consumed {\n                                id,\n                                producer_id,\n                                kind,\n                                rtp_parameters,\n                            });\n                            // Consumer is stored in a hashmap since if we don't do it, it will get\n                            // destroyed as soon as its instance goes out out scope\n                            address.do_send(InternalMessage::SaveConsumer(consumer));\n                            println!(\"{kind:?} consumer created: {id}\");\n                        }\n                        Err(error) => {\n                            eprintln!(\"Failed to create consumer: {error}\");\n                            address.do_send(InternalMessage::Stop);\n                        }\n                    }\n                });\n            }\n            ClientMessage::ConsumerResume { id } => {\n                if let Some(consumer) = self.consumers.get(&id).cloned() {\n                    actix::spawn(async move {\n                        match consumer.resume().await {\n                            Ok(_) => {\n                                println!(\n                                    \"Successfully resumed {:?} consumer {}\",\n                                    consumer.kind(),\n                                    consumer.id(),\n                                );\n                            }\n                            Err(error) => {\n                                println!(\n                                    \"Failed to resume {:?} consumer {}: {}\",\n                                    consumer.kind(),\n                                    consumer.id(),\n                                    error,\n                                );\n                            }\n                        }\n                    });\n                }\n            }\n        }\n    }\n}\n\n/// Simple handler that will transform typed server messages into JSON and send them over to the\n/// client over WebSocket connection\nimpl Handler<ServerMessage> for SvcSimulcastConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) {\n        ctx.text(serde_json::to_string(&message).unwrap());\n    }\n}\n\n/// Convenience handler for internal messages, these actions require mutable access to the\n/// connection struct and having such message handler makes it easy to use from background tasks\n/// where otherwise Mutex would have to be used instead\nimpl Handler<InternalMessage> for SvcSimulcastConnection {\n    type Result = ();\n\n    fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) {\n        match message {\n            InternalMessage::Stop => {\n                ctx.stop();\n            }\n            InternalMessage::SaveProducer(producer) => {\n                // Retain producer to prevent it from being destroyed\n                self.producers.push(producer);\n            }\n            InternalMessage::SaveConsumer(consumer) => {\n                self.consumers.insert(consumer.id(), consumer);\n            }\n        }\n    }\n}\n\n/// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nasync fn ws_index(\n    request: HttpRequest,\n    worker_manager: Data<WorkerManager>,\n    stream: Payload,\n) -> Result<HttpResponse, Error> {\n    match SvcSimulcastConnection::new(&worker_manager).await {\n        Ok(svc_simulcast_connection) => ws::start(svc_simulcast_connection, &request, stream),\n        Err(error) => {\n            eprintln!(\"{error}\");\n\n            Ok(HttpResponse::InternalServerError().finish())\n        }\n    }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    env_logger::init();\n\n    // We will reuse the same worker manager across all connections, this is more than enough for\n    // this use case\n    let worker_manager = Data::new(WorkerManager::new());\n    HttpServer::new(move || {\n        App::new()\n            .app_data(worker_manager.clone())\n            .route(\"/ws\", web::get().to(ws_index))\n    })\n    // 2 threads is plenty for this example, default is to have as many threads as CPU cores\n    .workers(2)\n    .bind(\"127.0.0.1:3000\")?\n    .run()\n    .await\n}\n"
  },
  {
    "path": "rust/examples/videoroom.rs",
    "content": "use crate::participant::ParticipantConnection;\nuse crate::room::RoomId;\nuse crate::rooms_registry::RoomsRegistry;\nuse actix_web::web::{Data, Payload, Query};\nuse actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};\nuse actix_web_actors::ws;\nuse mediasoup::prelude::*;\nuse serde::Deserialize;\nuse std::num::{NonZeroU32, NonZeroU8};\n\nmod room {\n    use crate::participant::ParticipantId;\n    use event_listener_primitives::{Bag, BagOnce, HandlerId};\n    use mediasoup::prelude::*;\n    use mediasoup::worker::{WorkerLogLevel, WorkerLogTag};\n    use parking_lot::Mutex;\n    use serde::{Deserialize, Serialize};\n    use std::collections::HashMap;\n    use std::fmt;\n    use std::sync::{Arc, Weak};\n    use uuid::Uuid;\n\n    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]\n    pub struct RoomId(Uuid);\n\n    impl fmt::Display for RoomId {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            fmt::Display::fmt(&self.0, f)\n        }\n    }\n\n    impl RoomId {\n        pub fn new() -> Self {\n            Self(Uuid::new_v4())\n        }\n    }\n\n    #[derive(Default)]\n    #[allow(clippy::type_complexity)]\n    struct Handlers {\n        producer_add:\n            Bag<Arc<dyn Fn(&ParticipantId, &Producer) + Send + Sync>, ParticipantId, Producer>,\n        producer_remove:\n            Bag<Arc<dyn Fn(&ParticipantId, &ProducerId) + Send + Sync>, ParticipantId, ProducerId>,\n        close: BagOnce<Box<dyn FnOnce() + Send>>,\n    }\n\n    struct Inner {\n        id: RoomId,\n        router: Router,\n        handlers: Handlers,\n        clients: Mutex<HashMap<ParticipantId, Vec<Producer>>>,\n    }\n\n    impl fmt::Debug for Inner {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            f.debug_struct(\"Inner\")\n                .field(\"id\", &self.id)\n                .field(\"handlers\", &\"...\")\n                .field(\"clients\", &self.clients)\n                .finish()\n        }\n    }\n\n    impl Drop for Inner {\n        fn drop(&mut self) {\n            println!(\"Room {} closed\", self.id);\n\n            self.handlers.close.call_simple();\n        }\n    }\n\n    /// Room holds producers of the participants such that other participants can consume audio and\n    /// video tracks of each other\n    #[derive(Debug, Clone)]\n    pub struct Room {\n        inner: Arc<Inner>,\n    }\n\n    impl Room {\n        /// Create new `Room` with random `RoomId`\n        pub async fn new(worker_manager: &WorkerManager) -> Result<Self, String> {\n            Self::new_with_id(worker_manager, RoomId::new()).await\n        }\n\n        /// Create new `Room` with a specific `RoomId`\n        pub async fn new_with_id(\n            worker_manager: &WorkerManager,\n            id: RoomId,\n        ) -> Result<Room, String> {\n            let worker = worker_manager\n                .create_worker({\n                    let mut settings = WorkerSettings::default();\n                    settings.log_level = WorkerLogLevel::Debug;\n                    settings.log_tags = vec![\n                        WorkerLogTag::Info,\n                        WorkerLogTag::Ice,\n                        WorkerLogTag::Dtls,\n                        WorkerLogTag::Rtp,\n                        WorkerLogTag::Srtp,\n                        WorkerLogTag::Rtcp,\n                        WorkerLogTag::Rtx,\n                        WorkerLogTag::Bwe,\n                        WorkerLogTag::Score,\n                        WorkerLogTag::Simulcast,\n                        WorkerLogTag::Svc,\n                        WorkerLogTag::Sctp,\n                        WorkerLogTag::Message,\n                    ];\n\n                    settings\n                })\n                .await\n                .map_err(|error| format!(\"Failed to create worker: {error}\"))?;\n            let router = worker\n                .create_router(RouterOptions::new(crate::media_codecs()))\n                .await\n                .map_err(|error| format!(\"Failed to create router: {error}\"))?;\n\n            println!(\"Room {id} created\");\n\n            Ok(Self {\n                inner: Arc::new(Inner {\n                    id,\n                    router,\n                    handlers: Handlers::default(),\n                    clients: Mutex::default(),\n                }),\n            })\n        }\n\n        /// ID of the room\n        pub fn id(&self) -> RoomId {\n            self.inner.id\n        }\n\n        /// Get router associated with this room\n        pub fn router(&self) -> &Router {\n            &self.inner.router\n        }\n\n        /// Add producer to the room, this will trigger notifications to other participants that\n        /// will be able to consume it\n        pub fn add_producer(&self, participant_id: ParticipantId, producer: Producer) {\n            self.inner\n                .clients\n                .lock()\n                .entry(participant_id)\n                .or_default()\n                .push(producer.clone());\n\n            self.inner\n                .handlers\n                .producer_add\n                .call_simple(&participant_id, &producer);\n        }\n\n        /// Remove participant and all of its associated producers\n        pub fn remove_participant(&self, participant_id: &ParticipantId) {\n            let producers = self.inner.clients.lock().remove(participant_id);\n\n            for producer in producers.unwrap_or_default() {\n                let producer_id = &producer.id();\n                self.inner\n                    .handlers\n                    .producer_remove\n                    .call_simple(participant_id, producer_id);\n            }\n        }\n\n        /// Get all producers of all participants, useful when new participant connects and needs to\n        /// consume tracks of everyone who is already in the room\n        pub fn get_all_producers(&self) -> Vec<(ParticipantId, ProducerId)> {\n            self.inner\n                .clients\n                .lock()\n                .iter()\n                .flat_map(|(participant_id, producers)| {\n                    let participant_id = *participant_id;\n                    producers\n                        .iter()\n                        .map(move |producer| (participant_id, producer.id()))\n                })\n                .collect()\n        }\n\n        /// Subscribe to notifications when new producer is added to the room\n        pub fn on_producer_add<F: Fn(&ParticipantId, &Producer) + Send + Sync + 'static>(\n            &self,\n            callback: F,\n        ) -> HandlerId {\n            self.inner.handlers.producer_add.add(Arc::new(callback))\n        }\n\n        /// Subscribe to notifications when producer is removed from the room\n        pub fn on_producer_remove<F: Fn(&ParticipantId, &ProducerId) + Send + Sync + 'static>(\n            &self,\n            callback: F,\n        ) -> HandlerId {\n            self.inner.handlers.producer_remove.add(Arc::new(callback))\n        }\n\n        /// Subscribe to notification when room is closed\n        pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n            self.inner.handlers.close.add(Box::new(callback))\n        }\n\n        /// Get `WeakRoom` that can later be upgraded to `Room`, but will not prevent room from\n        /// being destroyed\n        pub fn downgrade(&self) -> WeakRoom {\n            WeakRoom {\n                inner: Arc::downgrade(&self.inner),\n            }\n        }\n    }\n\n    /// Similar to `Room`, but doesn't prevent room from being destroyed\n    #[derive(Debug, Clone)]\n    pub struct WeakRoom {\n        inner: Weak<Inner>,\n    }\n\n    impl WeakRoom {\n        /// Upgrade `WeakRoom` to `Room`, may return `None` if underlying room was destroyed already\n        pub fn upgrade(&self) -> Option<Room> {\n            self.inner.upgrade().map(|inner| Room { inner })\n        }\n    }\n}\n\nmod rooms_registry {\n    use crate::room::{Room, RoomId, WeakRoom};\n    use async_lock::Mutex;\n    use mediasoup::prelude::*;\n    use std::collections::hash_map::Entry;\n    use std::collections::HashMap;\n    use std::sync::Arc;\n\n    #[derive(Debug, Default, Clone)]\n    pub struct RoomsRegistry {\n        // We store `WeakRoom` instead of full `Room` to avoid cycles and to not prevent rooms from\n        // being destroyed when last participant disconnects\n        rooms: Arc<Mutex<HashMap<RoomId, WeakRoom>>>,\n    }\n\n    impl RoomsRegistry {\n        /// Retrieves existing room or creates a new one with specified `RoomId`\n        pub async fn get_or_create_room(\n            &self,\n            worker_manager: &WorkerManager,\n            room_id: RoomId,\n        ) -> Result<Room, String> {\n            let mut rooms = self.rooms.lock().await;\n            match rooms.entry(room_id) {\n                Entry::Occupied(mut entry) => match entry.get().upgrade() {\n                    Some(room) => Ok(room),\n                    None => {\n                        let room = Room::new_with_id(worker_manager, room_id).await?;\n                        entry.insert(room.downgrade());\n                        room.on_close({\n                            let room_id = room.id();\n                            let rooms = Arc::clone(&self.rooms);\n\n                            move || {\n                                std::thread::spawn(move || {\n                                    futures_lite::future::block_on(async move {\n                                        rooms.lock().await.remove(&room_id);\n                                    });\n                                });\n                            }\n                        })\n                        .detach();\n                        Ok(room)\n                    }\n                },\n                Entry::Vacant(entry) => {\n                    let room = Room::new_with_id(worker_manager, room_id).await?;\n                    entry.insert(room.downgrade());\n                    room.on_close({\n                        let room_id = room.id();\n                        let rooms = Arc::clone(&self.rooms);\n\n                        move || {\n                            std::thread::spawn(move || {\n                                futures_lite::future::block_on(async move {\n                                    rooms.lock().await.remove(&room_id);\n                                });\n                            });\n                        }\n                    })\n                    .detach();\n                    Ok(room)\n                }\n            }\n        }\n\n        /// Create new room with random `RoomId`\n        pub async fn create_room(&self, worker_manager: &WorkerManager) -> Result<Room, String> {\n            let mut rooms = self.rooms.lock().await;\n            let room = Room::new(worker_manager).await?;\n            rooms.insert(room.id(), room.downgrade());\n            room.on_close({\n                let room_id = room.id();\n                let rooms = Arc::clone(&self.rooms);\n\n                move || {\n                    std::thread::spawn(move || {\n                        futures_lite::future::block_on(async move {\n                            rooms.lock().await.remove(&room_id);\n                        });\n                    });\n                }\n            })\n            .detach();\n            Ok(room)\n        }\n    }\n}\n\nmod participant {\n    use crate::participant::messages::{\n        ClientMessage, InternalMessage, ServerMessage, TransportOptions,\n    };\n    use crate::room::Room;\n    use actix::prelude::*;\n    use actix_web_actors::ws;\n    use event_listener_primitives::HandlerId;\n    use mediasoup::prelude::*;\n    use serde::{Deserialize, Serialize};\n    use std::collections::HashMap;\n    use std::fmt;\n    use std::net::{IpAddr, Ipv4Addr};\n    use uuid::Uuid;\n\n    mod messages {\n        use crate::participant::ParticipantId;\n        use crate::room::RoomId;\n        use actix::prelude::*;\n        use mediasoup::prelude::*;\n        use serde::{Deserialize, Serialize};\n\n        /// Data structure containing all the necessary information about transport options required\n        /// from the server to establish transport connection on the client\n        #[derive(Serialize)]\n        #[serde(rename_all = \"camelCase\")]\n        pub struct TransportOptions {\n            pub id: TransportId,\n            pub dtls_parameters: DtlsParameters,\n            pub ice_candidates: Vec<IceCandidate>,\n            pub ice_parameters: IceParameters,\n        }\n\n        /// Server messages sent to the client\n        #[derive(Serialize, Message)]\n        #[serde(tag = \"action\")]\n        #[rtype(result = \"()\")]\n        #[allow(clippy::large_enum_variant)]\n        pub enum ServerMessage {\n            /// Initialization message with consumer/producer transport options and Router's RTP\n            /// capabilities necessary to establish WebRTC transport connection client-side\n            #[serde(rename_all = \"camelCase\")]\n            Init {\n                room_id: RoomId,\n                consumer_transport_options: TransportOptions,\n                producer_transport_options: TransportOptions,\n                router_rtp_capabilities: RtpCapabilitiesFinalized,\n            },\n            /// Notification that new producer was added to the room\n            #[serde(rename_all = \"camelCase\")]\n            ProducerAdded {\n                participant_id: ParticipantId,\n                producer_id: ProducerId,\n            },\n            /// Notification that producer was removed from the room\n            #[serde(rename_all = \"camelCase\")]\n            ProducerRemoved {\n                participant_id: ParticipantId,\n                producer_id: ProducerId,\n            },\n            /// Notification that producer transport was connected successfully (in case of error\n            /// connection is just dropped, in real-world application you probably want to handle it\n            /// better)\n            ConnectedProducerTransport,\n            /// Notification that producer was created on the server\n            #[serde(rename_all = \"camelCase\")]\n            Produced { id: ProducerId },\n            /// Notification that consumer transport was connected successfully (in case of error\n            /// connection is just dropped, in real-world application you probably want to handle it\n            /// better)\n            ConnectedConsumerTransport,\n            /// Notification that consumer was successfully created server-side, client can resume\n            /// the consumer after this\n            #[serde(rename_all = \"camelCase\")]\n            Consumed {\n                id: ConsumerId,\n                producer_id: ProducerId,\n                kind: MediaKind,\n                rtp_parameters: RtpParameters,\n            },\n        }\n\n        /// Client messages sent to the server\n        #[derive(Deserialize, Message)]\n        #[serde(tag = \"action\")]\n        #[rtype(result = \"()\")]\n        pub enum ClientMessage {\n            /// Client-side initialization with its RTP capabilities, in this simple case we expect\n            /// those to match server Router's RTP capabilities\n            #[serde(rename_all = \"camelCase\")]\n            Init { rtp_capabilities: RtpCapabilities },\n            /// Request to connect producer transport with client-side DTLS parameters\n            #[serde(rename_all = \"camelCase\")]\n            ConnectProducerTransport { dtls_parameters: DtlsParameters },\n            /// Request to produce a new audio or video track with specified RTP parameters\n            #[serde(rename_all = \"camelCase\")]\n            Produce {\n                kind: MediaKind,\n                rtp_parameters: RtpParameters,\n            },\n            /// Request to connect consumer transport with client-side DTLS parameters\n            #[serde(rename_all = \"camelCase\")]\n            ConnectConsumerTransport { dtls_parameters: DtlsParameters },\n            /// Request to consume specified producer\n            #[serde(rename_all = \"camelCase\")]\n            Consume { producer_id: ProducerId },\n            /// Request to resume consumer that was previously created\n            #[serde(rename_all = \"camelCase\")]\n            ConsumerResume { id: ConsumerId },\n        }\n\n        /// Internal actor messages for convenience\n        #[derive(Message)]\n        #[rtype(result = \"()\")]\n        pub enum InternalMessage {\n            /// Save producer in connection-specific hashmap to prevent it from being destroyed\n            SaveProducer(Producer),\n            /// Save consumer in connection-specific hashmap to prevent it from being destroyed\n            SaveConsumer(Consumer),\n            /// Stop/close the WebSocket connection\n            Stop,\n        }\n    }\n\n    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]\n    pub struct ParticipantId(Uuid);\n\n    impl fmt::Display for ParticipantId {\n        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n            fmt::Display::fmt(&self.0, f)\n        }\n    }\n\n    impl ParticipantId {\n        fn new() -> Self {\n            Self(Uuid::new_v4())\n        }\n    }\n\n    /// Consumer/producer transports pair for the client\n    struct Transports {\n        consumer: WebRtcTransport,\n        producer: WebRtcTransport,\n    }\n\n    /// Actor that will represent WebSocket connection from the client, it will handle inbound and\n    /// outbound WebSocket messages in JSON.\n    ///\n    /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\n    pub struct ParticipantConnection {\n        id: ParticipantId,\n        /// RTP capabilities received from the client\n        client_rtp_capabilities: Option<RtpCapabilities>,\n        /// Consumers associated with this client, preventing them from being destroyed\n        consumers: HashMap<ConsumerId, Consumer>,\n        /// Producers associated with this client, preventing them from being destroyed\n        producers: Vec<Producer>,\n        /// Consumer and producer transports associated with this client\n        transports: Transports,\n        /// Room to which the client belongs\n        room: Room,\n        /// Event handlers that were attached and need to be removed when participant connection is\n        /// destroyed\n        attached_handlers: Vec<HandlerId>,\n    }\n\n    impl Drop for ParticipantConnection {\n        fn drop(&mut self) {\n            self.room.remove_participant(&self.id);\n        }\n    }\n\n    impl ParticipantConnection {\n        /// Create a new instance representing WebSocket connection\n        pub async fn new(room: Room) -> Result<Self, String> {\n            // We know that for videoroom example we'll need 2 transports, so we can create both\n            // right away. This may not be the case for real-world applications or you may create\n            // this at a different time and/or in different order.\n            let transport_options =\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }));\n            let producer_transport = room\n                .router()\n                .create_webrtc_transport(transport_options.clone())\n                .await\n                .map_err(|error| format!(\"Failed to create producer transport: {error}\"))?;\n\n            let consumer_transport = room\n                .router()\n                .create_webrtc_transport(transport_options)\n                .await\n                .map_err(|error| format!(\"Failed to create consumer transport: {error}\"))?;\n\n            Ok(Self {\n                id: ParticipantId::new(),\n                client_rtp_capabilities: None,\n                consumers: HashMap::new(),\n                producers: vec![],\n                transports: Transports {\n                    consumer: consumer_transport,\n                    producer: producer_transport,\n                },\n                room,\n                attached_handlers: Vec::new(),\n            })\n        }\n    }\n\n    impl Actor for ParticipantConnection {\n        type Context = ws::WebsocketContext<Self>;\n\n        fn started(&mut self, ctx: &mut Self::Context) {\n            println!(\"[participant_id {}] WebSocket connection created\", self.id);\n\n            // We know that both consumer and producer transports will be used, so we sent server\n            // information about both in an initialization message alongside with router\n            // capabilities to the client right after WebSocket connection is established\n            let server_init_message = ServerMessage::Init {\n                room_id: self.room.id(),\n                consumer_transport_options: TransportOptions {\n                    id: self.transports.consumer.id(),\n                    dtls_parameters: self.transports.consumer.dtls_parameters(),\n                    ice_candidates: self.transports.consumer.ice_candidates().clone(),\n                    ice_parameters: self.transports.consumer.ice_parameters().clone(),\n                },\n                producer_transport_options: TransportOptions {\n                    id: self.transports.producer.id(),\n                    dtls_parameters: self.transports.producer.dtls_parameters(),\n                    ice_candidates: self.transports.producer.ice_candidates().clone(),\n                    ice_parameters: self.transports.producer.ice_parameters().clone(),\n                },\n                router_rtp_capabilities: self.room.router().rtp_capabilities().clone(),\n            };\n\n            let address = ctx.address();\n            address.do_send(server_init_message);\n\n            // Listen for new producers added to the room\n            self.attached_handlers.push(self.room.on_producer_add({\n                let own_participant_id = self.id;\n                let address = address.clone();\n\n                move |participant_id, producer| {\n                    if &own_participant_id == participant_id {\n                        return;\n                    }\n                    address.do_send(ServerMessage::ProducerAdded {\n                        participant_id: *participant_id,\n                        producer_id: producer.id(),\n                    });\n                }\n            }));\n\n            // Listen for producers removed from the the room\n            self.attached_handlers.push(self.room.on_producer_remove({\n                let own_participant_id = self.id;\n                let address = address.clone();\n\n                move |participant_id, producer_id| {\n                    if &own_participant_id == participant_id {\n                        return;\n                    }\n                    address.do_send(ServerMessage::ProducerRemoved {\n                        participant_id: *participant_id,\n                        producer_id: *producer_id,\n                    });\n                }\n            }));\n\n            // Notify client about any producers that already exist in the room\n            for (participant_id, producer_id) in self.room.get_all_producers() {\n                address.do_send(ServerMessage::ProducerAdded {\n                    participant_id,\n                    producer_id,\n                });\n            }\n        }\n\n        fn stopped(&mut self, _ctx: &mut Self::Context) {\n            println!(\"[participant_id {0}] WebSocket connection closed\", self.id);\n        }\n    }\n\n    impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for ParticipantConnection {\n        fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {\n            // Here we handle incoming WebSocket messages, intentionally not handling continuation\n            // messages since we know all messages will fit into a single frame, but in real-world\n            // apps you need to handle continuation frames too (`ws::Message::Continuation`)\n            match msg {\n                Ok(ws::Message::Ping(msg)) => {\n                    ctx.pong(&msg);\n                }\n                Ok(ws::Message::Pong(_)) => {}\n                Ok(ws::Message::Text(text)) => match serde_json::from_str::<ClientMessage>(&text) {\n                    Ok(message) => {\n                        // Parse JSON into an enum and just send it back to the actor to be\n                        // processed by another handler below, it is much more convenient to just\n                        // parse it in one place and have typed data structure everywhere else\n                        ctx.address().do_send(message);\n                    }\n                    Err(error) => {\n                        eprintln!(\"Failed to parse client message: {error}\\n{text}\");\n                    }\n                },\n                Ok(ws::Message::Binary(bin)) => {\n                    eprintln!(\"Unexpected binary message: {bin:?}\");\n                }\n                Ok(ws::Message::Close(reason)) => {\n                    ctx.close(reason);\n                    ctx.stop();\n                }\n                _ => ctx.stop(),\n            }\n        }\n    }\n\n    impl Handler<ClientMessage> for ParticipantConnection {\n        type Result = ();\n\n        fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) {\n            match message {\n                ClientMessage::Init { rtp_capabilities } => {\n                    // We need to know client's RTP capabilities, those are sent using\n                    // initialization message and are stored in connection struct for future use\n                    self.client_rtp_capabilities.replace(rtp_capabilities);\n                }\n                ClientMessage::ConnectProducerTransport { dtls_parameters } => {\n                    let participant_id = self.id;\n                    let address = ctx.address();\n                    let transport = self.transports.producer.clone();\n                    // Establish connection for producer transport using DTLS parameters received\n                    // from the client, but doing so in a background task since this handler is\n                    // synchronous\n                    actix::spawn(async move {\n                        match transport\n                            .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                            .await\n                        {\n                            Ok(_) => {\n                                address.do_send(ServerMessage::ConnectedProducerTransport);\n                                println!(\n                                    \"[participant_id {participant_id}] Producer transport connected\"\n                                );\n                            }\n                            Err(error) => {\n                                eprintln!(\"Failed to connect producer transport: {error}\");\n                                address.do_send(InternalMessage::Stop);\n                            }\n                        }\n                    });\n                }\n                ClientMessage::Produce {\n                    kind,\n                    rtp_parameters,\n                } => {\n                    let participant_id = self.id;\n                    let address = ctx.address();\n                    let transport = self.transports.producer.clone();\n                    let room = self.room.clone();\n                    // Use producer transport to create a new producer on the server with given RTP\n                    // parameters\n                    actix::spawn(async move {\n                        match transport\n                            .produce(ProducerOptions::new(kind, rtp_parameters))\n                            .await\n                        {\n                            Ok(producer) => {\n                                let id = producer.id();\n                                address.do_send(ServerMessage::Produced { id });\n                                // Add producer to the room so that others can consume it\n                                room.add_producer(participant_id, producer.clone());\n                                // Producer is stored in a hashmap since if we don't do it, it will\n                                // get destroyed as soon as its instance goes out out scope\n                                address.do_send(InternalMessage::SaveProducer(producer));\n                                println!(\n                                    \"[participant_id {participant_id}] {kind:?} producer created: {id}\"\n                                );\n                            }\n                            Err(error) => {\n                                eprintln!(\n                                    \"[participant_id {participant_id}] Failed to create {kind:?} producer: {error}\"\n                                );\n                                address.do_send(InternalMessage::Stop);\n                            }\n                        }\n                    });\n                }\n                ClientMessage::ConnectConsumerTransport { dtls_parameters } => {\n                    let participant_id = self.id;\n                    let address = ctx.address();\n                    let transport = self.transports.consumer.clone();\n                    // The same as producer transport, but for consumer transport\n                    actix::spawn(async move {\n                        match transport\n                            .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                            .await\n                        {\n                            Ok(_) => {\n                                address.do_send(ServerMessage::ConnectedConsumerTransport);\n                                println!(\n                                    \"[participant_id {participant_id}] Consumer transport connected\"\n                                );\n                            }\n                            Err(error) => {\n                                eprintln!(\n                                    \"[participant_id {participant_id}] Failed to connect consumer transport: {error}\"\n                                );\n                                address.do_send(InternalMessage::Stop);\n                            }\n                        }\n                    });\n                }\n                ClientMessage::Consume { producer_id } => {\n                    let participant_id = self.id;\n                    let address = ctx.address();\n                    let transport = self.transports.consumer.clone();\n                    let rtp_capabilities = match self.client_rtp_capabilities.clone() {\n                        Some(rtp_capabilities) => rtp_capabilities,\n                        None => {\n                            eprintln!(\n                                \"[participant_id {participant_id}] Client should send RTP capabilities before \\\n                                consuming\"\n                            );\n                            return;\n                        }\n                    };\n                    // Create consumer for given producer ID, while first making sure that RTP\n                    // capabilities were sent by the client prior to that\n                    actix::spawn(async move {\n                        let mut options = ConsumerOptions::new(producer_id, rtp_capabilities);\n                        options.paused = true;\n\n                        match transport.consume(options).await {\n                            Ok(consumer) => {\n                                let id = consumer.id();\n                                let kind = consumer.kind();\n                                let rtp_parameters = consumer.rtp_parameters().clone();\n                                address.do_send(ServerMessage::Consumed {\n                                    id,\n                                    producer_id,\n                                    kind,\n                                    rtp_parameters,\n                                });\n                                // Consumer is stored in a hashmap since if we don't do it, it will\n                                // get destroyed as soon as its instance goes out out scope\n                                address.do_send(InternalMessage::SaveConsumer(consumer));\n                                println!(\n                                    \"[participant_id {participant_id}] {kind:?} consumer created: {id}\"\n                                );\n                            }\n                            Err(error) => {\n                                eprintln!(\n                                    \"[participant_id {participant_id}] Failed to create consumer: {error}\"\n                                );\n                                address.do_send(InternalMessage::Stop);\n                            }\n                        }\n                    });\n                }\n                ClientMessage::ConsumerResume { id } => {\n                    if let Some(consumer) = self.consumers.get(&id).cloned() {\n                        let participant_id = self.id;\n                        actix::spawn(async move {\n                            match consumer.resume().await {\n                                Ok(_) => {\n                                    println!(\n                                        \"[participant_id {}] Successfully resumed {:?} consumer {}\",\n                                        participant_id,\n                                        consumer.kind(),\n                                        consumer.id(),\n                                    );\n                                }\n                                Err(error) => {\n                                    println!(\n                                        \"[participant_id {}] Failed to resume {:?} consumer {}: {}\",\n                                        participant_id,\n                                        consumer.kind(),\n                                        consumer.id(),\n                                        error,\n                                    );\n                                }\n                            }\n                        });\n                    }\n                }\n            }\n        }\n    }\n\n    /// Simple handler that will transform typed server messages into JSON and send them over to the\n    /// client over WebSocket connection\n    impl Handler<ServerMessage> for ParticipantConnection {\n        type Result = ();\n\n        fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) {\n            ctx.text(serde_json::to_string(&message).unwrap());\n        }\n    }\n\n    /// Convenience handler for internal messages, these actions require mutable access to the\n    /// connection struct and having such message handler makes it easy to use from background tasks\n    /// where otherwise Mutex would have to be used instead\n    impl Handler<InternalMessage> for ParticipantConnection {\n        type Result = ();\n\n        fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) {\n            match message {\n                InternalMessage::Stop => {\n                    ctx.stop();\n                }\n                InternalMessage::SaveProducer(producer) => {\n                    // Retain producer to prevent it from being destroyed\n                    self.producers.push(producer);\n                }\n                InternalMessage::SaveConsumer(consumer) => {\n                    self.consumers.insert(consumer.id(), consumer);\n                }\n            }\n        }\n    }\n}\n\n/// List of codecs that SFU will accept from clients\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"useinbandfec\", 1_u32.into())]),\n            rtcp_feedback: vec![RtcpFeedback::TransportCc],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![\n                RtcpFeedback::Nack,\n                RtcpFeedback::NackPli,\n                RtcpFeedback::CcmFir,\n                RtcpFeedback::GoogRemb,\n                RtcpFeedback::TransportCc,\n            ],\n        },\n    ]\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct QueryParameters {\n    room_id: Option<RoomId>,\n}\n\n/// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection.\n///\n/// See https://actix.rs/docs/websockets/ for official `actix-web` documentation.\nasync fn ws_index(\n    query_parameters: Query<QueryParameters>,\n    request: HttpRequest,\n    worker_manager: Data<WorkerManager>,\n    rooms_registry: Data<RoomsRegistry>,\n    stream: Payload,\n) -> Result<HttpResponse, Error> {\n    let room = match query_parameters.room_id {\n        Some(room_id) => {\n            rooms_registry\n                .get_or_create_room(&worker_manager, room_id)\n                .await\n        }\n        None => rooms_registry.create_room(&worker_manager).await,\n    };\n\n    let room = match room {\n        Ok(room) => room,\n        Err(error) => {\n            eprintln!(\"{error}\");\n\n            return Ok(HttpResponse::InternalServerError().finish());\n        }\n    };\n\n    match ParticipantConnection::new(room).await {\n        Ok(echo_server) => ws::start(echo_server, &request, stream),\n        Err(error) => {\n            eprintln!(\"{error}\");\n\n            Ok(HttpResponse::InternalServerError().finish())\n        }\n    }\n}\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    env_logger::init();\n\n    // We will reuse the same worker manager across all connections, this is more than enough for\n    // this use case\n    let worker_manager = Data::new(WorkerManager::new());\n    // Rooms registry will hold all the active rooms\n    let rooms_registry = Data::new(RoomsRegistry::default());\n    HttpServer::new(move || {\n        App::new()\n            .app_data(worker_manager.clone())\n            .app_data(rooms_registry.clone())\n            .route(\"/ws\", web::get().to(ws_index))\n    })\n    // 2 threads is plenty for this example, default is to have as many threads as CPU cores\n    .workers(2)\n    .bind(\"127.0.0.1:3000\")?\n    .run()\n    .await\n}\n"
  },
  {
    "path": "rust/examples-frontend/echo/.eslintrc.js",
    "content": "module.exports = {\n\tenv : {\n\t\tbrowser : true,\n\t\tes6     : true\n\t},\n\tparserOptions : {\n\t\tproject    : 'tsconfig.json',\n\t\tsourceType : 'module'\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/echo/index.html",
    "content": "<!doctype html>\n<style>\n    body {\n        margin: 0;\n    }\n\n    #container {\n        display: flex;\n    }\n\n    figure {\n        margin: 0;\n        width: 50vw;\n    }\n\n    video {\n        max-width: 100%;\n    }\n</style>\n<div id=\"container\">\n    <figure>\n        <video id=\"preview-send\" muted controls></video>\n        <figcaption>Send preview</figcaption>\n    </figure>\n    <figure>\n        <video id=\"preview-receive\" muted controls></video>\n        <figcaption>Receive preview</figcaption>\n    </figure>\n</div>\n<script src=\"dist/bundle.js\">\n</script>\n"
  },
  {
    "path": "rust/examples-frontend/echo/package.json",
    "content": "{\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=14.8\"\n  },\n  \"scripts\": {\n    \"start\": \"webpack serve\"\n  },\n  \"author\": \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n  \"dependencies\": {\n    \"mediasoup-client\": \"^3.6.51\"\n  },\n  \"devDependencies\": {\n    \"ts-loader\": \"^9.2.8\",\n    \"typescript\": \"^4.6.3\",\n    \"webpack\": \"^5.71.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^5.2.2\"\n  }\n}\n"
  },
  {
    "path": "rust/examples-frontend/echo/src/index.ts",
    "content": "/* eslint-disable no-console */\nimport { Device } from 'mediasoup-client';\nimport { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters';\nimport { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport';\nimport { ConsumerOptions } from 'mediasoup-client/lib/Consumer';\n\ntype Brand<K, T> = K & { __brand: T };\n\ntype ConsumerId = Brand<string, 'ConsumerId'>;\ntype ProducerId = Brand<string, 'ProducerId'>;\n\ninterface ServerInit {\n\taction: 'Init';\n\tconsumerTransportOptions: TransportOptions;\n\tproducerTransportOptions: TransportOptions;\n\trouterRtpCapabilities: RtpCapabilities;\n}\n\ninterface ServerConnectedProducerTransport {\n\taction: 'ConnectedProducerTransport';\n}\n\ninterface ServerProduced {\n\taction: 'Produced';\n\tid: ProducerId;\n}\n\ninterface ServerConnectedConsumerTransport {\n\taction: 'ConnectedConsumerTransport';\n}\n\ninterface ServerConsumed {\n\taction: 'Consumed';\n\tid: ConsumerId;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ntype ServerMessage =\n\tServerInit |\n\tServerConnectedProducerTransport |\n\tServerProduced |\n\tServerConnectedConsumerTransport |\n\tServerConsumed;\n\ninterface ClientInit {\n\taction: 'Init';\n\trtpCapabilities: RtpCapabilities;\n}\n\ninterface ClientConnectProducerTransport {\n\taction: 'ConnectProducerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientConnectConsumerTransport {\n\taction: 'ConnectConsumerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientProduce {\n\taction: 'Produce';\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ninterface ClientConsume {\n\taction: 'Consume';\n\tproducerId: ProducerId;\n}\n\ninterface ClientConsumerResume {\n\taction: 'ConsumerResume';\n\tid: ConsumerId;\n}\n\ntype ClientMessage =\n\tClientInit |\n\tClientConnectProducerTransport |\n\tClientProduce |\n\tClientConnectConsumerTransport |\n\tClientConsume |\n\tClientConsumerResume;\n\nasync function init()\n{\n\tconst sendPreview = document.querySelector('#preview-send') as HTMLVideoElement;\n\tconst receivePreview = document.querySelector('#preview-receive') as HTMLVideoElement;\n\n\tsendPreview.onloadedmetadata = () =>\n\t{\n\t\tsendPreview.play();\n\t};\n\treceivePreview.onloadedmetadata = () =>\n\t{\n\t\treceivePreview.play();\n\t};\n\n\tconst receiveMediaStream = new MediaStream();\n\n\tconst ws = new WebSocket('ws://localhost:3000/ws');\n\n\tfunction send(message: ClientMessage)\n\t{\n\t\tws.send(JSON.stringify(message));\n\t}\n\n\tconst device = new Device();\n\tlet producerTransport: Transport | undefined;\n\tlet consumerTransport: Transport | undefined;\n\n\t{\n\t\tconst waitingForResponse: Map<ServerMessage['action'], Function> = new Map();\n\n\t\tws.onmessage = async (message) =>\n\t\t{\n\t\t\tconst decodedMessage: ServerMessage = JSON.parse(message.data);\n\n\t\t\tswitch (decodedMessage.action)\n\t\t\t{\n\t\t\t\tcase 'Init': {\n\t\t\t\t\t// It is expected that server will send initialization message right after\n\t\t\t\t\t// WebSocket connection is established\n\t\t\t\t\tawait device.load({\n\t\t\t\t\t\trouterRtpCapabilities : decodedMessage.routerRtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// Send client-side initialization message back right away\n\t\t\t\t\tsend({\n\t\t\t\t\t\taction          : 'Init',\n\t\t\t\t\t\trtpCapabilities : device.rtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// Producer transport is needed to send audio and video to SFU\n\t\t\t\t\tproducerTransport = device.createSendTransport(\n\t\t\t\t\t\tdecodedMessage.producerTransportOptions\n\t\t\t\t\t);\n\n\t\t\t\t\tproducerTransport\n\t\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to establish producer transport connection\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'ConnectProducerTransport',\n\t\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('ConnectedProducerTransport', () =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\t\tconsole.log('Producer transport connected');\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.on('produce', ({ kind, rtpParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Once connection is established, send request to produce\n\t\t\t\t\t\t\t// audio or video track\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'Produce',\n\t\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\trtpParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('Produced', ({ id }: {id: string}) =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess({ id });\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// Request microphone and camera access, in real-world apps you may want\n\t\t\t\t\t// to do this separately so that audio-only and video-only cases are\n\t\t\t\t\t// handled nicely instead of failing completely\n\t\t\t\t\tconst mediaStream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\t\taudio : true,\n\t\t\t\t\t\tvideo : {\n\t\t\t\t\t\t\twidth : {\n\t\t\t\t\t\t\t\tideal : 1280\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\theight : {\n\t\t\t\t\t\t\t\tideal : 720\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tframeRate : {\n\t\t\t\t\t\t\t\tideal : 60\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tsendPreview.srcObject = mediaStream;\n\n\t\t\t\t\tconst producers = [];\n\n\t\t\t\t\t// And create producers for all tracks that were previously requested\n\t\t\t\t\tfor (const track of mediaStream.getTracks())\n\t\t\t\t\t{\n\t\t\t\t\t\tconst producer = await producerTransport.produce({ track });\n\n\t\t\t\t\t\tproducers.push(producer);\n\t\t\t\t\t\tconsole.log(`${track.kind} producer created:`, producer);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Consumer transport is now needed to receive previously produced\n\t\t\t\t\t// tracks back\n\t\t\t\t\tconsumerTransport = device.createRecvTransport(\n\t\t\t\t\t\tdecodedMessage.consumerTransportOptions\n\t\t\t\t\t);\n\n\t\t\t\t\tconsumerTransport\n\t\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to establish consumer transport connection\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'ConnectConsumerTransport',\n\t\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('ConnectedConsumerTransport', () =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\t\tconsole.log('Consumer transport connected');\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// For simplicity of this example producers were stored in an array\n\t\t\t\t\t// and are now all consumed one at a time\n\t\t\t\t\tfor (const producer of producers)\n\t\t\t\t\t{\n\t\t\t\t\t\tawait new Promise((resolve) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to consume producer\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction     : 'Consume',\n\t\t\t\t\t\t\t\tproducerId : producer.id as ProducerId\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Once confirmation is received, corresponding consumer\n\t\t\t\t\t\t\t\t// can be created client-side\n\t\t\t\t\t\t\t\tconst consumer = await (consumerTransport as Transport).consume(\n\t\t\t\t\t\t\t\t\tconsumerOptions\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tconsole.log(`${consumer.kind} consumer created:`, consumer);\n\n\t\t\t\t\t\t\t\t// Consumer needs to be resumed after being created in\n\t\t\t\t\t\t\t\t// paused state (see official documentation about why:\n\t\t\t\t\t\t\t\t// https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume)\n\t\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\t\taction : 'ConsumerResume',\n\t\t\t\t\t\t\t\t\tid     : consumer.id as ConsumerId\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\treceiveMediaStream.addTrack(consumer.track);\n\t\t\t\t\t\t\t\treceivePreview.srcObject = receiveMediaStream;\n\n\t\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\t// All messages other than initialization go here and are assumed\n\t\t\t\t\t// to be notifications that correspond to previously sent requests\n\t\t\t\t\tconst callback = waitingForResponse.get(decodedMessage.action);\n\n\t\t\t\t\tif (callback)\n\t\t\t\t\t{\n\t\t\t\t\t\twaitingForResponse.delete(decodedMessage.action);\n\t\t\t\t\t\tcallback(decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tconsole.error('Received unexpected message', decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\tws.onerror = console.error;\n}\n\ninit();\n"
  },
  {
    "path": "rust/examples-frontend/echo/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es6\",\n    \"target\": \"es2018\",\n    \"sourceMap\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": false,\n    \"sourceRoot\": \"src\",\n    \"baseUrl\": \"src\",\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"noUnusedParameters\": true,\n    \"noUnusedLocals\": true,\n    \"lib\": [\n      \"es5\",\n      \"es2015\",\n      \"es2019\",\n      \"dom\"\n    ]\n  },\n  \"include\": [\n    \"src\",\n    \".eslintrc.js\",\n    \"webpack.config.js\"\n  ]\n}\n"
  },
  {
    "path": "rust/examples-frontend/echo/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n\tmode   : 'development',\n\tentry  : './src/index.ts',\n\tmodule : {\n\t\trules : [\n\t\t\t{\n\t\t\t\ttest    : /\\.ts$/,\n\t\t\t\tuse     : 'ts-loader',\n\t\t\t\texclude : /node_modules/\n\t\t\t}\n\t\t]\n\t},\n\tresolve : {\n\t\textensions : [ '.ts', '.js' ]\n\t},\n\toutput : {\n\t\tfilename : 'dist/bundle.js',\n\t\tpath     : path.resolve(__dirname, 'dist')\n\t},\n\tdevServer : {\n\t\tliveReload : false,\n\t\tport       : 3001,\n\t\tstatic     : {\n\t\t\tdirectory: __dirname\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/.eslintrc.js",
    "content": "module.exports = {\n\tenv : {\n\t\tbrowser : true,\n\t\tes6     : true\n\t},\n\tparserOptions : {\n\t\tproject    : 'tsconfig.json',\n\t\tsourceType : 'module'\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/index.html",
    "content": "<!doctype html>\n<style>\n    body {\n        margin: 0;\n    }\n\n    #container {\n        display: flex;\n    }\n\n    button {\n        display: none;\n    }\n\n    figure {\n        margin: 0;\n        width: 50vw;\n    }\n\n    audio {\n        max-width: 100%;\n    }\n\n    #container[waiting-for-click] button {\n        display: block;\n    }\n\n    #container[waiting-for-click] figure {\n        display: none;\n    }\n</style>\n<div id=\"container\" waiting-for-click>\n    <button id=\"connect\">Connect</button>\n    <figure>\n        <audio id=\"preview-receive\" controls></audio>\n        <figcaption>Receive preview (see server-side example logs for sending audio instructions)</figcaption>\n    </figure>\n</div>\n<script src=\"dist/bundle.js\">\n</script>\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/package.json",
    "content": "{\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=14.8\"\n  },\n  \"scripts\": {\n    \"start\": \"webpack serve\"\n  },\n  \"author\": \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n  \"dependencies\": {\n    \"mediasoup-client\": \"^3.6.51\"\n  },\n  \"devDependencies\": {\n    \"ts-loader\": \"^9.2.8\",\n    \"typescript\": \"^4.6.3\",\n    \"webpack\": \"^5.71.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^5.2.2\"\n  }\n}\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/src/index.ts",
    "content": "/* eslint-disable no-console */\nimport { Device } from 'mediasoup-client';\nimport { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters';\nimport { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport';\nimport { ConsumerOptions } from 'mediasoup-client/lib/Consumer';\nimport { getExtendedRtpCapabilities } from 'mediasoup-client/lib/ortc';\n\ntype Brand<K, T> = K & { __brand: T };\n\ntype ConsumerId = Brand<string, 'ConsumerId'>;\ntype ProducerId = Brand<string, 'ProducerId'>;\n\ninterface ServerInit {\n\taction: 'Init';\n\tconsumerTransportOptions: TransportOptions;\n\trouterRtpCapabilities: RtpCapabilities;\n\trtpProducerId: ProducerId;\n}\n\ninterface ServerConnectedConsumerTransport {\n\taction: 'ConnectedConsumerTransport';\n}\n\ninterface ServerConsumed {\n\taction: 'Consumed';\n\tid: ConsumerId;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ntype ServerMessage =\n\tServerInit |\n\tServerConnectedConsumerTransport |\n\tServerConsumed;\n\ninterface ClientInit {\n\taction: 'Init';\n\trtpCapabilities: RtpCapabilities;\n}\n\ninterface ClientConnectConsumerTransport {\n\taction: 'ConnectConsumerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientConsume {\n\taction: 'Consume';\n\tproducerId: ProducerId;\n}\n\ntype ClientMessage =\n\tClientInit |\n\tClientConnectConsumerTransport |\n\tClientConsume;\n\nasync function init()\n{\n\tconst receivePreview = document.querySelector('#preview-receive') as HTMLAudioElement;\n\n\treceivePreview.onloadedmetadata = () =>\n\t{\n\t\tconsole.log('onloadedmetadata');\n\t\treceivePreview.play();\n\t};\n\n\tconst receiveMediaStream = new MediaStream();\n\n\tconst ws = new WebSocket('ws://localhost:3000/ws');\n\n\tfunction send(message: ClientMessage)\n\t{\n\t\tws.send(JSON.stringify(message));\n\t}\n\n\tconst device = new Device();\n\tlet consumerTransport: Transport | undefined;\n\n\t{\n\t\tconst waitingForResponse: Map<ServerMessage['action'], Function> = new Map();\n\n\t\tws.onmessage = async (message) =>\n\t\t{\n\t\t\tconst decodedMessage: ServerMessage = JSON.parse(message.data);\n\n\t\t\tswitch (decodedMessage.action)\n\t\t\t{\n\t\t\t\tcase 'Init': {\n\t\t\t\t\t// It is expected that server will send initialization message right after\n\t\t\t\t\t// WebSocket connection is established\n\t\t\t\t\tawait device.load({\n\t\t\t\t\t\trouterRtpCapabilities : decodedMessage.routerRtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// TODO: Here we hardcode RTP capabilities, as Chromium doesn't really expose\n\t\t\t\t\t//  multiopus support in a meaningful way, so we need to hack it like this for\n\t\t\t\t\t//  now until `mediasoup-client` is updated to handle this case properly\n\t\t\t\t\t{\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tdevice._recvRtpCapabilities = {\n\t\t\t\t\t\t\tcodecs : [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tkind       : 'audio',\n\t\t\t\t\t\t\t\t\tmimeType   : 'audio/multiopus',\n\t\t\t\t\t\t\t\t\tclockRate  : 48000,\n\t\t\t\t\t\t\t\t\tchannels   : 6,\n\t\t\t\t\t\t\t\t\tparameters : {\n\t\t\t\t\t\t\t\t\t\tuseinbandfec      : 1,\n\t\t\t\t\t\t\t\t\t\t'channel_mapping' : '0,1,4,5,2,3',\n\t\t\t\t\t\t\t\t\t\t'num_streams'     : 4,\n\t\t\t\t\t\t\t\t\t\t'coupled_streams' : 2\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\trtcpFeedback : [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype      : 'transport-cc',\n\t\t\t\t\t\t\t\t\t\t\tparameter : ''\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\theaderExtensions : device.rtpCapabilities.headerExtensions\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tdevice._extendedRtpCapabilities = getExtendedRtpCapabilities(\n\t\t\t\t\t\t\tdevice.rtpCapabilities,\n\t\t\t\t\t\t\tdecodedMessage.routerRtpCapabilities\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Send client-side initialization message back right away.\n\t\t\t\t\tsend({\n\t\t\t\t\t\taction          : 'Init',\n\t\t\t\t\t\trtpCapabilities : device.rtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// Consumer transport is now needed to receive producer created on the server\n\t\t\t\t\tconsumerTransport = device.createRecvTransport(\n\t\t\t\t\t\tdecodedMessage.consumerTransportOptions\n\t\t\t\t\t);\n\n\t\t\t\t\t// TODO: Here we patch RTCPeerConnection in such a way that we can intercept\n\t\t\t\t\t//  and munge SDP answer until until `mediasoup-client` is updated to handle\n\t\t\t\t\t//  this case properly\n\t\t\t\t\t{\n\t\t\t\t\t\t// @ts-ignore\n\t\t\t\t\t\tconst pc = consumerTransport._handler._pc;\n\t\t\t\t\t\tconst setLocalDescription = pc.setLocalDescription;\n\n\t\t\t\t\t\tpc.setLocalDescription = (answer: {type: 'answer'; sdp: string}) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tanswer.sdp = answer.sdp.replace(\n\t\t\t\t\t\t\t\t'm=audio 9 UDP/TLS/RTP/SAVPF 0',\n\t\t\t\t\t\t\t\t`m=audio 9 UDP/TLS/RTP/SAVPF 100\na=rtpmap:100 multiopus/48000/6\na=fmtp:100 channel_mapping=0,4,1,2,3,5;coupled_streams=2;num_streams=4;useinbandfec=1`\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\treturn setLocalDescription.call(pc, answer);\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\n\t\t\t\t\tconsumerTransport\n\t\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to establish consumer transport connection\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'ConnectConsumerTransport',\n\t\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('ConnectedConsumerTransport', () =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\t\tconsole.log('Consumer transport connected');\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// We have just one producer and it is already created, so consuming is easy\n\t\t\t\t\tawait new Promise((resolve) =>\n\t\t\t\t\t{\n\t\t\t\t\t\t// Send request to consume producer\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\taction     : 'Consume',\n\t\t\t\t\t\t\tproducerId : decodedMessage.rtpProducerId\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\twaitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Once confirmation is received, corresponding consumer\n\t\t\t\t\t\t\t// can be created client-side\n\t\t\t\t\t\t\tconst consumer = await (consumerTransport as Transport).consume(\n\t\t\t\t\t\t\t\tconsumerOptions\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\tconsole.log(`${consumer.kind} consumer created:`, consumer);\n\n\t\t\t\t\t\t\treceiveMediaStream.addTrack(consumer.track);\n\t\t\t\t\t\t\treceivePreview.srcObject = receiveMediaStream;\n\n\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\t// All messages other than initialization go here and are assumed\n\t\t\t\t\t// to be notifications that correspond to previously sent requests\n\t\t\t\t\tconst callback = waitingForResponse.get(decodedMessage.action);\n\n\t\t\t\t\tif (callback)\n\t\t\t\t\t{\n\t\t\t\t\t\twaitingForResponse.delete(decodedMessage.action);\n\t\t\t\t\t\tcallback(decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tconsole.error('Received unexpected message', decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\tws.onerror = console.error;\n}\n\ndocument.querySelector('#connect')!\n\t.addEventListener('click', () =>\n\t{\n\t\tconst container = document.querySelector('#container')!;\n\n\t\tcontainer.removeAttribute('waiting-for-click');\n\n\t\tinit();\n\t});\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es6\",\n    \"target\": \"es2018\",\n    \"sourceMap\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": false,\n    \"sourceRoot\": \"src\",\n    \"baseUrl\": \"src\",\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"noUnusedParameters\": true,\n    \"noUnusedLocals\": true,\n    \"lib\": [\n      \"es5\",\n      \"es2015\",\n      \"es2019\",\n      \"dom\"\n    ]\n  },\n  \"include\": [\n    \"src\",\n    \".eslintrc.js\",\n    \"webpack.config.js\"\n  ]\n}\n"
  },
  {
    "path": "rust/examples-frontend/multiopus/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n\tmode   : 'development',\n\tentry  : './src/index.ts',\n\tmodule : {\n\t\trules : [\n\t\t\t{\n\t\t\t\ttest    : /\\.ts$/,\n\t\t\t\tuse     : 'ts-loader',\n\t\t\t\texclude : /node_modules/\n\t\t\t}\n\t\t]\n\t},\n\tresolve : {\n\t\textensions : [ '.ts', '.js' ]\n\t},\n\toutput : {\n\t\tfilename : 'dist/bundle.js',\n\t\tpath     : path.resolve(__dirname, 'dist')\n\t},\n\tdevServer : {\n\t\tliveReload : false,\n\t\tport       : 3001,\n\t\tstatic     : {\n\t\t\tdirectory: __dirname\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/readme.md",
    "content": "# Examples\nThis directory contains complementary frontend examples of using mediasoup Rust library.\n\nNOTE: These examples are simplified and provided for demo purposes only, they are by no means complete or representative\nof production-grade software, use for educational purposes only and refer to documentation for all possible options.\n\nIn order to run client-side part of examples (this directory) go to the directory with example and run following:\n```bash\nnpm install\nnpm start\n```\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/.eslintrc.js",
    "content": "module.exports = {\n\tenv : {\n\t\tbrowser : true,\n\t\tes6     : true\n\t},\n\tparserOptions : {\n\t\tproject    : 'tsconfig.json',\n\t\tsourceType : 'module'\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/index.html",
    "content": "<!doctype html>\n<style>\n    body {\n        margin: 0;\n    }\n\n    #container {\n        display: flex;\n        flex-direction: column;\n    }\n\n    figure {\n        margin: 0;\n        width: 50vw;\n    }\n\n    video {\n        max-width: 100%;\n    }\n\n    .previews {\n        display: flex;\n    }\n</style>\n<div id=\"container\">\n    <div class=\"previews\">\n        <figure>\n            <video id=\"preview-send\" muted controls></video>\n            <figcaption>Send preview</figcaption>\n        </figure>\n        <figure>\n            <video id=\"preview-receive\" muted controls></video>\n            <figcaption>Receive preview</figcaption>\n        </figure>\n    </div>\n\n    <p>\n        Video codec: <span id=\"video-codec\">?</span>\n    </p>\n    <div>\n        Switch layers: <button id=\"decreaseLayer\" type=\"button\">down</button>\n        S<span id=\"spatial\">?</span>T<span id=\"temporal\">?</span>\n        <button id=\"increaseLayer\" type=\"button\">up</button>\n    </div>\n</div>\n<script src=\"dist/bundle.js\">\n</script>\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/package.json",
    "content": "{\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=14.8\"\n  },\n  \"scripts\": {\n    \"start\": \"webpack serve\"\n  },\n  \"author\": \"Tarsis Maksym <tarsis.maksym@gmail.com>\",\n  \"dependencies\": {\n    \"mediasoup-client\": \"^3.6.51\"\n  },\n  \"devDependencies\": {\n    \"ts-loader\": \"^9.2.8\",\n    \"typescript\": \"^4.6.3\",\n    \"webpack\": \"^5.71.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^5.2.2\"\n  }\n}\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/src/index.ts",
    "content": "/* eslint-disable no-console */\nimport { Device, parseScalabilityMode } from 'mediasoup-client';\nimport { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters';\nimport { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport';\nimport { Consumer } from 'mediasoup-client/lib/types';\nimport { ConsumerOptions } from 'mediasoup-client/lib/Consumer';\n\ntype Brand<K, T> = K & { __brand: T };\n\ntype ConsumerId = Brand<string, 'ConsumerId'>;\ntype ProducerId = Brand<string, 'ProducerId'>;\n\ninterface ServerInit {\n\taction: 'Init';\n\tconsumerTransportOptions: TransportOptions;\n\tproducerTransportOptions: TransportOptions;\n\trouterRtpCapabilities: RtpCapabilities;\n}\n\ninterface ServerConnectedProducerTransport {\n\taction: 'ConnectedProducerTransport';\n}\n\ninterface ServerProduced {\n\taction: 'Produced';\n\tid: ProducerId;\n}\n\ninterface ServerConnectedConsumerTransport {\n\taction: 'ConnectedConsumerTransport';\n}\n\ninterface ServerConsumed {\n\taction: 'Consumed';\n\tid: ConsumerId;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ntype ServerMessage =\n\tServerInit |\n\tServerConnectedProducerTransport |\n\tServerProduced |\n\tServerConnectedConsumerTransport |\n\tServerConsumed;\n\ninterface ClientInit {\n\taction: 'Init';\n\trtpCapabilities: RtpCapabilities;\n}\n\ninterface ClientConnectProducerTransport {\n\taction: 'ConnectProducerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientConnectConsumerTransport {\n\taction: 'ConnectConsumerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientProduce {\n\taction: 'Produce';\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ninterface ClientConsume {\n\taction: 'Consume';\n\tproducerId: ProducerId;\n}\n\ninterface ClientConsumerResume {\n\taction: 'ConsumerResume';\n\tid: ConsumerId;\n}\n\ninterface ClientSetConsumerPreferredLayers {\n\taction: 'SetConsumerPreferredLayers';\n\tid: ConsumerId;\n\tpreferredLayers: {\n\t\tspatialLayer: number\n\t\ttemporalLayer: number\n\t}\n}\n\ntype ClientMessage =\n\tClientInit |\n\tClientConnectProducerTransport |\n\tClientProduce |\n\tClientConnectConsumerTransport |\n\tClientConsume |\n\tClientConsumerResume |\n\tClientSetConsumerPreferredLayers;\n\nasync function init()\n{\n\tconst isFirefox = navigator.userAgent.toLowerCase().includes('firefox')\n\tconst sendPreview = document.querySelector('#preview-send') as HTMLVideoElement;\n\tconst receivePreview = document.querySelector('#preview-receive') as HTMLVideoElement;\n\tconst videoCodec = document.querySelector('#video-codec') as HTMLSpanElement;\n\n\tsendPreview.onloadedmetadata = () =>\n\t{\n\t\tsendPreview.play();\n\t};\n\treceivePreview.onloadedmetadata = () =>\n\t{\n\t\treceivePreview.play();\n\t};\n\n\tconst decreaseLayer = document.querySelector('#decreaseLayer') as HTMLButtonElement;\n\tconst increaseLayer = document.querySelector('#increaseLayer') as HTMLButtonElement;\n\tconst spatialLayerNode = document.querySelector('#spatial') as HTMLSpanElement\n\tconst temporalLayerNode = document.querySelector('#temporal') as HTMLSpanElement\n\ttemporalLayerNode.innerText = 'none'\n\n\tlet videoConsumer: Consumer | null = null;\n\tlet maxSpatialLayer = 0;\n\tlet maxTemporalLayer = 0;\n\tlet preferredSpatialLayer = 0;\n\tlet preferredTemporalLayer = 0;\n\n\tdecreaseLayer.addEventListener('click', () => {\n\t\tlet newPreferredSpatialLayer: number;\n\t\tlet newPreferredTemporalLayer: number;\n\n\t\tif (preferredTemporalLayer > 0) {\n\t\t\tnewPreferredSpatialLayer = preferredSpatialLayer;\n\t\t\tnewPreferredTemporalLayer = preferredTemporalLayer - 1;\n\t\t} else if (preferredSpatialLayer > 0) {\n\t\t\tnewPreferredSpatialLayer = preferredSpatialLayer - 1;\n\t\t\tnewPreferredTemporalLayer = maxTemporalLayer;\n\t\t} else {\n\t\t\tnewPreferredSpatialLayer = maxSpatialLayer;\n\t\t\tnewPreferredTemporalLayer = maxTemporalLayer;\n\t\t}\n\n\t\tsetPreferredLayers(newPreferredSpatialLayer, newPreferredTemporalLayer)\n\t});\n\tincreaseLayer.addEventListener('click', () => {\n\t\tlet newPreferredSpatialLayer: number;\n\t\tlet newPreferredTemporalLayer: number;\n\n\t\tif (preferredTemporalLayer < 2) {\n\t\t\tnewPreferredSpatialLayer = preferredSpatialLayer;\n\t\t\tnewPreferredTemporalLayer = preferredTemporalLayer + 1;\n\t\t} else if (preferredSpatialLayer < 2) {\n\t\t\tnewPreferredSpatialLayer = preferredSpatialLayer + 1;\n\t\t\tnewPreferredTemporalLayer = 0;\n\t\t} else {\n\t\t\tnewPreferredSpatialLayer = 0;\n\t\t\tnewPreferredTemporalLayer = 0;\n\t\t}\n\n\t\tsetPreferredLayers(newPreferredSpatialLayer, newPreferredTemporalLayer)\n\t});\n\n\n\tconst setPreferredLayers = (spatialLayer: number, temporalLayer: number = 0): void => {\n\t\tif (!videoConsumer) {\n\t\t\tthrow new Error('Failed to update preferred layers: video consumer not found.')\n\t\t}\n\n\t\tpreferredSpatialLayer = spatialLayer\n\t\tpreferredTemporalLayer = temporalLayer\n\n\t\tspatialLayerNode.innerText = String(spatialLayer);\n\t\ttemporalLayerNode.innerText = String(temporalLayer);\n\n\t\tsend({\n\t\t\taction: 'SetConsumerPreferredLayers',\n\t\t\tid: videoConsumer.id as ConsumerId,\n\t\t\tpreferredLayers: {spatialLayer, temporalLayer}\n\t\t});\n\t}\n\n\tconst receiveMediaStream = new MediaStream();\n\n\tconst ws = new WebSocket('ws://localhost:3000/ws');\n\n\tfunction send(message: ClientMessage)\n\t{\n\t\tws.send(JSON.stringify(message));\n\t}\n\n\tconst device = new Device();\n\tlet producerTransport: Transport | undefined;\n\tlet consumerTransport: Transport | undefined;\n\n\t{\n\t\tconst waitingForResponse: Map<ServerMessage['action'], Function> = new Map();\n\n\t\tws.onmessage = async (message) =>\n\t\t{\n\t\t\tconst decodedMessage: ServerMessage = JSON.parse(message.data);\n\n\t\t\tswitch (decodedMessage.action)\n\t\t\t{\n\t\t\t\tcase 'Init': {\n\t\t\t\t\t// It is expected that server will send initialization message right after\n\t\t\t\t\t// WebSocket connection is established\n\t\t\t\t\tawait device.load({\n\t\t\t\t\t\trouterRtpCapabilities : decodedMessage.routerRtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// Send client-side initialization message back right away\n\t\t\t\t\tsend({\n\t\t\t\t\t\taction          : 'Init',\n\t\t\t\t\t\trtpCapabilities : device.rtpCapabilities\n\t\t\t\t\t});\n\n\t\t\t\t\t// Producer transport is needed to send audio and video to SFU\n\t\t\t\t\tproducerTransport = device.createSendTransport(\n\t\t\t\t\t\tdecodedMessage.producerTransportOptions\n\t\t\t\t\t);\n\n\t\t\t\t\tproducerTransport\n\t\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to establish producer transport connection\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'ConnectProducerTransport',\n\t\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('ConnectedProducerTransport', () =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\t\tconsole.log('Producer transport connected');\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.on('produce', ({ kind, rtpParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Once connection is established, send request to produce\n\t\t\t\t\t\t\t// audio or video track\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'Produce',\n\t\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\t\trtpParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('Produced', ({ id }: {id: string}) =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess({ id });\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// Request microphone and camera access, in real-world apps you may want\n\t\t\t\t\t// to do this separately so that audio-only and video-only cases are\n\t\t\t\t\t// handled nicely instead of failing completely\n\t\t\t\t\tconst mediaStream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\t\taudio : true,\n\t\t\t\t\t\tvideo : {\n\t\t\t\t\t\t\twidth : {\n\t\t\t\t\t\t\t\tideal : 1280\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\theight : {\n\t\t\t\t\t\t\t\tideal : 720\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tframeRate : {\n\t\t\t\t\t\t\t\tideal : 60\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tsendPreview.srcObject = mediaStream;\n\n\t\t\t\t\tconst producers = [];\n\n\t\t\t\t\t// And create producers for all tracks that were previously requested\n\t\t\t\t\tfor (const track of mediaStream.getTracks())\n\t\t\t\t\t{\n\t\t\t\t\t\tconst codec = track.kind === 'video'\n\t\t\t\t\t\t\t?\n\t\t\t\t\t\t\t\tdevice.rtpCapabilities.codecs?.find((codec) => {\n\t\t\t\t\t\t\t\t\t// Firefox supports VP9, but not SVC\n\t\t\t\t\t\t\t\t\treturn codec.mimeType.toLowerCase() === 'video/vp9' && !isFirefox;\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tdevice.rtpCapabilities.codecs?.find((codec) => codec.mimeType.toLowerCase() === 'video/vp8')\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\tif (track.kind === 'video')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvideoCodec.innerText = codec?.mimeType?.split('/')[1] ?? '?';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet encodings;\n\n\t\t\t\t\t\tif (codec?.mimeType.toLowerCase() === 'video/vp8')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tencodings = [\n\t\t\t\t\t\t\t\t{scaleResolutionDownBy: 4, maxBitrate: 500000},\n\t\t\t\t\t\t\t\t{scaleResolutionDownBy: 2, maxBitrate: 1000000},\n\t\t\t\t\t\t\t\t{scaleResolutionDownBy: 1, maxBitrate: 5000000}\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (codec?.mimeType.toLowerCase() === 'video/vp9')\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tencodings = [\n\t\t\t\t\t\t\t\t{scalabilityMode: 'S3T3'},\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst producer = await producerTransport.produce({\n\t\t\t\t\t\t\ttrack,\n\t\t\t\t\t\t\tencodings,\n\t\t\t\t\t\t\tcodec\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tproducers.push(producer);\n\t\t\t\t\t\tconsole.log(`${track.kind} producer created:`, producer);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Consumer transport is now needed to receive previously produced\n\t\t\t\t\t// tracks back\n\t\t\t\t\tconsumerTransport = device.createRecvTransport(\n\t\t\t\t\t\tdecodedMessage.consumerTransportOptions\n\t\t\t\t\t);\n\n\t\t\t\t\tconsumerTransport\n\t\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to establish consumer transport connection\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction : 'ConnectConsumerTransport',\n\t\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('ConnectedConsumerTransport', () =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\t\tconsole.log('Consumer transport connected');\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t// For simplicity of this example producers were stored in an array\n\t\t\t\t\t// and are now all consumed one at a time\n\t\t\t\t\tfor (const producer of producers)\n\t\t\t\t\t{\n\t\t\t\t\t\tawait new Promise((resolve) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Send request to consume producer\n\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\taction     : 'Consume',\n\t\t\t\t\t\t\t\tproducerId : producer.id as ProducerId\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\t\twaitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) =>\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t// Once confirmation is received, corresponding consumer\n\t\t\t\t\t\t\t\t// can be created client-side\n\t\t\t\t\t\t\t\tconst consumer = await (consumerTransport as Transport).consume(\n\t\t\t\t\t\t\t\t\tconsumerOptions\n\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\tconsole.log(`${consumer.kind} consumer created:`, consumer);\n\n\t\t\t\t\t\t\t\t// Consumer needs to be resumed after being created in\n\t\t\t\t\t\t\t\t// paused state (see official documentation about why:\n\t\t\t\t\t\t\t\t// https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume)\n\t\t\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\t\t\taction : 'ConsumerResume',\n\t\t\t\t\t\t\t\t\tid     : consumer.id as ConsumerId\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\treceiveMediaStream.addTrack(consumer.track);\n\t\t\t\t\t\t\t\treceivePreview.srcObject = receiveMediaStream;\n\n\t\t\t\t\t\t\t\tif (consumer.kind === 'video')\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tvideoConsumer = consumer\n\n\t\t\t\t\t\t\t\t\tconst encodings = videoConsumer.rtpParameters.encodings ?? [];\n\n\t\t\t\t\t\t\t\t\tif (encodings[0]) {\n\t\t\t\t\t\t\t\t\t\tconst scalabilityMode = parseScalabilityMode(encodings[0].scalabilityMode)\n\n\t\t\t\t\t\t\t\t\t\tmaxSpatialLayer = scalabilityMode.spatialLayers - 1;\n\t\t\t\t\t\t\t\t\t\tmaxTemporalLayer = scalabilityMode.temporalLayers -1;\n\t\t\t\t\t\t\t\t\t\tpreferredSpatialLayer = maxSpatialLayer;\n\t\t\t\t\t\t\t\t\t\tpreferredTemporalLayer = maxTemporalLayer;\n\n\t\t\t\t\t\t\t\t\t\tsetPreferredLayers(preferredSpatialLayer, preferredTemporalLayer);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\t// All messages other than initialization go here and are assumed\n\t\t\t\t\t// to be notifications that correspond to previously sent requests\n\t\t\t\t\tconst callback = waitingForResponse.get(decodedMessage.action);\n\n\t\t\t\t\tif (callback)\n\t\t\t\t\t{\n\t\t\t\t\t\twaitingForResponse.delete(decodedMessage.action);\n\t\t\t\t\t\tcallback(decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tconsole.error('Received unexpected message', decodedMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\tws.onerror = console.error;\n}\n\ninit();\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es6\",\n    \"target\": \"es2018\",\n    \"sourceMap\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": false,\n    \"sourceRoot\": \"src\",\n    \"baseUrl\": \"src\",\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"noUnusedParameters\": true,\n    \"noUnusedLocals\": true,\n    \"lib\": [\n      \"es5\",\n      \"es2015\",\n      \"es2019\",\n      \"dom\"\n    ]\n  },\n  \"include\": [\n    \"src\",\n    \".eslintrc.js\",\n    \"webpack.config.js\"\n  ]\n}\n"
  },
  {
    "path": "rust/examples-frontend/svc-simulcast/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n\tmode   : 'development',\n\tentry  : './src/index.ts',\n\tmodule : {\n\t\trules : [\n\t\t\t{\n\t\t\t\ttest    : /\\.ts$/,\n\t\t\t\tuse     : 'ts-loader',\n\t\t\t\texclude : /node_modules/\n\t\t\t}\n\t\t]\n\t},\n\tresolve : {\n\t\textensions : [ '.ts', '.js' ]\n\t},\n\toutput : {\n\t\tfilename : 'dist/bundle.js',\n\t\tpath     : path.resolve(__dirname, 'dist')\n\t},\n\tdevServer : {\n\t\tliveReload : false,\n\t\tport       : 3001,\n\t\tstatic     : {\n\t\t\tdirectory: __dirname\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/.eslintrc.js",
    "content": "module.exports = {\n\tenv : {\n\t\tbrowser : true,\n\t\tes6     : true\n\t},\n\tparserOptions : {\n\t\tproject    : 'tsconfig.json',\n\t\tsourceType : 'module'\n\t}\n};\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/index.html",
    "content": "<!doctype html>\n<style>\n    body {\n        margin: 0;\n    }\n\n    #container {\n        display: flex;\n    }\n\n    figure {\n        margin: 0;\n        width: 30vw;\n    }\n\n    video {\n        max-width: 100%;\n    }\n</style>\n<div id=\"container\">\n    <figure>\n        <video id=\"preview-send\" muted controls></video>\n        <figcaption>You</figcaption>\n    </figure>\n</div>\n<script src=\"dist/bundle.js\">\n</script>\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/package.json",
    "content": "{\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"engines\": {\n    \"node\": \">=14.8\"\n  },\n  \"scripts\": {\n    \"start\": \"webpack serve\"\n  },\n  \"author\": \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n  \"dependencies\": {\n    \"mediasoup-client\": \"^3.6.51\"\n  },\n  \"devDependencies\": {\n    \"ts-loader\": \"^9.2.8\",\n    \"typescript\": \"^4.6.3\",\n    \"webpack\": \"^5.71.0\",\n    \"webpack-cli\": \"^4.9.2\",\n    \"webpack-dev-server\": \"^5.2.2\"\n  }\n}\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/src/index.ts",
    "content": "/* eslint-disable no-console */\nimport { Device } from 'mediasoup-client';\nimport { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters';\nimport { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport';\nimport { ConsumerOptions } from 'mediasoup-client/lib/Consumer';\n\ntype Brand<K, T> = K & { __brand: T };\n\ntype RoomId = Brand<string, 'RoomId'>;\ntype ParticipantId = Brand<string, 'ParticipantId'>;\ntype ConsumerId = Brand<string, 'ConsumerId'>;\ntype ProducerId = Brand<string, 'ProducerId'>;\n\ninterface ServerInit {\n\taction: 'Init';\n\troomId: RoomId;\n\tconsumerTransportOptions: TransportOptions;\n\tproducerTransportOptions: TransportOptions;\n\trouterRtpCapabilities: RtpCapabilities;\n}\n\ninterface ServerProducerAdded {\n\taction: 'ProducerAdded';\n\tparticipantId: ParticipantId;\n\tproducerId: ProducerId;\n}\n\ninterface ServerProducerRemoved {\n\taction: 'ProducerRemoved';\n\tparticipantId: ParticipantId;\n\tproducerId: ProducerId;\n}\n\ninterface ServerConnectedProducerTransport {\n\taction: 'ConnectedProducerTransport';\n}\n\ninterface ServerProduced {\n\taction: 'Produced';\n\tid: ProducerId;\n}\n\ninterface ServerConnectedConsumerTransport {\n\taction: 'ConnectedConsumerTransport';\n}\n\ninterface ServerConsumed {\n\taction: 'Consumed';\n\tid: ConsumerId;\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ntype ServerMessage =\n\tServerInit |\n\tServerProducerAdded |\n\tServerProducerRemoved |\n\tServerConnectedProducerTransport |\n\tServerProduced |\n\tServerConnectedConsumerTransport |\n\tServerConsumed;\n\ninterface ClientInit {\n\taction: 'Init';\n\trtpCapabilities: RtpCapabilities;\n}\n\ninterface ClientConnectProducerTransport {\n\taction: 'ConnectProducerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientConnectConsumerTransport {\n\taction: 'ConnectConsumerTransport';\n\tdtlsParameters: DtlsParameters;\n}\n\ninterface ClientProduce {\n\taction: 'Produce';\n\tkind: MediaKind;\n\trtpParameters: RtpParameters;\n}\n\ninterface ClientConsume {\n\taction: 'Consume';\n\tproducerId: ProducerId;\n}\n\ninterface ClientConsumerResume {\n\taction: 'ConsumerResume';\n\tid: ConsumerId;\n}\n\ntype ClientMessage =\n\tClientInit |\n\tClientConnectProducerTransport |\n\tClientProduce |\n\tClientConnectConsumerTransport |\n\tClientConsume |\n\tClientConsumerResume;\n\nclass Participant\n{\n\tprivate readonly figure: HTMLElement;\n\tprivate readonly preview: HTMLVideoElement;\n\tprivate readonly mediaStream = new MediaStream();\n\n\tconstructor(\n\t\tpublic readonly id: ParticipantId)\n\t{\n\t\tconst container = document.querySelector('#container')!;\n\n\t\tthis.figure = document.createElement('figure');\n\t\tthis.preview = document.createElement('video');\n\n\t\tthis.preview.muted = true;\n\t\tthis.preview.controls= true;\n\n\t\tthis.preview.onloadedmetadata = () =>\n\t\t{\n\t\t\tthis.preview.play();\n\t\t};\n\n\t\tconst figcaption = document.createElement('figcaption');\n\n\t\tfigcaption.innerText = `Participant ${id}`;\n\n\t\tthis.figure.append(this.preview, figcaption);\n\n\t\tcontainer.append(this.figure);\n\t}\n\n\tpublic addTrack(track: MediaStreamTrack): void\n\t{\n\t\tthis.mediaStream.addTrack(track);\n\n\t\tthis.preview.srcObject = this.mediaStream;\n\t}\n\n\tpublic deleteTrack(track: MediaStreamTrack): void\n\t{\n\t\tthis.mediaStream.removeTrack(track);\n\n\t\tthis.preview.srcObject = this.mediaStream;\n\t}\n\n\tpublic hasTracks(): boolean\n\t{\n\t\treturn this.mediaStream.getTracks().length > 0;\n\t}\n\n\tpublic destroy(): void\n\t{\n\t\tthis.preview.srcObject = null;\n\t\tthis.figure.remove();\n\t}\n}\n\nclass Participants\n{\n\tprivate participants = new Map<ParticipantId, Participant>();\n\tprivate producerIdToTrack = new Map<ProducerId, MediaStreamTrack>();\n\n\tpublic addTrack(\n\t\tparticipantId: ParticipantId,\n\t\tproducerId: ProducerId,\n\t\ttrack: MediaStreamTrack): void\n\t{\n\t\tthis.producerIdToTrack.set(producerId, track);\n\t\tthis.getOrCreateParticipant(participantId).addTrack(track);\n\t}\n\n\tpublic deleteTrack(participantId: ParticipantId, producerId: ProducerId)\n\t{\n\t\tconst track = this.producerIdToTrack.get(producerId);\n\n\t\tif (track)\n\t\t{\n\t\t\tconst participant = this.getOrCreateParticipant(participantId);\n\n\t\t\tparticipant.deleteTrack(track);\n\t\t\tif (!participant.hasTracks())\n\t\t\t{\n\t\t\t\tthis.participants.delete(participantId);\n\t\t\t\tparticipant.destroy();\n\t\t\t}\n\t\t}\n\n\t\t// TODO\n\t}\n\n\tgetOrCreateParticipant(id: ParticipantId): Participant\n\t{\n\t\tlet participant = this.participants.get(id);\n\n\t\tif (!participant)\n\t\t{\n\t\t\tparticipant =new Participant(id);\n\t\t\tthis.participants.set(id, participant);\n\t\t}\n\n\t\treturn participant;\n\t}\n}\n\nasync function init()\n{\n\tconst sendPreview = document.querySelector('#preview-send') as HTMLVideoElement;\n\n\tsendPreview.onloadedmetadata = () =>\n\t{\n\t\tsendPreview.play();\n\t};\n\n\tconst participants = new Participants();\n\n\tconst roomId = (new URL(location.href)).searchParams.get('roomId') as RoomId | undefined;\n\tconst wsUrl = new URL('ws://localhost:3000/ws');\n\n\tif (roomId)\n\t{\n\t\twsUrl.searchParams.set('roomId', roomId);\n\t}\n\n\tconst ws = new WebSocket(wsUrl.toString());\n\n\tfunction send(message: ClientMessage)\n\t{\n\t\tws.send(JSON.stringify(message));\n\t}\n\n\tconst device = new Device();\n\tlet producerTransport: Transport | undefined;\n\tlet consumerTransport: Transport | undefined;\n\n\tlet sequentialMessages: Promise<void> = Promise.resolve();\n\tconst waitingForResponse: Map<ServerMessage['action'], Function> = new Map();\n\n\tconst onmessage = async (message: ServerMessage) =>\n\t{\n\t\tswitch (message.action)\n\t\t{\n\t\t\tcase 'Init': {\n\t\t\t\tif (!roomId)\n\t\t\t\t{\n\t\t\t\t\tconst url = new URL(location.href);\n\n\t\t\t\t\turl.searchParams.set('roomId', message.roomId);\n\t\t\t\t\thistory.pushState({}, '', url.toString());\n\t\t\t\t}\n\t\t\t\t// It is expected that server will send initialization message right after\n\t\t\t\t// WebSocket connection is established\n\t\t\t\tawait device.load({\n\t\t\t\t\trouterRtpCapabilities : message.routerRtpCapabilities\n\t\t\t\t});\n\n\t\t\t\t// Send client-side initialization message back right away\n\t\t\t\tsend({\n\t\t\t\t\taction          : 'Init',\n\t\t\t\t\trtpCapabilities : device.rtpCapabilities\n\t\t\t\t});\n\n\t\t\t\t// Producer transport is needed to send audio and video to SFU\n\t\t\t\tproducerTransport = device.createSendTransport(\n\t\t\t\t\tmessage.producerTransportOptions\n\t\t\t\t);\n\n\t\t\t\tproducerTransport\n\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t{\n\t\t\t\t\t\t// Send request to establish producer transport connection\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\taction : 'ConnectProducerTransport',\n\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\twaitingForResponse.set('ConnectedProducerTransport', () =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\tconsole.log('Producer transport connected');\n\t\t\t\t\t\t});\n\t\t\t\t\t})\n\t\t\t\t\t.on('produce', ({ kind, rtpParameters }, success) =>\n\t\t\t\t\t{\n\t\t\t\t\t\t// Once connection is established, send request to produce\n\t\t\t\t\t\t// audio or video track\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\taction : 'Produce',\n\t\t\t\t\t\t\tkind,\n\t\t\t\t\t\t\trtpParameters\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\twaitingForResponse.set('Produced', ({ id }: { id: string }) =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsuccess({ id });\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\n\t\t\t\t// Request microphone and camera access, in real-world apps you may want\n\t\t\t\t// to do this separately so that audio-only and video-only cases are\n\t\t\t\t// handled nicely instead of failing completely\n\t\t\t\tconst mediaStream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\taudio : true,\n\t\t\t\t\tvideo : {\n\t\t\t\t\t\twidth : {\n\t\t\t\t\t\t\tideal : 1280\n\t\t\t\t\t\t},\n\t\t\t\t\t\theight : {\n\t\t\t\t\t\t\tideal : 720\n\t\t\t\t\t\t},\n\t\t\t\t\t\tframeRate : {\n\t\t\t\t\t\t\tideal : 60\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tsendPreview.srcObject = mediaStream;\n\n\t\t\t\t// And create producers for all tracks that were previously requested\n\t\t\t\tfor (const track of mediaStream.getTracks())\n\t\t\t\t{\n\t\t\t\t\tconst producer = await producerTransport.produce({ track });\n\n\t\t\t\t\tconsole.log(`${track.kind} producer created:`, producer);\n\t\t\t\t}\n\n\t\t\t\t// Producer transport will be needed to receive produced tracks\n\t\t\t\tconsumerTransport = device.createRecvTransport(\n\t\t\t\t\tmessage.consumerTransportOptions\n\t\t\t\t);\n\n\t\t\t\tconsumerTransport\n\t\t\t\t\t.on('connect', ({ dtlsParameters }, success) =>\n\t\t\t\t\t{\n\t\t\t\t\t\t// Send request to establish consumer transport connection\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\taction : 'ConnectConsumerTransport',\n\t\t\t\t\t\t\tdtlsParameters\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\t\twaitingForResponse.set('ConnectedConsumerTransport', () =>\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsuccess();\n\t\t\t\t\t\t\tconsole.log('Consumer transport connected');\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'ProducerAdded': {\n\t\t\t\tawait new Promise((resolve) =>\n\t\t\t\t{\n\t\t\t\t\t// Send request to consume producer\n\t\t\t\t\tsend({\n\t\t\t\t\t\taction     : 'Consume',\n\t\t\t\t\t\tproducerId : message.producerId\n\t\t\t\t\t});\n\t\t\t\t\t// And wait for confirmation, but, obviously, no error handling,\n\t\t\t\t\t// which you should definitely have in real-world applications\n\t\t\t\t\twaitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) =>\n\t\t\t\t\t{\n\t\t\t\t\t\t// Once confirmation is received, corresponding consumer\n\t\t\t\t\t\t// can be created client-side\n\t\t\t\t\t\tconst consumer = await (consumerTransport as Transport).consume(\n\t\t\t\t\t\t\tconsumerOptions\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tconsole.log(`${consumer.kind} consumer created:`, consumer);\n\n\t\t\t\t\t\t// Consumer needs to be resumed after being created in\n\t\t\t\t\t\t// paused state (see official documentation about why:\n\t\t\t\t\t\t// https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume)\n\t\t\t\t\t\tsend({\n\t\t\t\t\t\t\taction : 'ConsumerResume',\n\t\t\t\t\t\t\tid     : consumer.id as ConsumerId\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tparticipants\n\t\t\t\t\t\t\t.addTrack(message.participantId, message.producerId, consumer.track);\n\t\t\t\t\t\tresolve(undefined);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'ProducerRemoved': {\n\t\t\t\tparticipants\n\t\t\t\t\t.deleteTrack(message.participantId, message.producerId);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\tconsole.error('Received unexpected message', message);\n\t\t\t}\n\t\t}\n\t};\n\n\tws.onmessage = (message) =>\n\t{\n\t\tconst decodedMessage: ServerMessage = JSON.parse(message.data);\n\n\t\t// All other messages go here and are assumed to be notifications\n\t\t// that correspond to previously sent requests\n\t\tconst callback = waitingForResponse.get(decodedMessage.action);\n\n\t\tif (callback)\n\t\t{\n\t\t\twaitingForResponse.delete(decodedMessage.action);\n\t\t\tcallback(decodedMessage);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Simple hack to make sure we process all messages in order, in real-world apps\n\t\t\t// messages it would be useful to have messages being processed concurrently\n\t\t\tsequentialMessages = sequentialMessages\n\t\t\t\t.then(() =>\n\t\t\t\t{\n\t\t\t\t\treturn onmessage(decodedMessage);\n\t\t\t\t})\n\t\t\t\t.catch((error) =>\n\t\t\t\t{\n\t\t\t\t\tconsole.error('Unexpected error during message handling:', error);\n\t\t\t\t});\n\t\t}\n\t};\n\tws.onerror = console.error;\n}\n\ninit();\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"es6\",\n    \"target\": \"es2018\",\n    \"sourceMap\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"moduleResolution\": \"node\",\n    \"allowSyntheticDefaultImports\": false,\n    \"sourceRoot\": \"src\",\n    \"baseUrl\": \"src\",\n    \"outDir\": \"dist\",\n    \"strict\": true,\n    \"noUnusedParameters\": true,\n    \"noUnusedLocals\": true,\n    \"lib\": [\n      \"es5\",\n      \"es2015\",\n      \"es2019\",\n      \"dom\"\n    ]\n  },\n  \"include\": [\n    \"src\",\n    \".eslintrc.js\",\n    \"webpack.config.js\"\n  ]\n}\n"
  },
  {
    "path": "rust/examples-frontend/videoroom/webpack.config.js",
    "content": "const path = require('path');\n\nmodule.exports = {\n\tmode   : 'development',\n\tentry  : './src/index.ts',\n\tmodule : {\n\t\trules : [\n\t\t\t{\n\t\t\t\ttest    : /\\.ts$/,\n\t\t\t\tuse     : 'ts-loader',\n\t\t\t\texclude : /node_modules/\n\t\t\t}\n\t\t]\n\t},\n\tresolve : {\n\t\textensions : [ '.ts', '.js' ]\n\t},\n\toutput : {\n\t\tfilename : 'dist/bundle.js',\n\t\tpath     : path.resolve(__dirname, 'dist')\n\t},\n\tdevServer : {\n\t\tliveReload : false,\n\t\tport       : 3001,\n\t\tstatic     : {\n\t\t\tdirectory: __dirname\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "rust/src/data_structures.rs",
    "content": "//! Miscellaneous data structures.\n\nuse mediasoup_sys::fbs::{\n    common, producer, rtp_packet, sctp_association, transport, web_rtc_transport,\n};\npub use mediasoup_types::data_structures::*;\n\nuse crate::fbs::{FromFbs, ToFbs};\n\nimpl ToFbs for ListenInfo {\n    type FbsType = transport::ListenInfo;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        transport::ListenInfo {\n            protocol: match self.protocol {\n                Protocol::Tcp => transport::Protocol::Tcp,\n                Protocol::Udp => transport::Protocol::Udp,\n            },\n            ip: self.ip.to_string(),\n            announced_address: self\n                .announced_address\n                .as_ref()\n                .map(|address| address.to_string()),\n            expose_internal_ip: self.expose_internal_ip,\n            port: self.port.unwrap_or(0),\n            port_range: match &self.port_range {\n                Some(port_range) => Box::new(transport::PortRange {\n                    min: *port_range.start(),\n                    max: *port_range.end(),\n                }),\n                None => Box::new(transport::PortRange { min: 0, max: 0 }),\n            },\n            flags: Box::new(self.flags.unwrap_or_default().to_fbs()),\n            send_buffer_size: self.send_buffer_size.unwrap_or(0),\n            recv_buffer_size: self.recv_buffer_size.unwrap_or(0),\n        }\n    }\n}\n\nimpl ToFbs for SocketFlags {\n    type FbsType = transport::SocketFlags;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        transport::SocketFlags {\n            ipv6_only: self.ipv6_only,\n            udp_reuse_port: self.udp_reuse_port,\n        }\n    }\n}\n\nimpl ToFbs for DtlsRole {\n    type FbsType = web_rtc_transport::DtlsRole;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            DtlsRole::Auto => web_rtc_transport::DtlsRole::Auto,\n            DtlsRole::Client => web_rtc_transport::DtlsRole::Client,\n            DtlsRole::Server => web_rtc_transport::DtlsRole::Server,\n        }\n    }\n}\n\nimpl FromFbs for DtlsRole {\n    type FbsType = web_rtc_transport::DtlsRole;\n\n    fn from_fbs(role: &Self::FbsType) -> Self {\n        match role {\n            web_rtc_transport::DtlsRole::Auto => DtlsRole::Auto,\n            web_rtc_transport::DtlsRole::Client => DtlsRole::Client,\n            web_rtc_transport::DtlsRole::Server => DtlsRole::Server,\n        }\n    }\n}\n\nimpl FromFbs for DtlsState {\n    type FbsType = web_rtc_transport::DtlsState;\n\n    fn from_fbs(state: &Self::FbsType) -> Self {\n        match state {\n            web_rtc_transport::DtlsState::New => DtlsState::New,\n            web_rtc_transport::DtlsState::Connecting => DtlsState::Connecting,\n            web_rtc_transport::DtlsState::Connected => DtlsState::Connected,\n            web_rtc_transport::DtlsState::Failed => DtlsState::Failed,\n            web_rtc_transport::DtlsState::Closed => DtlsState::Closed,\n        }\n    }\n}\n\nimpl ToFbs for DtlsParameters {\n    type FbsType = web_rtc_transport::DtlsParameters;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        web_rtc_transport::DtlsParameters {\n            role: self.role.to_fbs(),\n            fingerprints: self\n                .fingerprints\n                .iter()\n                .map(DtlsFingerprint::to_fbs)\n                .collect(),\n        }\n    }\n}\n\nimpl FromFbs for DtlsParameters {\n    type FbsType = web_rtc_transport::DtlsParameters;\n\n    fn from_fbs(parameters: &Self::FbsType) -> Self {\n        DtlsParameters {\n            role: DtlsRole::from_fbs(&parameters.role),\n            fingerprints: parameters\n                .fingerprints\n                .iter()\n                .map(|fingerprint| DtlsFingerprint::from_fbs(&fingerprint.clone()))\n                .collect(),\n        }\n    }\n}\n\nimpl ToFbs for DtlsFingerprint {\n    type FbsType = web_rtc_transport::Fingerprint;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            DtlsFingerprint::Sha1 { .. } => web_rtc_transport::Fingerprint {\n                algorithm: web_rtc_transport::FingerprintAlgorithm::Sha1,\n                value: self.value_string(),\n            },\n            DtlsFingerprint::Sha224 { .. } => web_rtc_transport::Fingerprint {\n                algorithm: web_rtc_transport::FingerprintAlgorithm::Sha224,\n                value: self.value_string(),\n            },\n            DtlsFingerprint::Sha256 { .. } => web_rtc_transport::Fingerprint {\n                algorithm: web_rtc_transport::FingerprintAlgorithm::Sha256,\n                value: self.value_string(),\n            },\n            DtlsFingerprint::Sha384 { .. } => web_rtc_transport::Fingerprint {\n                algorithm: web_rtc_transport::FingerprintAlgorithm::Sha384,\n                value: self.value_string(),\n            },\n            DtlsFingerprint::Sha512 { .. } => web_rtc_transport::Fingerprint {\n                algorithm: web_rtc_transport::FingerprintAlgorithm::Sha512,\n                value: self.value_string(),\n            },\n        }\n    }\n}\n\n/// Parses a series of hex bytes into a byte array.\nfn hex_as_bytes<const N: usize>(input: &str) -> [u8; N] {\n    let mut output = [0_u8; N];\n    for (i, o) in input.split(':').zip(&mut output.iter_mut()) {\n        *o = u8::from_str_radix(i, 16).unwrap_or_else(|error| {\n            panic!(\"Failed to parse value {i} as series of hex bytes: {error}\")\n        });\n    }\n\n    output\n}\n\nimpl FromFbs for DtlsFingerprint {\n    type FbsType = web_rtc_transport::Fingerprint;\n\n    fn from_fbs(fingerprint: &Self::FbsType) -> Self {\n        match fingerprint.algorithm {\n            web_rtc_transport::FingerprintAlgorithm::Sha1 => {\n                let value_result = hex_as_bytes::<20>(fingerprint.value.as_str());\n\n                DtlsFingerprint::Sha1 {\n                    value: value_result,\n                }\n            }\n            web_rtc_transport::FingerprintAlgorithm::Sha224 => {\n                let value_result = hex_as_bytes::<28>(fingerprint.value.as_str());\n\n                DtlsFingerprint::Sha224 {\n                    value: value_result,\n                }\n            }\n            web_rtc_transport::FingerprintAlgorithm::Sha256 => {\n                let value_result = hex_as_bytes::<32>(fingerprint.value.as_str());\n\n                DtlsFingerprint::Sha256 {\n                    value: value_result,\n                }\n            }\n            web_rtc_transport::FingerprintAlgorithm::Sha384 => {\n                let value_result = hex_as_bytes::<48>(fingerprint.value.as_str());\n\n                DtlsFingerprint::Sha384 {\n                    value: value_result,\n                }\n            }\n            web_rtc_transport::FingerprintAlgorithm::Sha512 => {\n                let value_result = hex_as_bytes::<64>(fingerprint.value.as_str());\n\n                DtlsFingerprint::Sha512 {\n                    value: value_result,\n                }\n            }\n        }\n    }\n}\n\nimpl FromFbs for IceRole {\n    type FbsType = web_rtc_transport::IceRole;\n\n    fn from_fbs(role: &Self::FbsType) -> Self {\n        match role {\n            web_rtc_transport::IceRole::Controlled => IceRole::Controlled,\n            web_rtc_transport::IceRole::Controlling => IceRole::Controlling,\n        }\n    }\n}\n\nimpl FromFbs for IceParameters {\n    type FbsType = web_rtc_transport::IceParameters;\n\n    fn from_fbs(parameters: &Self::FbsType) -> Self {\n        Self {\n            username_fragment: parameters.username_fragment.to_string(),\n            password: parameters.password.to_string(),\n            ice_lite: Some(parameters.ice_lite),\n        }\n    }\n}\n\nimpl FromFbs for SctpState {\n    type FbsType = sctp_association::SctpState;\n\n    fn from_fbs(state: &Self::FbsType) -> Self {\n        match state {\n            sctp_association::SctpState::New => Self::New,\n            sctp_association::SctpState::Connecting => Self::Connecting,\n            sctp_association::SctpState::Connected => Self::Connected,\n            sctp_association::SctpState::Failed => Self::Failed,\n            sctp_association::SctpState::Closed => Self::Closed,\n        }\n    }\n}\n\nimpl FromFbs for IceCandidateType {\n    type FbsType = web_rtc_transport::IceCandidateType;\n\n    fn from_fbs(candidate_type: &Self::FbsType) -> Self {\n        match candidate_type {\n            web_rtc_transport::IceCandidateType::Host => IceCandidateType::Host,\n        }\n    }\n}\n\nimpl FromFbs for IceCandidate {\n    type FbsType = web_rtc_transport::IceCandidate;\n\n    fn from_fbs(candidate: &Self::FbsType) -> Self {\n        Self {\n            foundation: candidate.foundation.clone(),\n            priority: candidate.priority,\n            address: candidate.address.clone(),\n            protocol: Protocol::from_fbs(&candidate.protocol),\n            port: candidate.port,\n            r#type: IceCandidateType::from_fbs(&candidate.type_),\n            tcp_type: FromFbs::from_fbs(&candidate.tcp_type),\n        }\n    }\n}\n\nimpl FromFbs for IceCandidateTcpType {\n    type FbsType = web_rtc_transport::IceCandidateTcpType;\n\n    fn from_fbs(candidate_type: &Self::FbsType) -> Self {\n        match candidate_type {\n            web_rtc_transport::IceCandidateTcpType::Passive => IceCandidateTcpType::Passive,\n        }\n    }\n}\n\nimpl FromFbs for IceState {\n    type FbsType = web_rtc_transport::IceState;\n\n    fn from_fbs(state: &Self::FbsType) -> Self {\n        match state {\n            web_rtc_transport::IceState::New => IceState::New,\n            web_rtc_transport::IceState::Connected => IceState::Connected,\n            web_rtc_transport::IceState::Completed => IceState::Completed,\n            web_rtc_transport::IceState::Disconnected => IceState::Disconnected,\n        }\n    }\n}\n\nimpl FromFbs for Protocol {\n    type FbsType = transport::Protocol;\n\n    fn from_fbs(protocol: &Self::FbsType) -> Self {\n        match protocol {\n            transport::Protocol::Tcp => Protocol::Tcp,\n            transport::Protocol::Udp => Protocol::Udp,\n        }\n    }\n}\n\nimpl FromFbs for TransportTuple {\n    type FbsType = transport::Tuple;\n\n    fn from_fbs(tuple: &Self::FbsType) -> Self {\n        match &tuple.remote_ip {\n            Some(_remote_ip) => TransportTuple::WithRemote {\n                local_address: tuple\n                    .local_address\n                    .parse()\n                    .expect(\"Error parsing local address\"),\n                local_port: tuple.local_port,\n                remote_ip: tuple\n                    .remote_ip\n                    .as_ref()\n                    .unwrap()\n                    .parse()\n                    .expect(\"Error parsing remote IP address\"),\n                remote_port: tuple.remote_port,\n                protocol: Protocol::from_fbs(&tuple.protocol),\n            },\n            None => TransportTuple::LocalOnly {\n                local_address: tuple\n                    .local_address\n                    .parse()\n                    .expect(\"Error parsing local address\"),\n                local_port: tuple.local_port,\n                protocol: Protocol::from_fbs(&tuple.protocol),\n            },\n        }\n    }\n}\n\nimpl FromFbs for TraceEventDirection {\n    type FbsType = common::TraceDirection;\n\n    fn from_fbs(event_type: &Self::FbsType) -> Self {\n        match event_type {\n            common::TraceDirection::DirectionIn => TraceEventDirection::In,\n            common::TraceDirection::DirectionOut => TraceEventDirection::Out,\n        }\n    }\n}\n\nimpl FromFbs for SrTraceInfo {\n    type FbsType = producer::SrTraceInfo;\n\n    fn from_fbs(info: &Self::FbsType) -> Self {\n        Self {\n            ssrc: info.ssrc,\n            ntp_sec: info.ntp_sec,\n            ntp_frac: info.ntp_frac,\n            rtp_ts: info.rtp_ts,\n            packet_count: info.packet_count,\n            octet_count: info.octet_count,\n        }\n    }\n}\n\nimpl FromFbs for BweType {\n    type FbsType = transport::BweType;\n\n    fn from_fbs(info: &Self::FbsType) -> Self {\n        match info {\n            transport::BweType::TransportCc => BweType::TransportCc,\n            transport::BweType::Remb => BweType::Remb,\n        }\n    }\n}\n\nimpl FromFbs for BweTraceInfo {\n    type FbsType = transport::BweTraceInfo;\n\n    fn from_fbs(info: &Self::FbsType) -> Self {\n        Self {\n            r#type: BweType::from_fbs(&info.bwe_type),\n            desired_bitrate: info.desired_bitrate,\n            effective_desired_bitrate: info.effective_desired_bitrate,\n            min_bitrate: info.min_bitrate,\n            max_bitrate: info.max_bitrate,\n            start_bitrate: info.start_bitrate,\n            max_padding_bitrate: info.max_padding_bitrate,\n            available_bitrate: info.available_bitrate,\n        }\n    }\n}\n\nimpl FromFbs for RtpPacketTraceInfo {\n    type FbsType = rtp_packet::Dump;\n\n    fn from_fbs(rtp_packet: &Self::FbsType) -> Self {\n        Self {\n            payload_type: rtp_packet.payload_type,\n            sequence_number: rtp_packet.sequence_number,\n            timestamp: rtp_packet.timestamp,\n            marker: rtp_packet.marker,\n            ssrc: rtp_packet.ssrc,\n            is_key_frame: rtp_packet.is_key_frame,\n            size: rtp_packet.size,\n            payload_size: rtp_packet.payload_size,\n            spatial_layer: rtp_packet.spatial_layer,\n            temporal_layer: rtp_packet.temporal_layer,\n            mid: rtp_packet.mid.clone(),\n            rid: rtp_packet.rid.clone(),\n            rrid: rtp_packet.rrid.clone(),\n            wide_sequence_number: rtp_packet.wide_sequence_number,\n            is_rtx: false,\n        }\n    }\n}\n"
  },
  {
    "path": "rust/src/fbs.rs",
    "content": "//! Traits for converting between Rust and FlatBuffers data structures.\n\npub(crate) trait TryFromFbs<'a>: Sized {\n    type FbsType;\n    type Error;\n\n    fn try_from_fbs(fbs: Self::FbsType) -> Result<Self, Self::Error>;\n}\n\npub(crate) trait FromFbs: Sized {\n    type FbsType;\n\n    fn from_fbs(fbs: &Self::FbsType) -> Self;\n}\n\nimpl<'a, T> TryFromFbs<'a> for Vec<T>\nwhere\n    T: TryFromFbs<'a>,\n{\n    type FbsType = Vec<T::FbsType>;\n    type Error = T::Error;\n\n    fn try_from_fbs(fbs: Self::FbsType) -> Result<Self, Self::Error> {\n        fbs.into_iter().map(T::try_from_fbs).collect()\n    }\n}\n\npub(crate) trait ToFbs: Sized {\n    type FbsType;\n\n    fn to_fbs(&self) -> Self::FbsType;\n}\n\nimpl<T> FromFbs for Option<T>\nwhere\n    T: FromFbs,\n{\n    type FbsType = Option<T::FbsType>;\n\n    fn from_fbs(value: &Self::FbsType) -> Self {\n        value.as_ref().map(T::from_fbs)\n    }\n}\n\nimpl<T> FromFbs for Vec<T>\nwhere\n    T: FromFbs,\n{\n    type FbsType = Vec<T::FbsType>;\n\n    fn from_fbs(fbs: &Self::FbsType) -> Self {\n        fbs.iter().map(T::from_fbs).collect()\n    }\n}\n\nimpl<T> ToFbs for Vec<T>\nwhere\n    T: ToFbs,\n{\n    type FbsType = Vec<T::FbsType>;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        self.iter().map(|item| item.to_fbs()).collect()\n    }\n}\n\nimpl<T> ToFbs for Option<T>\nwhere\n    T: ToFbs,\n{\n    type FbsType = Option<T::FbsType>;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        self.as_ref().map(T::to_fbs)\n    }\n}\n"
  },
  {
    "path": "rust/src/lib.rs",
    "content": "#![warn(rust_2018_idioms, missing_debug_implementations, missing_docs)]\n//! Rust port of [mediasoup](https://github.com/versatica/mediasoup) TypeScript library!\n//!\n//! For general information go to readme in repository.\n//!\n//! # For TypeScript users\n//! If you were using mediasoup in TypeScript before, most of the API should be familiar to you.\n//! However, this is not one-to-one port, API was adjusted to more idiomatic Rust style leveraging\n//! powerful type system and ownership system to make API more robust and more misuse-resistant.\n//!\n//! So you will find specific types in most places where plain strings were used, instead of\n//! `close()` you will see `Drop` implementation for major entities that will close everything\n//! gracefully when it goes out of scope.\n//!\n//! # Before you start\n//! This is very low-level **library**. Which means it doesn't come with a ready to use signaling\n//! mechanism or easy to customize app scaffold (see\n//! [design goals](https://github.com/versatica/mediasoup/tree/v3/rust/readme.md#design-goals)).\n//!\n//! It is recommended to visit mediasoup website and read\n//! [design overview](https://mediasoup.org/documentation/v3/mediasoup/design/) first.\n//!\n//! There are some requirements for building underlying C++ `mediasoup-worker`, please find them in\n//! [installation instructions](https://mediasoup.org/documentation/v3/mediasoup/installation/)\n//!\n//! # Examples\n//! There are some examples in `examples` and `examples-frontend` directories (for server- and\n//! client-side respectively), you may want to look at those to get a general idea of what API looks\n//! like and what needs to be done in what order (check WebSocket messages in browser DevTools for\n//! better understanding of what is happening under the hood).\n//!\n//! # How to start\n//! With that in mind, you want start with creating [`WorkerManager`](worker_manager::WorkerManager)\n//! instance and then 1 or more workers. Workers a responsible for low-level job of sending media\n//! and data back and forth. Each worker is backed by single-core C++ worker thread. On each worker\n//! you create one or more routers that enable injection, selection and forwarding of media and data\n//! through [`transport`] instances. There are a few different transports available, but most likely\n//! you'll want to use [`WebRtcTransport`](webrtc_transport::WebRtcTransport) most often. With\n//! transport created you can start creating [`Producer`](producer::Producer)s to send data to\n//! [`Router`](router::Router) and [`Consumer`](consumer::Consumer) instances to extract data from\n//! [`Router`](router::Router).\n//!\n//! Some of the more advanced cases involve multiple routers and even workers that can user more\n//! than one core on the machine or even scale beyond single host. Check\n//! [scalability page](https://mediasoup.org/documentation/v3/scalability/) of the official\n//! documentation.\n//!\n//! Please check integration and unit tests for usage examples, they cover all major functionality\n//! and are a good place to start until we have demo apps built in Rust).\n\npub use mediasoup_types as types;\nmod data_structures;\npub(crate) mod fbs;\nmod macros;\nmod messages;\n#[doc(hidden)]\npub mod ortc;\npub mod prelude;\npub mod router;\nmod rtp_parameters;\nmod sctp_parameters;\nmod srtp_parameters;\npub mod supported_rtp_capabilities;\npub mod webrtc_server;\npub mod worker;\npub mod worker_manager;\n\npub mod audio_level_observer {\n    //! An audio level observer monitors the volume of the selected audio producers.\n\n    pub use crate::router::audio_level_observer::*;\n}\n\npub mod active_speaker_observer {\n    //! An active speaker observer monitors the speaking activity of the selected audio producers.\n\n    pub use crate::router::active_speaker_observer::*;\n}\n\npub mod consumer {\n    //! A consumer represents an audio or video source being forwarded from a mediasoup router to an\n    //! endpoint. It's created on top of a transport that defines how the media packets are carried.\n\n    pub use crate::router::consumer::*;\n}\n\npub mod data_consumer {\n    //! A data consumer represents an endpoint capable of receiving data messages from a mediasoup\n    //! [`Router`](router::Router).\n    //!\n    //! A data consumer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA\n    //! DataChannel) to receive those messages, or can directly receive them in the Rust application\n    //! if the data consumer was created on top of a\n    //! [`DirectTransport`](direct_transport::DirectTransport).\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::data_consumer::*;\n}\n\npub mod producer {\n    //! A producer represents an audio or video source being injected into a mediasoup router. It's\n    //! created on top of a transport that defines how the media packets are carried.\n\n    pub use crate::router::producer::*;\n}\n\npub mod data_producer {\n    //! A data producer represents an endpoint capable of injecting data messages into a mediasoup\n    //! [`Router`](router::Router).\n    //!\n    //! A data producer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA DataChannel) to\n    //! deliver those messages, or can directly send them from the Rust application if the data\n    //! producer was created on top of a [`DirectTransport`](direct_transport::DirectTransport).\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::data_producer::*;\n}\n\npub mod transport {\n    //! A transport connects an endpoint with a mediasoup router and enables transmission of media\n    //! in both directions by means of [`Producer`](producer::Producer),\n    //! [`Consumer`](consumer::Consumer), [`DataProducer`](data_producer::DataProducer) and\n    //! [`DataConsumer`](data_consumer::DataConsumer) instances created on it.\n    //!\n    //! mediasoup implements the following transports:\n    //! * [`WebRtcTransport`](webrtc_transport::WebRtcTransport)\n    //! * [`PlainTransport`](plain_transport::PlainTransport)\n    //! * [`PipeTransport`](pipe_transport::PipeTransport)\n    //! * [`DirectTransport`](direct_transport::DirectTransport)\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::transport::*;\n}\n\npub mod direct_transport {\n    //! A direct transport represents a direct connection between the mediasoup Rust process and a\n    //! [`Router`](router::Router) instance in a mediasoup-worker thread.\n    //!\n    //! A direct transport can be used to directly send and receive data messages from/to Rust by\n    //! means of [`DataProducer`](data_producer::DataProducer)s and\n    //! [`DataConsumer`](data_consumer::DataConsumer)s of type `Direct` created on a direct\n    //! transport.\n    //! Direct messages sent by a [`DataProducer`](data_producer::DataProducer) in a direct\n    //! transport can be consumed by endpoints connected through a SCTP capable transport\n    //! ([`WebRtcTransport`](webrtc_transport::WebRtcTransport),\n    //! [`PlainTransport`](plain_transport::PlainTransport),\n    //! [`PipeTransport`](pipe_transport::PipeTransport) and also by the Rust application by means\n    //! of a [`DataConsumer`](data_consumer::DataConsumer) created on a [`DirectTransport`] (and\n    //! vice-versa: messages sent over SCTP/DataChannel can be consumed by the Rust application by\n    //! means of a [`DataConsumer`](data_consumer::DataConsumer) created on a [`DirectTransport`]).\n    //!\n    //! A direct transport can also be used to inject and directly consume RTP and RTCP packets in\n    //! Rust by using the [`DirectProducer::send`](producer::DirectProducer::send) and\n    //! [`Consumer::on_rtp`](consumer::Consumer::on_rtp) API (plus [`DirectTransport::send_rtcp`]\n    //! and [`DirectTransport::on_rtcp`] API).\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::direct_transport::*;\n}\n\npub mod pipe_transport {\n    //! A pipe transport represents a network path through which RTP, RTCP (optionally secured with\n    //! SRTP) and SCTP (DataChannel) is transmitted. Pipe transports are intended to\n    //! intercommunicate two [`Router`](router::Router) instances collocated on the same host or on\n    //! separate hosts.\n    //!\n    //! # Notes on usage\n    //! When calling [`PipeTransport::consume`](transport::Transport::consume), all RTP streams of\n    //! the [`Producer`](producer::Producer) are transmitted verbatim (in contrast to what happens\n    //! in [`WebRtcTransport`](webrtc_transport::WebRtcTransport) and\n    //! [`PlainTransport`](plain_transport::PlainTransport) in which a single and continuous RTP\n    //! stream is sent to the consuming endpoint).\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::pipe_transport::*;\n}\n\npub mod plain_transport {\n    //! A plain transport represents a network path through which RTP, RTCP (optionally secured with\n    //! SRTP) and SCTP (DataChannel) is transmitted.\n\n    pub use crate::router::plain_transport::*;\n}\n\npub mod rtp_observer {\n    //! An RTP observer inspects the media received by a set of selected producers.\n    //!\n    //! mediasoup implements the following RTP observers:\n    //! * [`AudioLevelObserver`](audio_level_observer::AudioLevelObserver)\n\n    #[cfg(doc)]\n    use super::*;\n    pub use crate::router::rtp_observer::*;\n}\n\npub mod webrtc_transport {\n    //! A WebRTC transport represents a network path negotiated by both, a WebRTC endpoint and\n    //! mediasoup, via ICE and DTLS procedures. A WebRTC transport may be used to receive media, to\n    //! send media or to both receive and send. There is no limitation in mediasoup. However, due to\n    //! their design, mediasoup-client and libmediasoupclient require separate WebRTC transports for\n    //! sending and receiving.\n    //!\n    //! # Notes on usage\n    //! The WebRTC transport implementation of mediasoup is\n    //! [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not\n    //! initiate ICE connections but expects ICE Binding Requests from endpoints.\n\n    pub use crate::router::webrtc_transport::*;\n}\n"
  },
  {
    "path": "rust/src/macros.rs",
    "content": "#[doc(hidden)]\n#[macro_export]\nmacro_rules! uuid_based_wrapper_type {\n    (\n        $(#[$outer:meta])*\n        $struct_name: ident\n    ) => {\n        $(#[$outer])*\n        #[derive(\n            Debug,\n            Copy,\n            Clone,\n            serde::Deserialize,\n            serde::Serialize,\n            Hash,\n            Ord,\n            PartialOrd,\n            Eq,\n            PartialEq,\n        )]\n        pub struct $struct_name(::uuid::Uuid);\n\n        impl std::fmt::Display for $struct_name {\n            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n                std::fmt::Display::fmt(&self.0, f)\n            }\n        }\n\n        impl ::std::str::FromStr for $struct_name {\n            type Err = ::uuid::Error;\n\n            fn from_str(s: &str) -> Result<Self, Self::Err> {\n                ::uuid::Uuid::from_str(s).map(Self)\n            }\n        }\n\n        impl From<$struct_name> for ::uuid::Uuid {\n            fn from(id: $struct_name) -> Self {\n                id.0\n            }\n        }\n\n        impl $struct_name {\n            pub(super) fn new() -> Self {\n                $struct_name(::uuid::Uuid::new_v4())\n            }\n        }\n\n        impl From<$struct_name> for $crate::worker::SubscriptionTarget {\n            fn from(id: $struct_name) -> Self {\n                Self::Uuid(id.0)\n            }\n        }\n    };\n}\n"
  },
  {
    "path": "rust/src/messages.rs",
    "content": "use crate::active_speaker_observer::ActiveSpeakerObserverOptions;\nuse crate::audio_level_observer::AudioLevelObserverOptions;\nuse crate::consumer::{\n    ConsumerId, ConsumerLayers, ConsumerScore, ConsumerTraceEventType, ConsumerType,\n};\nuse crate::data_consumer::{DataConsumerId, DataConsumerType};\nuse crate::data_producer::{DataProducerId, DataProducerType};\nuse crate::direct_transport::DirectTransportOptions;\nuse crate::fbs::{FromFbs, ToFbs, TryFromFbs};\nuse crate::ortc::RtpMapping;\nuse crate::pipe_transport::PipeTransportOptions;\nuse crate::plain_transport::PlainTransportOptions;\nuse crate::producer::{ProducerId, ProducerTraceEventType, ProducerType};\nuse crate::router::consumer::ConsumerDump;\nuse crate::router::producer::ProducerDump;\nuse crate::router::{RouterDump, RouterId};\nuse crate::rtp_observer::RtpObserverId;\nuse crate::transport::{TransportId, TransportTraceEventType};\nuse crate::webrtc_server::{\n    WebRtcServerDump, WebRtcServerIceUsernameFragment, WebRtcServerId, WebRtcServerIpPort,\n    WebRtcServerListenInfos, WebRtcServerTupleHash,\n};\nuse crate::webrtc_transport::{\n    WebRtcTransportListen, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse crate::worker::{ChannelMessageHandlers, LibUringDump, WorkerDump, WorkerUpdateSettings};\nuse mediasoup_sys::fbs::{\n    active_speaker_observer, audio_level_observer, consumer, data_consumer, data_producer,\n    direct_transport, message, notification, pipe_transport, plain_transport, producer, request,\n    response, router, rtp_observer, transport, web_rtc_server, web_rtc_transport, worker,\n};\nuse mediasoup_types::data_structures::{\n    DtlsParameters, DtlsRole, DtlsState, IceCandidate, IceParameters, IceRole, IceState,\n    ListenInfo, SctpState, TransportTuple,\n};\nuse mediasoup_types::rtp_parameters::{MediaKind, RtpEncodingParameters, RtpParameters};\nuse mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters, SctpStreamParameters};\nuse mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters};\nuse parking_lot::Mutex;\nuse planus::Builder;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt::{Debug, Display};\nuse std::net::IpAddr;\nuse std::num::NonZeroU16;\nuse std::ops::Deref;\n\npub(crate) trait Request\nwhere\n    Self: Debug,\n{\n    /// Request method to call on worker.\n    const METHOD: request::Method;\n    type HandlerId: Display;\n    type Response;\n\n    /// Get a serialized message out of this request.\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8>;\n\n    /// Default response to return in case of soft error, such as channel already closed, entity\n    /// doesn't exist on worker during closing.\n    fn default_for_soft_error() -> Option<Self::Response> {\n        None\n    }\n\n    /// Convert generic response into specific type of this request.\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>>;\n}\n\npub(crate) trait Notification: Debug {\n    /// Notification event to call on worker.\n    const EVENT: notification::Event;\n    type HandlerId: Display;\n\n    /// Get a serialized message out of this notification.\n    fn into_bytes(self, handler_id: Self::HandlerId) -> Vec<u8>;\n}\n\n#[derive(Debug)]\npub(crate) struct WorkerDumpRequest {}\n\nimpl Request for WorkerDumpRequest {\n    const METHOD: request::Method = request::Method::WorkerDump;\n    type HandlerId = &'static str;\n    type Response = WorkerDump;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::WorkerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = worker::DumpResponse::try_from(data)?;\n\n        Ok(WorkerDump {\n            router_ids: data\n                .router_ids\n                .into_iter()\n                .map(|id| id.parse())\n                .collect::<Result<_, _>>()?,\n            webrtc_server_ids: data\n                .web_rtc_server_ids\n                .into_iter()\n                .map(|id| id.parse())\n                .collect::<Result<_, _>>()?,\n            channel_message_handlers: ChannelMessageHandlers {\n                channel_request_handlers: data\n                    .channel_message_handlers\n                    .channel_request_handlers\n                    .into_iter()\n                    .map(|id| id.parse())\n                    .collect::<Result<_, _>>()?,\n                channel_notification_handlers: data\n                    .channel_message_handlers\n                    .channel_notification_handlers\n                    .into_iter()\n                    .map(|id| id.parse())\n                    .collect::<Result<_, _>>()?,\n            },\n            liburing: data.liburing.map(|liburing| LibUringDump {\n                sqe_process_count: liburing.sqe_process_count,\n                sqe_miss_count: liburing.sqe_miss_count,\n                user_data_miss_count: liburing.user_data_miss_count,\n            }),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WorkerUpdateSettingsRequest {\n    pub(crate) data: WorkerUpdateSettings,\n}\n\nimpl Request for WorkerUpdateSettingsRequest {\n    const METHOD: request::Method = request::Method::WorkerUpdateSettings;\n    type HandlerId = &'static str;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = worker::UpdateSettingsRequest::create(\n            &mut builder,\n            self.data\n                .log_level\n                .as_ref()\n                .map(|log_level| log_level.as_str()),\n            self.data.log_tags.as_ref().map(|log_tags| {\n                log_tags\n                    .iter()\n                    .map(|log_tag| log_tag.as_str())\n                    .collect::<Vec<_>>()\n            }),\n        );\n        let request_body = request::Body::create_worker_update_settings_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WorkerCreateWebRtcServerRequest {\n    pub(crate) webrtc_server_id: WebRtcServerId,\n    pub(crate) listen_infos: WebRtcServerListenInfos,\n}\n\nimpl Request for WorkerCreateWebRtcServerRequest {\n    const METHOD: request::Method = request::Method::WorkerCreateWebrtcserver;\n    type HandlerId = &'static str;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = worker::CreateWebRtcServerRequest::create(\n            &mut builder,\n            self.webrtc_server_id.to_string(),\n            self.listen_infos.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_worker_create_web_rtc_server_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WorkerCloseNotification {}\n\nimpl Notification for WorkerCloseNotification {\n    const EVENT: notification::Event = notification::Event::WorkerClose;\n    type HandlerId = &'static str;\n\n    fn into_bytes(self, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let notification = notification::Notification::create(\n            &mut builder,\n            handler_id.to_string(),\n            Self::EVENT,\n            None::<notification::Body>,\n        );\n        let message_body = message::Body::create_notification(&mut builder, notification);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WebRtcServerCloseRequest {\n    pub(crate) webrtc_server_id: WebRtcServerId,\n}\n\nimpl Request for WebRtcServerCloseRequest {\n    const METHOD: request::Method = request::Method::WorkerWebrtcserverClose;\n    type HandlerId = &'static str;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = worker::CloseWebRtcServerRequest::create(\n            &mut builder,\n            self.webrtc_server_id.to_string(),\n        );\n        let request_body =\n            request::Body::create_worker_close_web_rtc_server_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WebRtcServerDumpRequest {}\n\nimpl Request for WebRtcServerDumpRequest {\n    const METHOD: request::Method = request::Method::WebrtcserverDump;\n    type HandlerId = WebRtcServerId;\n    type Response = WebRtcServerDump;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::WebRtcServerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = web_rtc_server::DumpResponse::try_from(data)?;\n\n        Ok(WebRtcServerDump {\n            id: data.id.parse()?,\n            udp_sockets: data\n                .udp_sockets\n                .into_iter()\n                .map(|ip_port| WebRtcServerIpPort {\n                    ip: ip_port.ip.parse().unwrap(),\n                    port: ip_port.port,\n                })\n                .collect(),\n            tcp_servers: data\n                .tcp_servers\n                .into_iter()\n                .map(|ip_port| WebRtcServerIpPort {\n                    ip: ip_port.ip.parse().unwrap(),\n                    port: ip_port.port,\n                })\n                .collect(),\n            webrtc_transport_ids: data\n                .web_rtc_transport_ids\n                .into_iter()\n                .map(|id| id.parse())\n                .collect::<Result<_, _>>()?,\n            local_ice_username_fragments: data\n                .local_ice_username_fragments\n                .into_iter()\n                .map(|username_fragment| WebRtcServerIceUsernameFragment {\n                    local_ice_username_fragment: username_fragment\n                        .local_ice_username_fragment\n                        .parse()\n                        .unwrap(),\n                    webrtc_transport_id: username_fragment.web_rtc_transport_id.parse().unwrap(),\n                })\n                .collect(),\n            tuple_hashes: data\n                .tuple_hashes\n                .into_iter()\n                .map(|tuple_hash| WebRtcServerTupleHash {\n                    tuple_hash: tuple_hash.tuple_hash,\n                    webrtc_transport_id: tuple_hash.web_rtc_transport_id.parse().unwrap(),\n                })\n                .collect(),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WorkerCreateRouterRequest {\n    pub(crate) router_id: RouterId,\n}\n\nimpl Request for WorkerCreateRouterRequest {\n    const METHOD: request::Method = request::Method::WorkerCreateRouter;\n    type HandlerId = &'static str;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = worker::CreateRouterRequest::create(&mut builder, self.router_id.to_string());\n        let request_body = request::Body::create_worker_create_router_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCloseRequest {\n    pub(crate) router_id: RouterId,\n}\n\nimpl Request for RouterCloseRequest {\n    const METHOD: request::Method = request::Method::WorkerCloseRouter;\n    type HandlerId = &'static str;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = worker::CloseRouterRequest::create(&mut builder, self.router_id.to_string());\n        let request_body = request::Body::create_worker_close_router_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterDumpRequest {}\n\nimpl Request for RouterDumpRequest {\n    const METHOD: request::Method = request::Method::RouterDump;\n    type HandlerId = RouterId;\n    type Response = RouterDump;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::RouterDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = router::DumpResponse::try_from(data)?;\n\n        Ok(RouterDump {\n            id: data.id.parse()?,\n            map_consumer_id_producer_id: data\n                .map_consumer_id_producer_id\n                .into_iter()\n                .map(|key_value| Ok((key_value.key.parse()?, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_data_consumer_id_data_producer_id: data\n                .map_data_consumer_id_data_producer_id\n                .into_iter()\n                .map(|key_value| Ok((key_value.key.parse()?, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_data_producer_id_data_consumer_ids: data\n                .map_data_producer_id_data_consumer_ids\n                .into_iter()\n                .map(|key_values| {\n                    Ok((\n                        key_values.key.parse()?,\n                        key_values\n                            .values\n                            .into_iter()\n                            .map(|value| value.parse())\n                            .collect::<Result<_, _>>()?,\n                    ))\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_producer_id_consumer_ids: data\n                .map_producer_id_consumer_ids\n                .into_iter()\n                .map(|key_values| {\n                    Ok((\n                        key_values.key.parse()?,\n                        key_values\n                            .values\n                            .into_iter()\n                            .map(|value| value.parse())\n                            .collect::<Result<_, _>>()?,\n                    ))\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_producer_id_observer_ids: data\n                .map_producer_id_observer_ids\n                .into_iter()\n                .map(|key_values| {\n                    Ok((\n                        key_values.key.parse()?,\n                        key_values\n                            .values\n                            .into_iter()\n                            .map(|value| value.parse())\n                            .collect::<Result<_, _>>()?,\n                    ))\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            rtp_observer_ids: data\n                .rtp_observer_ids\n                .into_iter()\n                .map(|id| id.parse())\n                .collect::<Result<_, _>>()?,\n            transport_ids: data\n                .transport_ids\n                .into_iter()\n                .map(|id| id.parse())\n                .collect::<Result<_, _>>()?,\n        })\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateDirectTransportData {\n    transport_id: TransportId,\n    direct: bool,\n    max_message_size: u32,\n}\n\nimpl RouterCreateDirectTransportData {\n    pub(crate) fn from_options(\n        transport_id: TransportId,\n        direct_transport_options: &DirectTransportOptions,\n    ) -> Self {\n        Self {\n            transport_id,\n            direct: true,\n            max_message_size: direct_transport_options.max_message_size,\n        }\n    }\n}\n\nimpl ToFbs for RouterCreateDirectTransportData {\n    type FbsType = direct_transport::DirectTransportOptions;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        direct_transport::DirectTransportOptions {\n            base: Box::new(transport::Options {\n                direct: true,\n                max_message_size: Some(self.max_message_size),\n                initial_available_outgoing_bitrate: None,\n                enable_sctp: false,\n                num_sctp_streams: None,\n                max_sctp_message_size: 0,\n                sctp_send_buffer_size: 0,\n                is_data_channel: false,\n            }),\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCreateDirectTransportRequest {\n    pub(crate) data: RouterCreateDirectTransportData,\n}\n\nimpl Request for RouterCreateDirectTransportRequest {\n    const METHOD: request::Method = request::Method::RouterCreateDirecttransport;\n    type HandlerId = RouterId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = router::CreateDirectTransportRequest::create(\n            &mut builder,\n            self.data.transport_id.to_string(),\n            self.data.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_router_create_direct_transport_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(untagged)]\nenum RouterCreateWebrtcTransportListen {\n    #[serde(rename_all = \"camelCase\")]\n    Individual {\n        listen_infos: WebRtcTransportListenInfos,\n    },\n    Server {\n        #[serde(rename = \"webRtcServerId\")]\n        webrtc_server_id: WebRtcServerId,\n    },\n}\n\nimpl ToFbs for RouterCreateWebrtcTransportListen {\n    type FbsType = web_rtc_transport::Listen;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            RouterCreateWebrtcTransportListen::Individual { listen_infos } => {\n                web_rtc_transport::Listen::ListenIndividual(Box::new(\n                    web_rtc_transport::ListenIndividual {\n                        listen_infos: ToFbs::to_fbs(listen_infos.deref()),\n                    },\n                ))\n            }\n            RouterCreateWebrtcTransportListen::Server { webrtc_server_id } => {\n                web_rtc_transport::Listen::ListenServer(Box::new(web_rtc_transport::ListenServer {\n                    web_rtc_server_id: webrtc_server_id.to_string(),\n                }))\n            }\n        }\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateWebrtcTransportData {\n    transport_id: TransportId,\n    #[serde(flatten)]\n    listen: RouterCreateWebrtcTransportListen,\n    initial_available_outgoing_bitrate: u32,\n    enable_udp: bool,\n    enable_tcp: bool,\n    prefer_udp: bool,\n    prefer_tcp: bool,\n    ice_consent_timeout: u8,\n    enable_sctp: bool,\n    num_sctp_streams: NumSctpStreams,\n    max_sctp_message_size: u32,\n    sctp_send_buffer_size: u32,\n    is_data_channel: bool,\n}\n\nimpl RouterCreateWebrtcTransportData {\n    pub(crate) fn from_options(\n        transport_id: TransportId,\n        webrtc_transport_options: &WebRtcTransportOptions,\n    ) -> Self {\n        Self {\n            transport_id,\n            listen: match &webrtc_transport_options.listen {\n                WebRtcTransportListen::Individual { listen_infos } => {\n                    RouterCreateWebrtcTransportListen::Individual {\n                        listen_infos: listen_infos.clone(),\n                    }\n                }\n                WebRtcTransportListen::Server { webrtc_server } => {\n                    RouterCreateWebrtcTransportListen::Server {\n                        webrtc_server_id: webrtc_server.id(),\n                    }\n                }\n            },\n            initial_available_outgoing_bitrate: webrtc_transport_options\n                .initial_available_outgoing_bitrate,\n            enable_udp: webrtc_transport_options.enable_udp,\n            enable_tcp: webrtc_transport_options.enable_tcp,\n            prefer_udp: webrtc_transport_options.prefer_udp,\n            prefer_tcp: webrtc_transport_options.prefer_tcp,\n            ice_consent_timeout: webrtc_transport_options.ice_consent_timeout,\n            enable_sctp: webrtc_transport_options.enable_sctp,\n            num_sctp_streams: webrtc_transport_options.num_sctp_streams,\n            max_sctp_message_size: webrtc_transport_options.max_sctp_message_size,\n            sctp_send_buffer_size: webrtc_transport_options.sctp_send_buffer_size,\n            is_data_channel: true,\n        }\n    }\n}\n\nimpl ToFbs for RouterCreateWebrtcTransportData {\n    type FbsType = web_rtc_transport::WebRtcTransportOptions;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        web_rtc_transport::WebRtcTransportOptions {\n            base: Box::new(transport::Options {\n                direct: false,\n                max_message_size: None,\n                initial_available_outgoing_bitrate: Some(self.initial_available_outgoing_bitrate),\n                enable_sctp: self.enable_sctp,\n                num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())),\n                max_sctp_message_size: self.max_sctp_message_size,\n                sctp_send_buffer_size: self.sctp_send_buffer_size,\n                is_data_channel: true,\n            }),\n            listen: self.listen.to_fbs(),\n            enable_udp: self.enable_udp,\n            enable_tcp: self.enable_tcp,\n            prefer_udp: self.prefer_udp,\n            prefer_tcp: self.prefer_tcp,\n            ice_consent_timeout: self.ice_consent_timeout,\n        }\n    }\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct WebRtcTransportData {\n    pub(crate) ice_role: IceRole,\n    pub(crate) ice_parameters: IceParameters,\n    pub(crate) ice_candidates: Vec<IceCandidate>,\n    pub(crate) ice_state: Mutex<IceState>,\n    pub(crate) ice_selected_tuple: Mutex<Option<TransportTuple>>,\n    pub(crate) dtls_parameters: Mutex<DtlsParameters>,\n    pub(crate) dtls_state: Mutex<DtlsState>,\n    pub(crate) dtls_remote_cert: Mutex<Option<String>>,\n    pub(crate) sctp_parameters: Option<SctpParameters>,\n    pub(crate) sctp_state: Mutex<Option<SctpState>>,\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCreateWebRtcTransportRequest {\n    pub(crate) data: RouterCreateWebrtcTransportData,\n}\n\nimpl Request for RouterCreateWebRtcTransportRequest {\n    const METHOD: request::Method = request::Method::RouterCreateWebrtctransport;\n    type HandlerId = RouterId;\n    type Response = WebRtcTransportData;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let RouterCreateWebrtcTransportListen::Individual { listen_infos: _ } = self.data.listen\n        else {\n            panic!(\"RouterCreateWebrtcTransportListen variant must be Individual\");\n        };\n\n        let mut builder = Builder::new();\n        let data = router::CreateWebRtcTransportRequest::create(\n            &mut builder,\n            self.data.transport_id.to_string(),\n            self.data.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_router_create_web_rtc_transport_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::WebRtcTransportDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = web_rtc_transport::DumpResponse::try_from(data)?;\n\n        Ok(WebRtcTransportData {\n            ice_role: IceRole::from_fbs(&data.ice_role),\n            ice_parameters: IceParameters::from_fbs(data.ice_parameters.as_ref()),\n            ice_candidates: FromFbs::from_fbs(&data.ice_candidates),\n            ice_state: Mutex::new(IceState::from_fbs(&data.ice_state)),\n            ice_selected_tuple: Mutex::new(\n                data.ice_selected_tuple\n                    .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            ),\n            dtls_parameters: Mutex::new(DtlsParameters::from_fbs(data.dtls_parameters.as_ref())),\n            dtls_state: Mutex::new(DtlsState::from_fbs(&data.dtls_state)),\n            dtls_remote_cert: Mutex::new(None),\n            sctp_parameters: data\n                .base\n                .sctp_parameters\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCreateWebRtcTransportWithServerRequest {\n    pub(crate) data: RouterCreateWebrtcTransportData,\n}\n\nimpl Request for RouterCreateWebRtcTransportWithServerRequest {\n    const METHOD: request::Method = request::Method::RouterCreateWebrtctransportWithServer;\n    type HandlerId = RouterId;\n    type Response = WebRtcTransportData;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let RouterCreateWebrtcTransportListen::Server {\n            webrtc_server_id: _,\n        } = self.data.listen\n        else {\n            panic!(\"RouterCreateWebrtcTransportListen variant must be Server\");\n        };\n\n        let mut builder = Builder::new();\n        let data = router::CreateWebRtcTransportRequest::create(\n            &mut builder,\n            self.data.transport_id.to_string(),\n            self.data.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_router_create_web_rtc_transport_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::WebRtcTransportDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = web_rtc_transport::DumpResponse::try_from(data)?;\n\n        Ok(WebRtcTransportData {\n            ice_role: IceRole::from_fbs(&data.ice_role),\n            ice_parameters: IceParameters::from_fbs(data.ice_parameters.as_ref()),\n            ice_candidates: FromFbs::from_fbs(&data.ice_candidates),\n            ice_state: Mutex::new(IceState::from_fbs(&data.ice_state)),\n            ice_selected_tuple: Mutex::new(\n                data.ice_selected_tuple\n                    .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            ),\n            dtls_parameters: Mutex::new(DtlsParameters::from_fbs(data.dtls_parameters.as_ref())),\n            dtls_state: Mutex::new(DtlsState::from_fbs(&data.dtls_state)),\n            dtls_remote_cert: Mutex::new(None),\n            sctp_parameters: data\n                .base\n                .sctp_parameters\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)),\n        })\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreatePlainTransportData {\n    transport_id: TransportId,\n    listen_info: ListenInfo,\n    rtcp_listen_info: Option<ListenInfo>,\n    rtcp_mux: bool,\n    comedia: bool,\n    enable_sctp: bool,\n    num_sctp_streams: NumSctpStreams,\n    max_sctp_message_size: u32,\n    sctp_send_buffer_size: u32,\n    enable_srtp: bool,\n    srtp_crypto_suite: SrtpCryptoSuite,\n    is_data_channel: bool,\n}\n\nimpl RouterCreatePlainTransportData {\n    pub(crate) fn from_options(\n        transport_id: TransportId,\n        plain_transport_options: &PlainTransportOptions,\n    ) -> Self {\n        Self {\n            transport_id,\n            listen_info: plain_transport_options.listen_info.clone(),\n            rtcp_listen_info: plain_transport_options.rtcp_listen_info.clone(),\n            rtcp_mux: plain_transport_options.rtcp_mux,\n            comedia: plain_transport_options.comedia,\n            enable_sctp: plain_transport_options.enable_sctp,\n            num_sctp_streams: plain_transport_options.num_sctp_streams,\n            max_sctp_message_size: plain_transport_options.max_sctp_message_size,\n            sctp_send_buffer_size: plain_transport_options.sctp_send_buffer_size,\n            enable_srtp: plain_transport_options.enable_srtp,\n            srtp_crypto_suite: plain_transport_options.srtp_crypto_suite,\n            is_data_channel: false,\n        }\n    }\n}\n\nimpl ToFbs for RouterCreatePlainTransportData {\n    type FbsType = plain_transport::PlainTransportOptions;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        plain_transport::PlainTransportOptions {\n            base: Box::new(transport::Options {\n                direct: false,\n                max_message_size: None,\n                initial_available_outgoing_bitrate: None,\n                enable_sctp: self.enable_sctp,\n                num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())),\n                max_sctp_message_size: self.max_sctp_message_size,\n                sctp_send_buffer_size: self.sctp_send_buffer_size,\n                is_data_channel: self.is_data_channel,\n            }),\n            listen_info: Box::new(self.listen_info.clone().to_fbs()),\n            rtcp_listen_info: self\n                .rtcp_listen_info\n                .clone()\n                .map(|listen_info| Box::new(listen_info.to_fbs())),\n            rtcp_mux: self.rtcp_mux,\n            comedia: self.comedia,\n            enable_srtp: self.enable_srtp,\n            srtp_crypto_suite: Some(SrtpCryptoSuite::to_fbs(&self.srtp_crypto_suite)),\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCreatePlainTransportRequest {\n    pub(crate) data: RouterCreatePlainTransportData,\n}\n\nimpl Request for RouterCreatePlainTransportRequest {\n    const METHOD: request::Method = request::Method::RouterCreatePlaintransport;\n    type HandlerId = RouterId;\n    type Response = PlainTransportData;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = router::CreatePlainTransportRequest::create(\n            &mut builder,\n            self.data.transport_id.to_string(),\n            self.data.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_router_create_plain_transport_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::PlainTransportDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = plain_transport::DumpResponse::try_from(data)?;\n\n        Ok(PlainTransportData {\n            tuple: Mutex::new(TransportTuple::from_fbs(data.tuple.as_ref())),\n            rtcp_tuple: Mutex::new(\n                data.rtcp_tuple\n                    .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            ),\n            sctp_parameters: data\n                .base\n                .sctp_parameters\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)),\n            srtp_parameters: Mutex::new(\n                data.srtp_parameters\n                    .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())),\n            ),\n        })\n    }\n}\n\npub(crate) struct PlainTransportData {\n    // The following fields are present, but unused\n    // rtcp_mux: bool,\n    // comedia: bool,\n    pub(crate) tuple: Mutex<TransportTuple>,\n    pub(crate) rtcp_tuple: Mutex<Option<TransportTuple>>,\n    pub(crate) sctp_parameters: Option<SctpParameters>,\n    pub(crate) sctp_state: Mutex<Option<SctpState>>,\n    pub(crate) srtp_parameters: Mutex<Option<SrtpParameters>>,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreatePipeTransportData {\n    transport_id: TransportId,\n    listen_info: ListenInfo,\n    enable_sctp: bool,\n    num_sctp_streams: NumSctpStreams,\n    max_sctp_message_size: u32,\n    sctp_send_buffer_size: u32,\n    enable_rtx: bool,\n    enable_srtp: bool,\n    is_data_channel: bool,\n}\n\nimpl RouterCreatePipeTransportData {\n    pub(crate) fn from_options(\n        transport_id: TransportId,\n        pipe_transport_options: &PipeTransportOptions,\n    ) -> Self {\n        Self {\n            transport_id,\n            listen_info: pipe_transport_options.listen_info.clone(),\n            enable_sctp: pipe_transport_options.enable_sctp,\n            num_sctp_streams: pipe_transport_options.num_sctp_streams,\n            max_sctp_message_size: pipe_transport_options.max_sctp_message_size,\n            sctp_send_buffer_size: pipe_transport_options.sctp_send_buffer_size,\n            enable_rtx: pipe_transport_options.enable_rtx,\n            enable_srtp: pipe_transport_options.enable_srtp,\n            is_data_channel: false,\n        }\n    }\n}\n\nimpl ToFbs for RouterCreatePipeTransportData {\n    type FbsType = pipe_transport::PipeTransportOptions;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        pipe_transport::PipeTransportOptions {\n            base: Box::new(transport::Options {\n                direct: false,\n                max_message_size: None,\n                initial_available_outgoing_bitrate: None,\n                enable_sctp: self.enable_sctp,\n                num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())),\n                max_sctp_message_size: self.max_sctp_message_size,\n                sctp_send_buffer_size: self.sctp_send_buffer_size,\n                is_data_channel: self.is_data_channel,\n            }),\n            listen_info: Box::new(self.listen_info.clone().to_fbs()),\n            enable_rtx: self.enable_rtx,\n            enable_srtp: self.enable_srtp,\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RouterCreatePipeTransportRequest {\n    pub(crate) data: RouterCreatePipeTransportData,\n}\n\nimpl Request for RouterCreatePipeTransportRequest {\n    const METHOD: request::Method = request::Method::RouterCreatePipetransport;\n    type HandlerId = RouterId;\n    type Response = PipeTransportData;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = router::CreatePipeTransportRequest::create(\n            &mut builder,\n            self.data.transport_id.to_string(),\n            self.data.to_fbs(),\n        );\n        let request_body =\n            request::Body::create_router_create_pipe_transport_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::PipeTransportDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = pipe_transport::DumpResponse::try_from(data)?;\n\n        Ok(PipeTransportData {\n            tuple: Mutex::new(TransportTuple::from_fbs(data.tuple.as_ref())),\n            sctp_parameters: data\n                .base\n                .sctp_parameters\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)),\n            rtx: data.rtx,\n            srtp_parameters: Mutex::new(\n                data.srtp_parameters\n                    .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())),\n            ),\n        })\n    }\n}\n\npub(crate) struct PipeTransportData {\n    pub(crate) tuple: Mutex<TransportTuple>,\n    pub(crate) sctp_parameters: Option<SctpParameters>,\n    pub(crate) sctp_state: Mutex<Option<SctpState>>,\n    pub(crate) rtx: bool,\n    pub(crate) srtp_parameters: Mutex<Option<SrtpParameters>>,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateAudioLevelObserverRequest {\n    pub(crate) data: RouterCreateAudioLevelObserverData,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateAudioLevelObserverData {\n    rtp_observer_id: RtpObserverId,\n    max_entries: NonZeroU16,\n    threshold: i8,\n    interval: u16,\n}\n\nimpl RouterCreateAudioLevelObserverData {\n    pub(crate) fn from_options(\n        rtp_observer_id: RtpObserverId,\n        audio_level_observer_options: &AudioLevelObserverOptions,\n    ) -> Self {\n        Self {\n            rtp_observer_id,\n            max_entries: audio_level_observer_options.max_entries,\n            threshold: audio_level_observer_options.threshold,\n            interval: audio_level_observer_options.interval,\n        }\n    }\n}\n\nimpl Request for RouterCreateAudioLevelObserverRequest {\n    const METHOD: request::Method = request::Method::RouterCreateAudiolevelobserver;\n    type HandlerId = RouterId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let options = audio_level_observer::AudioLevelObserverOptions::create(\n            &mut builder,\n            u16::from(self.data.max_entries),\n            self.data.threshold,\n            self.data.interval,\n        );\n        let data = router::CreateAudioLevelObserverRequest::create(\n            &mut builder,\n            self.data.rtp_observer_id.to_string(),\n            options,\n        );\n        let request_body =\n            request::Body::create_router_create_audio_level_observer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateActiveSpeakerObserverRequest {\n    pub(crate) data: RouterCreateActiveSpeakerObserverData,\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RouterCreateActiveSpeakerObserverData {\n    rtp_observer_id: RtpObserverId,\n    interval: u16,\n}\n\nimpl RouterCreateActiveSpeakerObserverData {\n    pub(crate) fn from_options(\n        rtp_observer_id: RtpObserverId,\n        active_speaker_observer_options: &ActiveSpeakerObserverOptions,\n    ) -> Self {\n        Self {\n            rtp_observer_id,\n            interval: active_speaker_observer_options.interval,\n        }\n    }\n}\n\nimpl Request for RouterCreateActiveSpeakerObserverRequest {\n    const METHOD: request::Method = request::Method::RouterCreateActivespeakerobserver;\n    type HandlerId = RouterId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let options = active_speaker_observer::ActiveSpeakerObserverOptions::create(\n            &mut builder,\n            self.data.interval,\n        );\n        let data = router::CreateActiveSpeakerObserverRequest::create(\n            &mut builder,\n            self.data.rtp_observer_id.to_string(),\n            options,\n        );\n        let request_body =\n            request::Body::create_router_create_active_speaker_observer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportDumpRequest {}\n\nimpl Request for TransportDumpRequest {\n    const METHOD: request::Method = request::Method::TransportDump;\n    type HandlerId = TransportId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n#[derive(Debug)]\npub(crate) struct TransportGetStatsRequest {}\n\nimpl Request for TransportGetStatsRequest {\n    const METHOD: request::Method = request::Method::TransportGetStats;\n    type HandlerId = TransportId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportCloseRequest {\n    pub(crate) transport_id: TransportId,\n}\n\nimpl Request for TransportCloseRequest {\n    const METHOD: request::Method = request::Method::RouterCloseTransport;\n    type HandlerId = RouterId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data =\n            router::CloseTransportRequest::create(&mut builder, self.transport_id.to_string());\n        let request_body = request::Body::create_router_close_transport_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct WebRtcTransportConnectResponse {\n    pub(crate) dtls_local_role: DtlsRole,\n}\n\n#[derive(Debug)]\npub(crate) struct WebRtcTransportConnectRequest {\n    pub(crate) dtls_parameters: DtlsParameters,\n}\n\nimpl Request for WebRtcTransportConnectRequest {\n    const METHOD: request::Method = request::Method::WebrtctransportConnect;\n    type HandlerId = TransportId;\n    type Response = WebRtcTransportConnectResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data =\n            web_rtc_transport::ConnectRequest::create(&mut builder, self.dtls_parameters.to_fbs());\n        let request_body =\n            request::Body::create_web_rtc_transport_connect_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::WebRtcTransportConnectResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = web_rtc_transport::ConnectResponse::try_from(data)?;\n\n        Ok(WebRtcTransportConnectResponse {\n            dtls_local_role: DtlsRole::from_fbs(&data.dtls_local_role),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct PipeTransportConnectResponse {\n    pub(crate) tuple: TransportTuple,\n}\n\n#[derive(Debug)]\npub(crate) struct PipeTransportConnectRequest {\n    pub(crate) ip: IpAddr,\n    pub(crate) port: u16,\n    pub(crate) srtp_parameters: Option<SrtpParameters>,\n}\n\nimpl Request for PipeTransportConnectRequest {\n    const METHOD: request::Method = request::Method::PipetransportConnect;\n    type HandlerId = TransportId;\n    type Response = PipeTransportConnectResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = pipe_transport::ConnectRequest::create(\n            &mut builder,\n            self.ip.to_string(),\n            self.port,\n            ToFbs::to_fbs(&self.srtp_parameters),\n        );\n        let request_body = request::Body::create_pipe_transport_connect_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::PipeTransportConnectResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = pipe_transport::ConnectResponse::try_from(data)?;\n\n        Ok(PipeTransportConnectResponse {\n            tuple: TransportTuple::from_fbs(data.tuple.as_ref()),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct PlainTransportConnectResponse {\n    pub(crate) tuple: TransportTuple,\n    pub(crate) rtcp_tuple: Option<TransportTuple>,\n    pub(crate) srtp_parameters: Option<SrtpParameters>,\n}\n\n#[derive(Debug)]\npub(crate) struct TransportConnectPlainRequest {\n    pub(crate) ip: Option<IpAddr>,\n    pub(crate) port: Option<u16>,\n    pub(crate) rtcp_port: Option<u16>,\n    pub(crate) srtp_parameters: Option<SrtpParameters>,\n}\n\nimpl Request for TransportConnectPlainRequest {\n    const METHOD: request::Method = request::Method::PlaintransportConnect;\n    type HandlerId = TransportId;\n    type Response = PlainTransportConnectResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = plain_transport::ConnectRequest::create(\n            &mut builder,\n            self.ip.map(|ip| ip.to_string()),\n            self.port,\n            self.rtcp_port,\n            ToFbs::to_fbs(&self.srtp_parameters),\n        );\n        let request_body =\n            request::Body::create_plain_transport_connect_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::PlainTransportConnectResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = plain_transport::ConnectResponse::try_from(data)?;\n\n        Ok(PlainTransportConnectResponse {\n            tuple: TransportTuple::from_fbs(data.tuple.as_ref()),\n            rtcp_tuple: data\n                .rtcp_tuple\n                .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            srtp_parameters: data\n                .srtp_parameters\n                .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportSetMaxIncomingBitrateRequest {\n    pub(crate) bitrate: u32,\n}\n\nimpl Request for TransportSetMaxIncomingBitrateRequest {\n    const METHOD: request::Method = request::Method::TransportSetMaxIncomingBitrate;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = transport::SetMaxIncomingBitrateRequest::create(&mut builder, self.bitrate);\n        let request_body =\n            request::Body::create_transport_set_max_incoming_bitrate_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportSetMaxOutgoingBitrateRequest {\n    pub(crate) bitrate: u32,\n}\n\nimpl Request for TransportSetMaxOutgoingBitrateRequest {\n    const METHOD: request::Method = request::Method::TransportSetMaxOutgoingBitrate;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = transport::SetMaxOutgoingBitrateRequest::create(&mut builder, self.bitrate);\n        let request_body =\n            request::Body::create_transport_set_max_outgoing_bitrate_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportSetMinOutgoingBitrateRequest {\n    pub(crate) bitrate: u32,\n}\n\nimpl Request for TransportSetMinOutgoingBitrateRequest {\n    const METHOD: request::Method = request::Method::TransportSetMinOutgoingBitrate;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = transport::SetMinOutgoingBitrateRequest::create(&mut builder, self.bitrate);\n        let request_body =\n            request::Body::create_transport_set_min_outgoing_bitrate_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportRestartIceRequest {}\n\nimpl Request for TransportRestartIceRequest {\n    const METHOD: request::Method = request::Method::TransportRestartIce;\n    type HandlerId = TransportId;\n    type Response = IceParameters;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::TransportRestartIceResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = transport::RestartIceResponse::try_from(data)?;\n\n        Ok(IceParameters::from_fbs(&web_rtc_transport::IceParameters {\n            username_fragment: data.username_fragment,\n            password: data.password,\n            ice_lite: data.ice_lite,\n        }))\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportProduceRequest {\n    pub(crate) producer_id: ProducerId,\n    pub(crate) kind: MediaKind,\n    pub(crate) rtp_parameters: RtpParameters,\n    pub(crate) rtp_mapping: RtpMapping,\n    pub(crate) paused: bool,\n    pub(crate) key_frame_request_delay: u32,\n    pub(crate) enable_mediasoup_packet_id_header_extension: bool,\n}\n\n#[derive(Debug)]\npub(crate) struct TransportProduceResponse {\n    pub(crate) r#type: ProducerType,\n}\n\nimpl Request for TransportProduceRequest {\n    const METHOD: request::Method = request::Method::TransportProduce;\n    type HandlerId = TransportId;\n    type Response = TransportProduceResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::ProduceRequest::create(\n            &mut builder,\n            self.producer_id.to_string(),\n            self.kind.to_fbs(),\n            Box::new(self.rtp_parameters.to_fbs()),\n            Box::new(self.rtp_mapping.to_fbs()),\n            self.paused,\n            self.key_frame_request_delay,\n            self.enable_mediasoup_packet_id_header_extension,\n        );\n        let request_body = request::Body::create_transport_produce_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::TransportProduceResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = transport::ProduceResponse::try_from(data)?;\n\n        Ok(TransportProduceResponse {\n            r#type: ProducerType::from_fbs(&data.type_),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportConsumeRequest {\n    pub(crate) consumer_id: ConsumerId,\n    pub(crate) producer_id: ProducerId,\n    pub(crate) kind: MediaKind,\n    pub(crate) rtp_parameters: RtpParameters,\n    pub(crate) r#type: ConsumerType,\n    pub(crate) consumable_rtp_encodings: Vec<RtpEncodingParameters>,\n    pub(crate) paused: bool,\n    pub(crate) preferred_layers: Option<ConsumerLayers>,\n    pub(crate) ignore_dtx: bool,\n}\n\n#[derive(Debug)]\npub(crate) struct TransportConsumeResponse {\n    pub(crate) paused: bool,\n    pub(crate) producer_paused: bool,\n    pub(crate) score: ConsumerScore,\n    pub(crate) preferred_layers: Option<ConsumerLayers>,\n}\n\nimpl Request for TransportConsumeRequest {\n    const METHOD: request::Method = request::Method::TransportConsume;\n    type HandlerId = TransportId;\n    type Response = TransportConsumeResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::ConsumeRequest::create(\n            &mut builder,\n            self.consumer_id.to_string(),\n            self.producer_id.to_string(),\n            self.kind.to_fbs(),\n            Box::new(self.rtp_parameters.to_fbs()),\n            self.r#type.to_fbs(),\n            ToFbs::to_fbs(&self.consumable_rtp_encodings),\n            self.paused,\n            ToFbs::to_fbs(&self.preferred_layers),\n            // self.preferred_layers.map(ConsumerLayers::to_fbs),\n            self.ignore_dtx,\n        );\n        let request_body = request::Body::create_transport_consume_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::TransportConsumeResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = transport::ConsumeResponse::try_from(data)?;\n\n        Ok(TransportConsumeResponse {\n            paused: data.paused,\n            producer_paused: data.producer_paused,\n            score: ConsumerScore::from_fbs(data.score.as_ref()),\n            preferred_layers: data\n                .preferred_layers\n                .map(|preferred_layers| ConsumerLayers::from_fbs(preferred_layers.as_ref())),\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportProduceDataRequest {\n    pub(crate) data_producer_id: DataProducerId,\n    pub(crate) r#type: DataProducerType,\n    // #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub(crate) sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub(crate) label: String,\n    pub(crate) protocol: String,\n    pub(crate) paused: bool,\n}\n\n#[derive(Debug)]\npub(crate) struct TransportProduceDataResponse {\n    pub(crate) r#type: DataProducerType,\n    pub(crate) sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub(crate) label: String,\n    pub(crate) protocol: String,\n    pub(crate) paused: bool,\n}\n\nimpl Request for TransportProduceDataRequest {\n    const METHOD: request::Method = request::Method::TransportProduceData;\n    type HandlerId = TransportId;\n    type Response = TransportProduceDataResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::ProduceDataRequest::create(\n            &mut builder,\n            self.data_producer_id.to_string(),\n            match self.r#type {\n                DataProducerType::Sctp => data_producer::Type::Sctp,\n                DataProducerType::Direct => data_producer::Type::Direct,\n            },\n            ToFbs::to_fbs(&self.sctp_stream_parameters),\n            if self.label.is_empty() {\n                None\n            } else {\n                Some(self.label)\n            },\n            if self.protocol.is_empty() {\n                None\n            } else {\n                Some(self.protocol)\n            },\n            self.paused,\n        );\n        let request_body = request::Body::create_transport_produce_data_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataProducerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_producer::DumpResponse::try_from(data)?;\n\n        Ok(TransportProduceDataResponse {\n            r#type: match data.type_ {\n                data_producer::Type::Sctp => DataProducerType::Sctp,\n                data_producer::Type::Direct => DataProducerType::Direct,\n            },\n            sctp_stream_parameters: data.sctp_stream_parameters.map(|stream_parameters| {\n                SctpStreamParameters::from_fbs(stream_parameters.as_ref())\n            }),\n            label: data.label.to_string(),\n            protocol: data.protocol.to_string(),\n            paused: data.paused,\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportConsumeDataRequest {\n    pub(crate) data_consumer_id: DataConsumerId,\n    pub(crate) data_producer_id: DataProducerId,\n    pub(crate) r#type: DataConsumerType,\n    pub(crate) sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub(crate) label: String,\n    pub(crate) protocol: String,\n    pub(crate) paused: bool,\n    pub(crate) subchannels: Option<Vec<u16>>,\n}\n\n#[derive(Debug)]\npub(crate) struct TransportConsumeDataResponse {\n    pub(crate) r#type: DataConsumerType,\n    pub(crate) sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub(crate) label: String,\n    pub(crate) protocol: String,\n    pub(crate) paused: bool,\n    pub(crate) data_producer_paused: bool,\n    pub(crate) subchannels: Vec<u16>,\n}\n\nimpl Request for TransportConsumeDataRequest {\n    const METHOD: request::Method = request::Method::TransportConsumeData;\n    type HandlerId = TransportId;\n    type Response = TransportConsumeDataResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::ConsumeDataRequest::create(\n            &mut builder,\n            self.data_consumer_id.to_string(),\n            self.data_producer_id.to_string(),\n            match self.r#type {\n                DataConsumerType::Sctp => data_producer::Type::Sctp,\n                DataConsumerType::Direct => data_producer::Type::Direct,\n            },\n            ToFbs::to_fbs(&self.sctp_stream_parameters),\n            if self.label.is_empty() {\n                None\n            } else {\n                Some(self.label)\n            },\n            if self.protocol.is_empty() {\n                None\n            } else {\n                Some(self.protocol)\n            },\n            self.paused,\n            self.subchannels,\n        );\n        let request_body = request::Body::create_transport_consume_data_request(&mut builder, data);\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataConsumerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_consumer::DumpResponse::try_from(data)?;\n\n        Ok(TransportConsumeDataResponse {\n            r#type: match data.type_ {\n                data_producer::Type::Sctp => DataConsumerType::Sctp,\n                data_producer::Type::Direct => DataConsumerType::Direct,\n            },\n            sctp_stream_parameters: data.sctp_stream_parameters.map(|stream_parameters| {\n                SctpStreamParameters::from_fbs(stream_parameters.as_ref())\n            }),\n            label: data.label.to_string(),\n            protocol: data.protocol.to_string(),\n            paused: data.paused,\n            data_producer_paused: data.data_producer_paused,\n            subchannels: data.subchannels,\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct TransportEnableTraceEventRequest {\n    pub(crate) types: Vec<TransportTraceEventType>,\n}\n\nimpl Request for TransportEnableTraceEventRequest {\n    const METHOD: request::Method = request::Method::TransportEnableTraceEvent;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = transport::EnableTraceEventRequest {\n            events: ToFbs::to_fbs(&self.types),\n        };\n\n        let request_body = request::Body::TransportEnableTraceEventRequest(Box::new(data));\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n\n    fn default_for_soft_error() -> Option<Self::Response> {\n        None\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct TransportSendRtcpNotification {\n    pub(crate) rtcp_packet: Vec<u8>,\n}\n\nimpl Notification for TransportSendRtcpNotification {\n    const EVENT: notification::Event = notification::Event::TransportSendRtcp;\n    type HandlerId = TransportId;\n\n    fn into_bytes(self, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = transport::SendRtcpNotification::create(&mut builder, self.rtcp_packet);\n        let notification_body =\n            notification::Body::create_transport_send_rtcp_notification(&mut builder, data);\n\n        let notification = notification::Notification::create(\n            &mut builder,\n            handler_id.to_string(),\n            Self::EVENT,\n            Some(notification_body),\n        );\n        let message_body = message::Body::create_notification(&mut builder, notification);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ProducerCloseRequest {\n    pub(crate) producer_id: ProducerId,\n}\n\nimpl Request for ProducerCloseRequest {\n    const METHOD: request::Method = request::Method::TransportCloseProducer;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data =\n            transport::CloseProducerRequest::create(&mut builder, self.producer_id.to_string());\n        let request_body =\n            request::Body::create_transport_close_producer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ProducerDumpRequest {}\n\nimpl Request for ProducerDumpRequest {\n    const METHOD: request::Method = request::Method::ProducerDump;\n    type HandlerId = ProducerId;\n    type Response = ProducerDump;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::ProducerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        ProducerDump::try_from_fbs(data)\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ProducerGetStatsRequest {}\n\nimpl Request for ProducerGetStatsRequest {\n    const METHOD: request::Method = request::Method::ProducerGetStats;\n    type HandlerId = ProducerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ProducerPauseRequest {}\n\nimpl Request for ProducerPauseRequest {\n    const METHOD: request::Method = request::Method::ProducerPause;\n    type HandlerId = ProducerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ProducerResumeRequest {}\n\nimpl Request for ProducerResumeRequest {\n    const METHOD: request::Method = request::Method::ProducerResume;\n    type HandlerId = ProducerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ProducerEnableTraceEventRequest {\n    pub(crate) types: Vec<ProducerTraceEventType>,\n}\n\nimpl Request for ProducerEnableTraceEventRequest {\n    const METHOD: request::Method = request::Method::ProducerEnableTraceEvent;\n    type HandlerId = ProducerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = producer::EnableTraceEventRequest {\n            events: ToFbs::to_fbs(&self.types),\n        };\n\n        let request_body = request::Body::ProducerEnableTraceEventRequest(Box::new(data));\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n\n    fn default_for_soft_error() -> Option<Self::Response> {\n        None\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct ProducerSendNotification {\n    pub(crate) rtp_packet: Vec<u8>,\n}\n\nimpl Notification for ProducerSendNotification {\n    const EVENT: notification::Event = notification::Event::ProducerSend;\n    type HandlerId = ProducerId;\n\n    fn into_bytes(self, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = producer::SendNotification::create(&mut builder, self.rtp_packet);\n        let notification_body =\n            notification::Body::create_producer_send_notification(&mut builder, data);\n\n        let notification = notification::Notification::create(\n            &mut builder,\n            handler_id.to_string(),\n            Self::EVENT,\n            Some(notification_body),\n        );\n        let message_body = message::Body::create_notification(&mut builder, notification);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerCloseRequest {\n    pub(crate) consumer_id: ConsumerId,\n}\n\nimpl Request for ConsumerCloseRequest {\n    const METHOD: request::Method = request::Method::TransportCloseConsumer;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data =\n            transport::CloseConsumerRequest::create(&mut builder, self.consumer_id.to_string());\n        let request_body =\n            request::Body::create_transport_close_consumer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerDumpRequest {}\n\nimpl Request for ConsumerDumpRequest {\n    const METHOD: request::Method = request::Method::ConsumerDump;\n    type HandlerId = ConsumerId;\n    type Response = ConsumerDump;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::ConsumerDumpResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        ConsumerDump::try_from_fbs(data)\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerGetStatsRequest {}\n\nimpl Request for ConsumerGetStatsRequest {\n    const METHOD: request::Method = request::Method::ConsumerGetStats;\n    type HandlerId = ConsumerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerPauseRequest {}\n\nimpl Request for ConsumerPauseRequest {\n    const METHOD: request::Method = request::Method::ConsumerPause;\n    type HandlerId = ConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ConsumerResumeRequest {}\n\nimpl Request for ConsumerResumeRequest {\n    const METHOD: request::Method = request::Method::ConsumerResume;\n    type HandlerId = ConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ConsumerSetPreferredLayersRequest {\n    pub(crate) data: ConsumerLayers,\n}\n\nimpl Request for ConsumerSetPreferredLayersRequest {\n    const METHOD: request::Method = request::Method::ConsumerSetPreferredLayers;\n    type HandlerId = ConsumerId;\n    type Response = Option<ConsumerLayers>;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = consumer::SetPreferredLayersRequest::create(\n            &mut builder,\n            ConsumerLayers::to_fbs(&self.data),\n        );\n        let request_body =\n            request::Body::create_consumer_set_preferred_layers_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::ConsumerSetPreferredLayersResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = consumer::SetPreferredLayersResponse::try_from(data)?;\n\n        match data.preferred_layers {\n            Some(preferred_layers) => Ok(Some(ConsumerLayers::from_fbs(preferred_layers.as_ref()))),\n            None => Ok(None),\n        }\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ConsumerSetPriorityRequest {\n    pub(crate) priority: u8,\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct ConsumerSetPriorityResponse {\n    pub(crate) priority: u8,\n}\n\nimpl Request for ConsumerSetPriorityRequest {\n    const METHOD: request::Method = request::Method::ConsumerSetPriority;\n    type HandlerId = ConsumerId;\n    type Response = ConsumerSetPriorityResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = consumer::SetPriorityRequest::create(&mut builder, self.priority);\n        let request_body = request::Body::create_consumer_set_priority_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::ConsumerSetPriorityResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = consumer::SetPriorityResponse::try_from(data)?;\n\n        Ok(ConsumerSetPriorityResponse {\n            priority: data.priority,\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerRequestKeyFrameRequest {}\n\nimpl Request for ConsumerRequestKeyFrameRequest {\n    const METHOD: request::Method = request::Method::ConsumerRequestKeyFrame;\n    type HandlerId = ConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct ConsumerEnableTraceEventRequest {\n    pub(crate) types: Vec<ConsumerTraceEventType>,\n}\n\nimpl Request for ConsumerEnableTraceEventRequest {\n    const METHOD: request::Method = request::Method::ConsumerEnableTraceEvent;\n    type HandlerId = ConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = consumer::EnableTraceEventRequest {\n            events: ToFbs::to_fbs(&self.types),\n        };\n\n        let request_body = request::Body::ConsumerEnableTraceEventRequest(Box::new(data));\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n\n    fn default_for_soft_error() -> Option<Self::Response> {\n        None\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataProducerCloseRequest {\n    pub(crate) data_producer_id: DataProducerId,\n}\n\nimpl Request for DataProducerCloseRequest {\n    const METHOD: request::Method = request::Method::TransportCloseDataproducer;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::CloseDataProducerRequest::create(\n            &mut builder,\n            self.data_producer_id.to_string(),\n        );\n        let request_body =\n            request::Body::create_transport_close_data_producer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataProducerDumpRequest {}\n\nimpl Request for DataProducerDumpRequest {\n    const METHOD: request::Method = request::Method::DataproducerDump;\n    type HandlerId = DataProducerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataProducerGetStatsRequest {}\n\nimpl Request for DataProducerGetStatsRequest {\n    const METHOD: request::Method = request::Method::DataproducerGetStats;\n    type HandlerId = DataProducerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataProducerPauseRequest {}\n\nimpl Request for DataProducerPauseRequest {\n    const METHOD: request::Method = request::Method::DataproducerPause;\n    type HandlerId = DataProducerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataProducerResumeRequest {}\n\nimpl Request for DataProducerResumeRequest {\n    const METHOD: request::Method = request::Method::DataproducerResume;\n    type HandlerId = DataProducerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataProducerSendNotification {\n    pub(crate) ppid: u32,\n    pub(crate) payload: Vec<u8>,\n    pub(crate) subchannels: Option<Vec<u16>>,\n    pub(crate) required_subchannel: Option<u16>,\n}\n\nimpl Notification for DataProducerSendNotification {\n    const EVENT: notification::Event = notification::Event::DataproducerSend;\n    type HandlerId = DataProducerId;\n\n    fn into_bytes(self, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_producer::SendNotification::create(\n            &mut builder,\n            self.ppid,\n            self.payload,\n            self.subchannels,\n            self.required_subchannel,\n        );\n        let notification_body =\n            notification::Body::create_data_producer_send_notification(&mut builder, data);\n\n        let notification = notification::Notification::create(\n            &mut builder,\n            handler_id.to_string(),\n            Self::EVENT,\n            Some(notification_body),\n        );\n        let message_body = message::Body::create_notification(&mut builder, notification);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataConsumerCloseRequest {\n    pub(crate) data_consumer_id: DataConsumerId,\n}\n\nimpl Request for DataConsumerCloseRequest {\n    const METHOD: request::Method = request::Method::TransportCloseDataconsumer;\n    type HandlerId = TransportId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data = transport::CloseDataConsumerRequest::create(\n            &mut builder,\n            self.data_consumer_id.to_string(),\n        );\n        let request_body =\n            request::Body::create_transport_close_data_consumer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataConsumerDumpRequest {}\n\nimpl Request for DataConsumerDumpRequest {\n    const METHOD: request::Method = request::Method::DataconsumerDump;\n    type HandlerId = DataConsumerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct DataConsumerGetStatsRequest {}\n\nimpl Request for DataConsumerGetStatsRequest {\n    const METHOD: request::Method = request::Method::DataconsumerGetStats;\n    type HandlerId = DataConsumerId;\n    type Response = response::Body;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        match response {\n            Some(data) => Ok(data.try_into().unwrap()),\n            _ => {\n                panic!(\"Wrong message from worker: {response:?}\");\n            }\n        }\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataConsumerPauseRequest {}\n\nimpl Request for DataConsumerPauseRequest {\n    const METHOD: request::Method = request::Method::DataconsumerPause;\n    type HandlerId = DataConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataConsumerResumeRequest {}\n\nimpl Request for DataConsumerResumeRequest {\n    const METHOD: request::Method = request::Method::DataconsumerResume;\n    type HandlerId = DataConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataConsumerGetBufferedAmountRequest {}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataConsumerGetBufferedAmountResponse {\n    pub(crate) buffered_amount: u32,\n}\n\nimpl Request for DataConsumerGetBufferedAmountRequest {\n    const METHOD: request::Method = request::Method::DataconsumerGetBufferedAmount;\n    type HandlerId = DataConsumerId;\n    type Response = DataConsumerGetBufferedAmountResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataConsumerGetBufferedAmountResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_consumer::GetBufferedAmountResponse::try_from(data)?;\n\n        Ok(DataConsumerGetBufferedAmountResponse {\n            buffered_amount: data.buffered_amount,\n        })\n    }\n}\n\n#[derive(Debug, Serialize)]\npub(crate) struct DataConsumerSetBufferedAmountLowThresholdRequest {\n    pub(crate) threshold: u32,\n}\n\nimpl Request for DataConsumerSetBufferedAmountLowThresholdRequest {\n    const METHOD: request::Method = request::Method::DataconsumerSetBufferedAmountLowThreshold;\n    type HandlerId = DataConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_consumer::SetBufferedAmountLowThresholdRequest::create(\n            &mut builder,\n            self.threshold,\n        );\n        let request_body =\n            request::Body::create_data_consumer_set_buffered_amount_low_threshold_request(\n                &mut builder,\n                data,\n            );\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Clone, Serialize)]\n#[serde(into = \"u32\")]\npub(crate) struct DataConsumerSendRequest {\n    pub(crate) ppid: u32,\n    pub(crate) payload: Vec<u8>,\n}\n\nimpl Request for DataConsumerSendRequest {\n    const METHOD: request::Method = request::Method::DataconsumerSend;\n    type HandlerId = DataConsumerId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_consumer::SendRequest::create(&mut builder, self.ppid, self.payload);\n        let request_body = request::Body::create_data_consumer_send_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\nimpl From<DataConsumerSendRequest> for u32 {\n    fn from(request: DataConsumerSendRequest) -> Self {\n        request.ppid\n    }\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerSetSubchannelsRequest {\n    pub(crate) subchannels: Vec<u16>,\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerSetSubchannelsResponse {\n    pub(crate) subchannels: Vec<u16>,\n}\n\nimpl Request for DataConsumerSetSubchannelsRequest {\n    const METHOD: request::Method = request::Method::DataconsumerSetSubchannels;\n    type HandlerId = DataConsumerId;\n    type Response = DataConsumerSetSubchannelsResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_consumer::SetSubchannelsRequest::create(&mut builder, self.subchannels);\n        let request_body =\n            request::Body::create_data_consumer_set_subchannels_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataConsumerSetSubchannelsResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_consumer::SetSubchannelsResponse::try_from(data)?;\n\n        Ok(DataConsumerSetSubchannelsResponse {\n            subchannels: data.subchannels,\n        })\n    }\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerAddSubchannelRequest {\n    pub(crate) subchannel: u16,\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerAddSubchannelResponse {\n    pub(crate) subchannels: Vec<u16>,\n}\n\nimpl Request for DataConsumerAddSubchannelRequest {\n    const METHOD: request::Method = request::Method::DataconsumerAddSubchannel;\n    type HandlerId = DataConsumerId;\n    type Response = DataConsumerAddSubchannelResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_consumer::AddSubchannelRequest::create(&mut builder, self.subchannel);\n        let request_body =\n            request::Body::create_data_consumer_add_subchannel_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataConsumerAddSubchannelResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_consumer::AddSubchannelResponse::try_from(data)?;\n\n        Ok(DataConsumerAddSubchannelResponse {\n            subchannels: data.subchannels,\n        })\n    }\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerRemoveSubchannelRequest {\n    pub(crate) subchannel: u16,\n}\n\n#[derive(Debug, Clone, Serialize)]\npub(crate) struct DataConsumerRemoveSubchannelResponse {\n    pub(crate) subchannels: Vec<u16>,\n}\n\nimpl Request for DataConsumerRemoveSubchannelRequest {\n    const METHOD: request::Method = request::Method::DataconsumerRemoveSubchannel;\n    type HandlerId = DataConsumerId;\n    type Response = DataConsumerRemoveSubchannelResponse;\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data = data_consumer::RemoveSubchannelRequest::create(&mut builder, self.subchannel);\n        let request_body =\n            request::Body::create_data_consumer_remove_subchannel_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        let Some(response::BodyRef::DataConsumerRemoveSubchannelResponse(data)) = response else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        };\n\n        let data = data_consumer::RemoveSubchannelResponse::try_from(data)?;\n\n        Ok(DataConsumerRemoveSubchannelResponse {\n            subchannels: data.subchannels,\n        })\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RtpObserverCloseRequest {\n    pub(crate) rtp_observer_id: RtpObserverId,\n}\n\nimpl Request for RtpObserverCloseRequest {\n    const METHOD: request::Method = request::Method::RouterCloseRtpobserver;\n    type HandlerId = RouterId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let data =\n            router::CloseRtpObserverRequest::create(&mut builder, self.rtp_observer_id.to_string());\n        let request_body =\n            request::Body::create_router_close_rtp_observer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RtpObserverPauseRequest {}\n\nimpl Request for RtpObserverPauseRequest {\n    const METHOD: request::Method = request::Method::RtpobserverPause;\n    type HandlerId = RtpObserverId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug)]\npub(crate) struct RtpObserverResumeRequest {}\n\nimpl Request for RtpObserverResumeRequest {\n    const METHOD: request::Method = request::Method::RtpobserverResume;\n    type HandlerId = RtpObserverId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            None::<request::Body>,\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RtpObserverAddProducerRequest {\n    pub(crate) producer_id: ProducerId,\n}\n\nimpl Request for RtpObserverAddProducerRequest {\n    const METHOD: request::Method = request::Method::RtpobserverAddProducer;\n    type HandlerId = RtpObserverId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data =\n            rtp_observer::AddProducerRequest::create(&mut builder, self.producer_id.to_string());\n        let request_body =\n            request::Body::create_rtp_observer_add_producer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n\n#[derive(Debug, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub(crate) struct RtpObserverRemoveProducerRequest {\n    pub(crate) producer_id: ProducerId,\n}\n\nimpl Request for RtpObserverRemoveProducerRequest {\n    const METHOD: request::Method = request::Method::RtpobserverRemoveProducer;\n    type HandlerId = RtpObserverId;\n    type Response = ();\n\n    fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec<u8> {\n        let mut builder = Builder::new();\n\n        let data =\n            rtp_observer::RemoveProducerRequest::create(&mut builder, self.producer_id.to_string());\n        let request_body =\n            request::Body::create_rtp_observer_remove_producer_request(&mut builder, data);\n\n        let request = request::Request::create(\n            &mut builder,\n            id,\n            Self::METHOD,\n            handler_id.to_string(),\n            Some(request_body),\n        );\n        let message_body = message::Body::create_request(&mut builder, request);\n        let message = message::Message::create(&mut builder, message_body);\n\n        builder.finish(message, None).to_vec()\n    }\n\n    fn convert_response(\n        _response: Option<response::BodyRef<'_>>,\n    ) -> Result<Self::Response, Box<dyn Error + Send + Sync>> {\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "rust/src/ortc/tests.rs",
    "content": "use super::*;\nuse mediasoup_types::rtp_parameters::{MimeTypeAudio, RtpHeaderExtension};\nuse std::iter;\n\n#[test]\nfn generate_router_rtp_capabilities_succeeds() {\n    let media_codecs = vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"useinbandfec\", 1_u32.into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: Some(125), // Let's force it.\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"profile-level-id\", \"42e01f\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![], // Will be ignored.\n        },\n    ];\n\n    let rtp_capabilities = generate_router_rtp_capabilities(media_codecs)\n        .expect(\"Failed to generate router RTP capabilities\");\n\n    assert_eq!(\n        rtp_capabilities.codecs,\n        vec![\n            RtpCodecCapabilityFinalized::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: 100, // 100 is the first available dynamic PT.\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"foo\", \"bar\".into()),\n                ]),\n                rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc,],\n            },\n            RtpCodecCapabilityFinalized::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                preferred_payload_type: 125,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc\n                ],\n            },\n            RtpCodecCapabilityFinalized::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: 101, // 101 is the second available dynamic PT.\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 125u32.into())]),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapabilityFinalized::Video {\n                mime_type: MimeTypeVideo::H264,\n                preferred_payload_type: 102, // 102 is the third available dynamic PT.\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    // Since packetization-mode param was not included in the\n                    // H264 codec and it's default value is 0, it's not added\n                    // by ortc file.\n                    // (\"packetization-mode\", 0_u32.into()),\n                    (\"level-asymmetry-allowed\", 1_u32.into()),\n                    (\"profile-level-id\", \"42e01f\".into()),\n                    (\"foo\", \"bar\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecCapabilityFinalized::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: 103,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 102u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ]\n    );\n}\n\n#[test]\nfn generate_router_rtp_capabilities_unsupported() {\n    assert!(matches!(\n        generate_router_rtp_capabilities(vec![RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(1).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        }]),\n        Err(RtpCapabilitiesError::UnsupportedCodec { .. })\n    ));\n}\n\n#[test]\nfn generate_router_rtp_capabilities_too_many_codecs() {\n    assert!(matches!(\n        generate_router_rtp_capabilities(\n            iter::repeat_n(\n                RtpCodecCapability::Audio {\n                    mime_type: MimeTypeAudio::Opus,\n                    preferred_payload_type: None,\n                    clock_rate: NonZeroU32::new(48000).unwrap(),\n                    channels: NonZeroU8::new(2).unwrap(),\n                    parameters: RtpCodecParametersParameters::default(),\n                    rtcp_feedback: vec![],\n                },\n                100\n            )\n            .collect::<Vec<_>>()\n        ),\n        Err(RtpCapabilitiesError::CannotAllocate)\n    ));\n}\n\n#[test]\nfn get_producer_rtp_parameters_mapping_get_consumable_rtp_parameters_get_consumer_rtp_parameters_get_pipe_consumer_rtp_parameters_succeeds(\n) {\n    let media_codecs = vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"useinbandfec\", 1_u32.into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"lalala\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ];\n\n    let router_rtp_capabilities = generate_router_rtp_capabilities(media_codecs)\n        .expect(\"Failed to generate router RTP capabilities\");\n\n    let rtp_parameters = RtpParameters {\n        mid: None,\n        codecs: vec![\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::H264,\n                payload_type: 111,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"foo\", 1234u32.into()),\n                    (\"packetization-mode\", 1_u32.into()),\n                    (\"profile-level-id\", \"4d0032\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::GoogRemb,\n                ],\n            },\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                payload_type: 112,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 111_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ],\n        header_extensions: vec![\n            RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::Mid,\n                id: 1,\n                encrypt: false,\n            },\n            RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::VideoOrientation,\n                id: 2,\n                encrypt: false,\n            },\n        ],\n        encodings: vec![\n            RtpEncodingParameters {\n                ssrc: Some(11111111),\n                rtx: Some(RtpEncodingParametersRtx { ssrc: 11111112 }),\n                scalability_mode: ScalabilityMode::L1T3,\n                max_bitrate: Some(111111),\n                ..RtpEncodingParameters::default()\n            },\n            RtpEncodingParameters {\n                ssrc: Some(21111111),\n                rtx: Some(RtpEncodingParametersRtx { ssrc: 21111112 }),\n                scalability_mode: ScalabilityMode::L1T3,\n                max_bitrate: Some(222222),\n                ..RtpEncodingParameters::default()\n            },\n            RtpEncodingParameters {\n                rid: Some(\"high\".to_string()),\n                scalability_mode: ScalabilityMode::L1T3,\n                max_bitrate: Some(333333),\n                ..RtpEncodingParameters::default()\n            },\n        ],\n        rtcp: RtcpParameters {\n            cname: Some(\"qwerty1234\".to_string()),\n            ..RtcpParameters::default()\n        },\n        msid: None,\n    };\n\n    let rtp_mapping =\n        get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities)\n            .expect(\"Failed to get producer RTP parameters mapping\");\n\n    assert_eq!(\n        rtp_mapping.codecs,\n        vec![\n            RtpMappingCodec {\n                payload_type: 111,\n                mapped_payload_type: 101\n            },\n            RtpMappingCodec {\n                payload_type: 112,\n                mapped_payload_type: 102\n            },\n        ]\n    );\n\n    assert_eq!(rtp_mapping.encodings.first().unwrap().ssrc, Some(11111111));\n    assert_eq!(rtp_mapping.encodings.first().unwrap().rid, None);\n    assert_eq!(rtp_mapping.encodings.get(1).unwrap().ssrc, Some(21111111));\n    assert_eq!(rtp_mapping.encodings.get(1).unwrap().rid, None);\n    assert_eq!(rtp_mapping.encodings.get(2).unwrap().ssrc, None);\n    assert_eq!(\n        rtp_mapping.encodings.get(2).unwrap().rid,\n        Some(\"high\".to_string())\n    );\n\n    let consumable_rtp_parameters = get_consumable_rtp_parameters(\n        MediaKind::Video,\n        &rtp_parameters,\n        &router_rtp_capabilities,\n        &rtp_mapping,\n    );\n\n    assert_eq!(\n        consumable_rtp_parameters.codecs,\n        vec![\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::H264,\n                payload_type: 101,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"foo\", 1234u32.into()),\n                    (\"packetization-mode\", 1_u32.into()),\n                    (\"profile-level-id\", \"4d0032\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                payload_type: 102,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ]\n    );\n\n    assert_eq!(\n        consumable_rtp_parameters.encodings.first().unwrap().ssrc,\n        Some(rtp_mapping.encodings.first().unwrap().mapped_ssrc),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .first()\n            .unwrap()\n            .max_bitrate,\n        Some(111111),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .first()\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n    assert_eq!(\n        consumable_rtp_parameters.encodings.get(1).unwrap().ssrc,\n        Some(rtp_mapping.encodings.get(1).unwrap().mapped_ssrc),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .get(1)\n            .unwrap()\n            .max_bitrate,\n        Some(222222),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .get(1)\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n    assert_eq!(\n        consumable_rtp_parameters.encodings.get(2).unwrap().ssrc,\n        Some(rtp_mapping.encodings.get(2).unwrap().mapped_ssrc),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .get(2)\n            .unwrap()\n            .max_bitrate,\n        Some(333333),\n    );\n    assert_eq!(\n        consumable_rtp_parameters\n            .encodings\n            .get(2)\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n\n    assert_eq!(\n        consumable_rtp_parameters.rtcp,\n        RtcpParameters {\n            cname: rtp_parameters.rtcp.cname.clone(),\n            reduced_size: true,\n        }\n    );\n\n    let remote_rtp_capabilities = RtpCapabilities {\n        codecs: vec![\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: Some(100),\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::H264,\n                preferred_payload_type: Some(101),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"packetization-mode\", 1_u32.into()),\n                    (\"profile-level-id\", \"4d0032\".into()),\n                    (\"baz\", \"LOLOLO\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::Unsupported,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: Some(102),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ],\n        header_extensions: vec![\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::RtpStreamId,\n                preferred_id: 2,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                preferred_id: 6,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::VideoOrientation,\n                preferred_id: 8,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::TimeOffset,\n                preferred_id: 9,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n        ],\n    };\n\n    let consumer_rtp_parameters = get_consumer_rtp_parameters(\n        &consumable_rtp_parameters,\n        &remote_rtp_capabilities,\n        false,\n        true,\n    )\n    .expect(\"Failed to get consumer RTP parameters\");\n\n    assert_eq!(\n        consumer_rtp_parameters.codecs,\n        vec![\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::H264,\n                payload_type: 101,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"foo\", 1234u32.into()),\n                    (\"packetization-mode\", 1_u32.into()),\n                    (\"profile-level-id\", \"4d0032\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::Unsupported,\n                ],\n            },\n            RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                payload_type: 102,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ]\n    );\n\n    assert_eq!(consumer_rtp_parameters.encodings.len(), 1);\n    assert!(consumer_rtp_parameters\n        .encodings\n        .first()\n        .unwrap()\n        .ssrc\n        .is_some());\n    assert!(consumer_rtp_parameters\n        .encodings\n        .first()\n        .unwrap()\n        .rtx\n        .is_some());\n    assert_eq!(\n        consumer_rtp_parameters\n            .encodings\n            .first()\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L3T3,\n    );\n    assert_eq!(\n        consumer_rtp_parameters\n            .encodings\n            .first()\n            .unwrap()\n            .max_bitrate,\n        Some(333333),\n    );\n\n    assert_eq!(\n        consumer_rtp_parameters.header_extensions,\n        vec![\n            RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::Mid,\n                id: 1,\n                encrypt: false,\n            },\n            RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::VideoOrientation,\n                id: 8,\n                encrypt: false,\n            },\n            RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::TimeOffset,\n                id: 9,\n                encrypt: false,\n            },\n        ],\n    );\n\n    assert_eq!(\n        consumer_rtp_parameters.rtcp,\n        RtcpParameters {\n            cname: rtp_parameters.rtcp.cname.clone(),\n            reduced_size: true,\n        },\n    );\n\n    let pipe_consumer_rtp_parameters =\n        get_pipe_consumer_rtp_parameters(&consumable_rtp_parameters, false);\n\n    assert_eq!(\n        pipe_consumer_rtp_parameters.codecs,\n        vec![RtpCodecParameters::Video {\n            mime_type: MimeTypeVideo::H264,\n            payload_type: 101,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"foo\", 1234u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n            ]),\n            rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir],\n        }],\n    );\n\n    assert_eq!(pipe_consumer_rtp_parameters.encodings.len(), 3);\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .first()\n        .unwrap()\n        .ssrc\n        .is_some());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .first()\n        .unwrap()\n        .rtx\n        .is_none());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .first()\n        .unwrap()\n        .max_bitrate\n        .is_some());\n    assert_eq!(\n        pipe_consumer_rtp_parameters\n            .encodings\n            .first()\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(1)\n        .unwrap()\n        .ssrc\n        .is_some());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(1)\n        .unwrap()\n        .rtx\n        .is_none());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(1)\n        .unwrap()\n        .max_bitrate\n        .is_some());\n    assert_eq!(\n        pipe_consumer_rtp_parameters\n            .encodings\n            .get(1)\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(2)\n        .unwrap()\n        .ssrc\n        .is_some());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(2)\n        .unwrap()\n        .rtx\n        .is_none());\n    assert!(pipe_consumer_rtp_parameters\n        .encodings\n        .get(2)\n        .unwrap()\n        .max_bitrate\n        .is_some());\n    assert_eq!(\n        pipe_consumer_rtp_parameters\n            .encodings\n            .get(2)\n            .unwrap()\n            .scalability_mode,\n        ScalabilityMode::L1T3,\n    );\n\n    assert_eq!(\n        pipe_consumer_rtp_parameters.rtcp,\n        RtcpParameters {\n            cname: rtp_parameters.rtcp.cname,\n            reduced_size: true,\n        },\n    );\n}\n\n#[test]\nfn get_producer_rtp_parameters_mapping_unsupported() {\n    let media_codecs = vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"640032\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ];\n\n    let router_rtp_capabilities = generate_router_rtp_capabilities(media_codecs)\n        .expect(\"Failed to generate router RTP capabilities\");\n\n    let rtp_parameters = RtpParameters {\n        mid: None,\n        codecs: vec![RtpCodecParameters::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            payload_type: 120,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::Unsupported],\n        }],\n        header_extensions: vec![],\n        encodings: vec![RtpEncodingParameters {\n            ssrc: Some(11111111),\n            ..RtpEncodingParameters::default()\n        }],\n        rtcp: RtcpParameters {\n            cname: Some(\"qwerty1234\".to_string()),\n            ..RtcpParameters::default()\n        },\n        msid: None,\n    };\n\n    assert!(matches!(\n        get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities),\n        Err(RtpParametersMappingError::UnsupportedCodec { .. }),\n    ));\n}\n"
  },
  {
    "path": "rust/src/ortc.rs",
    "content": "use crate::fbs::{ToFbs, TryFromFbs};\nuse crate::supported_rtp_capabilities;\nuse mediasoup_sys::fbs::rtp_parameters;\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeType, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters,\n    RtpCapabilities, RtpCapabilitiesFinalized, RtpCodecCapability, RtpCodecCapabilityFinalized,\n    RtpCodecParameters, RtpCodecParametersParameters, RtpCodecParametersParametersValue,\n    RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtensionDirection,\n    RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters,\n};\nuse mediasoup_types::scalability_modes::ScalabilityMode;\nuse once_cell::sync::Lazy;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::borrow::Cow;\nuse std::collections::BTreeMap;\nuse std::convert::TryFrom;\nuse std::error::Error;\nuse std::mem;\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::ops::Deref;\nuse thiserror::Error;\n\n#[cfg(test)]\nmod tests;\n\nconst DYNAMIC_PAYLOAD_TYPES: &[u8] = &[\n    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,\n    119, 120, 121, 122, 123, 124, 125, 126, 127, 96, 97, 98, 99,\n];\n\n// TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n// extension.\n//\n// This is an object where we store some objects we may later need.\nstruct Cache {\n    pub dependency_descriptor_header_extension_parameters_for_pipe_consumer:\n        Option<RtpHeaderExtensionParameters>,\n}\n\nstatic CACHE: Lazy<Mutex<Cache>> = Lazy::new(|| {\n    Mutex::new(Cache {\n        dependency_descriptor_header_extension_parameters_for_pipe_consumer: None,\n    })\n});\n\n#[doc(hidden)]\n#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpMappingCodec {\n    pub payload_type: u8,\n    pub mapped_payload_type: u8,\n}\n\n#[doc(hidden)]\n#[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpMappingEncoding {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ssrc: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rid: Option<String>,\n    #[serde(default, skip_serializing_if = \"ScalabilityMode::is_none\")]\n    pub scalability_mode: ScalabilityMode,\n    pub mapped_ssrc: u32,\n}\n\n#[doc(hidden)]\n#[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\npub struct RtpMapping {\n    pub codecs: Vec<RtpMappingCodec>,\n    pub encodings: Vec<RtpMappingEncoding>,\n}\n\nimpl<'a> TryFromFbs<'a> for RtpMapping {\n    type FbsType = rtp_parameters::RtpMappingRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(mapping: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            codecs: mapping\n                .codecs()?\n                .iter()\n                .map(|mapping| {\n                    Ok(RtpMappingCodec {\n                        payload_type: mapping?.payload_type()?,\n                        mapped_payload_type: mapping?.mapped_payload_type()?,\n                    })\n                })\n                .collect::<Result<Vec<_>, Box<dyn Error + Send + Sync>>>()?,\n            encodings: mapping\n                .encodings()?\n                .iter()\n                .map(|mapping| {\n                    Ok(RtpMappingEncoding {\n                        rid: mapping?.rid()?.map(|rid| rid.to_string()),\n                        ssrc: mapping?.ssrc()?,\n                        scalability_mode: mapping?\n                            .scalability_mode()?\n                            .map(|maybe_scalability_mode| maybe_scalability_mode.parse())\n                            .transpose()?\n                            .unwrap_or_default(),\n                        mapped_ssrc: mapping?.mapped_ssrc()?,\n                    })\n                })\n                .collect::<Result<Vec<_>, Box<dyn Error + Send + Sync>>>()?,\n        })\n    }\n}\n\nimpl ToFbs for RtpMapping {\n    type FbsType = rtp_parameters::RtpMapping;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        rtp_parameters::RtpMapping {\n            codecs: self\n                .codecs\n                .iter()\n                .map(|mapping| rtp_parameters::CodecMapping {\n                    payload_type: mapping.payload_type,\n                    mapped_payload_type: mapping.mapped_payload_type,\n                })\n                .collect(),\n            encodings: self\n                .encodings\n                .iter()\n                .map(|mapping| rtp_parameters::EncodingMapping {\n                    rid: mapping.rid.clone().map(|rid| rid.to_string()),\n                    ssrc: mapping.ssrc,\n                    scalability_mode: Some(mapping.scalability_mode.to_string()),\n                    mapped_ssrc: mapping.mapped_ssrc,\n                })\n                .collect(),\n        }\n    }\n}\n\n/// Error caused by invalid RTP parameters.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum RtpParametersError {\n    /// Invalid codec apt parameter.\n    #[error(\"Invalid codec apt parameter {0}\")]\n    InvalidAptParameter(Cow<'static, str>),\n}\n\n/// Error caused by invalid RTP capabilities.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum RtpCapabilitiesError {\n    /// Media codec not supported.\n    #[error(\"Media codec not supported [mime_type:{mime_type:?}\")]\n    UnsupportedCodec {\n        /// Mime type\n        mime_type: MimeType,\n    },\n    /// Cannot allocate more dynamic codec payload types.\n    #[error(\"Cannot allocate more dynamic codec payload types\")]\n    CannotAllocate,\n    /// Invalid codec apt parameter.\n    #[error(\"Invalid codec apt parameter {0}\")]\n    InvalidAptParameter(Cow<'static, str>),\n    /// Duplicated preferred payload type\n    #[error(\"Duplicated preferred payload type {0}\")]\n    DuplicatedPreferredPayloadType(u8),\n}\n\n/// Error caused by invalid or unsupported RTP parameters given.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum RtpParametersMappingError {\n    /// Unsupported codec.\n    #[error(\"Unsupported codec [mime_type:{mime_type:?}, payloadType:{payload_type}]\")]\n    UnsupportedCodec {\n        /// Mime type.\n        mime_type: MimeType,\n        /// Payload type.\n        payload_type: u8,\n    },\n    /// No RTX codec for capability codec PT.\n    #[error(\"No RTX codec for capability codec PT {preferred_payload_type}\")]\n    UnsupportedRtxCodec {\n        /// Preferred payload type.\n        preferred_payload_type: u8,\n    },\n    /// Missing media codec found for RTX PT.\n    #[error(\"Missing media codec found for RTX PT {payload_type}\")]\n    MissingMediaCodecForRtx {\n        /// Payload type.\n        payload_type: u8,\n    },\n}\n\n/// Error caused by bad consumer RTP parameters.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum ConsumerRtpParametersError {\n    /// Invalid capabilities\n    #[error(\"Invalid capabilities: {0}\")]\n    InvalidCapabilities(RtpCapabilitiesError),\n    /// No compatible media codecs\n    #[error(\"No compatible media codecs\")]\n    NoCompatibleMediaCodecs,\n}\n\nfn generate_ssrc() -> u32 {\n    fastrand::u32(100_000_000..999_999_999)\n}\n\n/// Validates [`RtpParameters`].\npub(crate) fn validate_rtp_parameters(\n    rtp_parameters: &RtpParameters,\n) -> Result<(), RtpParametersError> {\n    for codec in &rtp_parameters.codecs {\n        validate_rtp_codec_parameters(codec)?;\n    }\n\n    Ok(())\n}\n\n/// Validates [`RtpCodecParameters`].\nfn validate_rtp_codec_parameters(codec: &RtpCodecParameters) -> Result<(), RtpParametersError> {\n    for (key, value) in codec.parameters().iter() {\n        // Specific parameters validation.\n        if key.as_ref() == \"apt\" {\n            match value {\n                RtpCodecParametersParametersValue::Number(_) => {\n                    // Good\n                }\n                RtpCodecParametersParametersValue::String(string) => {\n                    return Err(RtpParametersError::InvalidAptParameter(string.clone()));\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\n// Validates [`RtpCodecCapability`].\nfn validate_rtp_codec_capability(codec: &RtpCodecCapability) -> Result<(), RtpCapabilitiesError> {\n    for (key, value) in codec.parameters().iter() {\n        // Specific parameters validation.\n        if key.as_ref() == \"apt\" {\n            match value {\n                RtpCodecParametersParametersValue::Number(_) => {\n                    // Good\n                }\n                RtpCodecParametersParametersValue::String(string) => {\n                    return Err(RtpCapabilitiesError::InvalidAptParameter(string.clone()));\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\n/// Validates [`RtpCapabilities`].\npub(crate) fn validate_rtp_capabilities(\n    caps: &RtpCapabilities,\n) -> Result<(), RtpCapabilitiesError> {\n    for codec in &caps.codecs {\n        validate_rtp_codec_capability(codec)?;\n    }\n\n    Ok(())\n}\n\n/// Generate RTP capabilities for the Router based on the given media codecs and mediasoup supported\n/// RTP capabilities.\npub(crate) fn generate_router_rtp_capabilities(\n    mut media_codecs: Vec<RtpCodecCapability>,\n) -> Result<RtpCapabilitiesFinalized, RtpCapabilitiesError> {\n    let supported_rtp_capabilities = supported_rtp_capabilities::get_supported_rtp_capabilities();\n\n    validate_rtp_capabilities(&supported_rtp_capabilities)?;\n\n    let mut dynamic_payload_types = Vec::from(DYNAMIC_PAYLOAD_TYPES);\n    let mut caps = RtpCapabilitiesFinalized {\n        codecs: vec![],\n        header_extensions: supported_rtp_capabilities.header_extensions,\n    };\n\n    for media_codec in &mut media_codecs {\n        validate_rtp_codec_capability(media_codec)?;\n\n        let codec = match supported_rtp_capabilities\n            .codecs\n            .iter()\n            .find(|supported_codec| {\n                match_codecs(media_codec.deref().into(), (*supported_codec).into(), false).is_ok()\n            }) {\n            Some(codec) => codec,\n            None => {\n                return Err(RtpCapabilitiesError::UnsupportedCodec {\n                    mime_type: media_codec.mime_type(),\n                });\n            }\n        };\n\n        let preferred_payload_type = match media_codec.preferred_payload_type() {\n            Some(preferred_payload_type) => {\n                // If the given media codec has preferred_payload_type, keep it.\n                // Also remove the payload_type from the list of available dynamic values.\n                dynamic_payload_types.retain(|&pt| pt != preferred_payload_type);\n\n                preferred_payload_type\n            }\n            None => {\n                if let Some(preferred_payload_type) = codec.preferred_payload_type() {\n                    // Otherwise if the supported codec has preferredPayloadType, use it.\n                    // No need to remove it from the list since it's not a dynamic value.\n                    preferred_payload_type\n                } else {\n                    // Otherwise choose a dynamic one.\n                    if dynamic_payload_types.is_empty() {\n                        return Err(RtpCapabilitiesError::CannotAllocate);\n                    }\n                    // Take the first available payload type and remove it from the list.\n                    dynamic_payload_types.remove(0)\n                }\n            }\n        };\n\n        // Ensure there is not duplicated preferredPayloadType values.\n        for codec in &caps.codecs {\n            if codec.preferred_payload_type() == preferred_payload_type {\n                return Err(RtpCapabilitiesError::DuplicatedPreferredPayloadType(\n                    preferred_payload_type,\n                ));\n            }\n        }\n\n        let codec_finalized = match codec {\n            RtpCodecCapability::Audio {\n                mime_type,\n                preferred_payload_type: _,\n                clock_rate,\n                channels,\n                parameters,\n                rtcp_feedback,\n            } => RtpCodecCapabilityFinalized::Audio {\n                mime_type: *mime_type,\n                preferred_payload_type,\n                clock_rate: *clock_rate,\n                channels: *channels,\n                parameters: {\n                    // Merge the media codec parameters.\n                    let mut parameters = parameters.clone();\n                    parameters.extend(mem::take(media_codec.parameters_mut()));\n                    parameters\n                },\n                rtcp_feedback: rtcp_feedback.clone(),\n            },\n            RtpCodecCapability::Video {\n                mime_type,\n                preferred_payload_type: _,\n                clock_rate,\n                parameters,\n                rtcp_feedback,\n            } => RtpCodecCapabilityFinalized::Video {\n                mime_type: *mime_type,\n                preferred_payload_type,\n                clock_rate: *clock_rate,\n                parameters: {\n                    // Merge the media codec parameters.\n                    let mut parameters = parameters.clone();\n                    parameters.extend(mem::take(media_codec.parameters_mut()));\n                    parameters\n                },\n                rtcp_feedback: rtcp_feedback.clone(),\n            },\n        };\n\n        // Add a RTX video codec if video.\n        if matches!(codec_finalized, RtpCodecCapabilityFinalized::Video { .. }) {\n            if dynamic_payload_types.is_empty() {\n                return Err(RtpCapabilitiesError::CannotAllocate);\n            }\n            // Take the first available payload_type and remove it from the list.\n            let payload_type = dynamic_payload_types.remove(0);\n\n            let rtx_codec = RtpCodecCapabilityFinalized::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: payload_type,\n                clock_rate: codec_finalized.clock_rate(),\n                parameters: RtpCodecParametersParameters::from([(\n                    \"apt\",\n                    codec_finalized.preferred_payload_type().into(),\n                )]),\n                rtcp_feedback: vec![],\n            };\n\n            // Append to the codec list.\n            caps.codecs.push(codec_finalized);\n            caps.codecs.push(rtx_codec);\n        } else {\n            // Append to the codec list.\n            caps.codecs.push(codec_finalized);\n        }\n    }\n\n    // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n    // extension.\n    //\n    // We need to create and store this Dependency-Descriptor header extension to\n    // leter be used by `getPipeConsumerRtpParameters()` function.\n    let dependency_descriptor_header_extension_for_pipe_consumer =\n        caps.header_extensions.iter().find(|ext| {\n            ext.uri == RtpHeaderExtensionUri::DependencyDescriptor\n                && ext.direction != RtpHeaderExtensionDirection::SendRecv\n        });\n\n    if let Some(dependency_descriptor) = dependency_descriptor_header_extension_for_pipe_consumer {\n        if dependency_descriptor.direction != RtpHeaderExtensionDirection::SendRecv {\n            let mut cache = CACHE.lock();\n            cache.dependency_descriptor_header_extension_parameters_for_pipe_consumer =\n                Some(RtpHeaderExtensionParameters {\n                    uri: dependency_descriptor.uri,\n                    id: dependency_descriptor.preferred_id,\n                    encrypt: dependency_descriptor.preferred_encrypt,\n                });\n        }\n    }\n\n    Ok(caps)\n}\n\n/// Get a mapping of codec payloads and encodings of the given Producer RTP parameters as values\n/// expected by the Router.\npub(crate) fn get_producer_rtp_parameters_mapping(\n    rtp_parameters: &RtpParameters,\n    rtp_capabilities: &RtpCapabilitiesFinalized,\n) -> Result<RtpMapping, RtpParametersMappingError> {\n    let mut rtp_mapping = RtpMapping::default();\n\n    // Match parameters media codecs to capabilities media codecs.\n    let mut codec_to_cap_codec =\n        BTreeMap::<&RtpCodecParameters, Cow<'_, RtpCodecCapabilityFinalized>>::new();\n\n    for codec in &rtp_parameters.codecs {\n        if codec.is_rtx() {\n            continue;\n        }\n\n        // Search for the same media codec in capabilities.\n        match rtp_capabilities.codecs.iter().find_map(|cap_codec| {\n            match_codecs(codec.into(), cap_codec.into(), true)\n                .ok()\n                .map(|profile_level_id| {\n                    // This is rather ugly, but we need to fix `profile-level-id` and this was the\n                    // quickest way to do it\n                    profile_level_id.map_or(Cow::Borrowed(cap_codec), |profile_level_id| {\n                        let mut cap_codec = cap_codec.clone();\n                        cap_codec\n                            .parameters_mut()\n                            .insert(\"profile-level-id\", profile_level_id);\n                        Cow::Owned(cap_codec)\n                    })\n                })\n        }) {\n            Some(matched_codec_capability) => {\n                codec_to_cap_codec.insert(codec, matched_codec_capability);\n            }\n            None => {\n                return Err(RtpParametersMappingError::UnsupportedCodec {\n                    mime_type: codec.mime_type(),\n                    payload_type: codec.payload_type(),\n                });\n            }\n        }\n    }\n\n    // Match parameters RTX codecs to capabilities RTX codecs.\n    for codec in &rtp_parameters.codecs {\n        if !codec.is_rtx() {\n            continue;\n        }\n\n        // Search for the associated media codec.\n        let associated_media_codec = rtp_parameters.codecs.iter().find(|media_codec| {\n            let media_codec_payload_type = media_codec.payload_type();\n            let codec_parameters_apt = codec.parameters().get(\"apt\");\n\n            match codec_parameters_apt {\n                Some(RtpCodecParametersParametersValue::Number(apt)) => {\n                    u32::from(media_codec_payload_type) == *apt\n                }\n                _ => false,\n            }\n        });\n\n        match associated_media_codec {\n            Some(associated_media_codec) => {\n                let cap_media_codec = codec_to_cap_codec.get(associated_media_codec).unwrap();\n\n                // Ensure that the capabilities media codec has a RTX codec.\n                let associated_cap_rtx_codec = rtp_capabilities.codecs.iter().find(|cap_codec| {\n                    if !cap_codec.is_rtx() {\n                        return false;\n                    }\n\n                    let cap_codec_parameters_apt = cap_codec.parameters().get(\"apt\");\n                    match cap_codec_parameters_apt {\n                        Some(RtpCodecParametersParametersValue::Number(apt)) => {\n                            u32::from(cap_media_codec.preferred_payload_type()) == *apt\n                        }\n                        _ => false,\n                    }\n                });\n\n                match associated_cap_rtx_codec {\n                    Some(associated_cap_rtx_codec) => {\n                        codec_to_cap_codec.insert(codec, Cow::Borrowed(associated_cap_rtx_codec));\n                    }\n                    None => {\n                        return Err(RtpParametersMappingError::UnsupportedRtxCodec {\n                            preferred_payload_type: cap_media_codec.preferred_payload_type(),\n                        });\n                    }\n                }\n            }\n            None => {\n                return Err(RtpParametersMappingError::MissingMediaCodecForRtx {\n                    payload_type: codec.payload_type(),\n                });\n            }\n        }\n    }\n\n    // Generate codecs mapping.\n    for (codec, cap_codec) in codec_to_cap_codec {\n        rtp_mapping.codecs.push(RtpMappingCodec {\n            payload_type: codec.payload_type(),\n            mapped_payload_type: cap_codec.preferred_payload_type(),\n        });\n    }\n\n    // Generate encodings mapping.\n    let mut mapped_ssrc: u32 = generate_ssrc();\n\n    for encoding in &rtp_parameters.encodings {\n        rtp_mapping.encodings.push(RtpMappingEncoding {\n            ssrc: encoding.ssrc,\n            rid: encoding.rid.clone(),\n            scalability_mode: encoding.scalability_mode.clone(),\n            mapped_ssrc,\n        });\n\n        mapped_ssrc += 1;\n    }\n\n    Ok(rtp_mapping)\n}\n\n// Generate RTP parameters to be internally used by Consumers given the RTP parameters of a Producer\n// and the RTP capabilities of the Router.\npub(crate) fn get_consumable_rtp_parameters(\n    kind: MediaKind,\n    params: &RtpParameters,\n    caps: &RtpCapabilitiesFinalized,\n    rtp_mapping: &RtpMapping,\n) -> RtpParameters {\n    let mut consumable_params = RtpParameters::default();\n\n    for codec in &params.codecs {\n        if codec.is_rtx() {\n            continue;\n        }\n\n        let consumable_codec_pt = rtp_mapping\n            .codecs\n            .iter()\n            .find(|entry| entry.payload_type == codec.payload_type())\n            .unwrap()\n            .mapped_payload_type;\n\n        let consumable_codec = match caps\n            .codecs\n            .iter()\n            .find(|cap_codec| cap_codec.preferred_payload_type() == consumable_codec_pt)\n            .unwrap()\n        {\n            RtpCodecCapabilityFinalized::Audio {\n                mime_type,\n                preferred_payload_type,\n                clock_rate,\n                channels,\n                parameters: _,\n                rtcp_feedback,\n            } => {\n                RtpCodecParameters::Audio {\n                    mime_type: *mime_type,\n                    payload_type: *preferred_payload_type,\n                    clock_rate: *clock_rate,\n                    channels: *channels,\n                    // Keep the Producer codec parameters.\n                    parameters: codec.parameters().clone(),\n                    rtcp_feedback: rtcp_feedback.clone(),\n                }\n            }\n            RtpCodecCapabilityFinalized::Video {\n                mime_type,\n                preferred_payload_type,\n                clock_rate,\n                parameters: _,\n                rtcp_feedback,\n            } => {\n                RtpCodecParameters::Video {\n                    mime_type: *mime_type,\n                    payload_type: *preferred_payload_type,\n                    clock_rate: *clock_rate,\n                    // Keep the Producer codec parameters.\n                    parameters: codec.parameters().clone(),\n                    rtcp_feedback: rtcp_feedback.clone(),\n                }\n            }\n        };\n\n        let consumable_cap_rtx_codec = caps.codecs.iter().find(|cap_rtx_codec| {\n            if !cap_rtx_codec.is_rtx() {\n                return false;\n            }\n\n            let cap_rtx_codec_parameters_apt = cap_rtx_codec.parameters().get(\"apt\");\n\n            match cap_rtx_codec_parameters_apt {\n                Some(RtpCodecParametersParametersValue::Number(apt)) => {\n                    u8::try_from(*apt).is_ok_and(|apt| apt == consumable_codec.payload_type())\n                }\n                _ => false,\n            }\n        });\n\n        consumable_params.codecs.push(consumable_codec);\n\n        if let Some(consumable_cap_rtx_codec) = consumable_cap_rtx_codec {\n            let consumable_rtx_codec = match consumable_cap_rtx_codec {\n                RtpCodecCapabilityFinalized::Audio {\n                    mime_type,\n                    preferred_payload_type,\n                    clock_rate,\n                    channels,\n                    parameters,\n                    rtcp_feedback,\n                } => RtpCodecParameters::Audio {\n                    mime_type: *mime_type,\n                    payload_type: *preferred_payload_type,\n                    clock_rate: *clock_rate,\n                    channels: *channels,\n                    parameters: parameters.clone(),\n                    rtcp_feedback: rtcp_feedback.clone(),\n                },\n                RtpCodecCapabilityFinalized::Video {\n                    mime_type,\n                    preferred_payload_type,\n                    clock_rate,\n                    parameters,\n                    rtcp_feedback,\n                } => RtpCodecParameters::Video {\n                    mime_type: *mime_type,\n                    payload_type: *preferred_payload_type,\n                    clock_rate: *clock_rate,\n                    parameters: parameters.clone(),\n                    rtcp_feedback: rtcp_feedback.clone(),\n                },\n            };\n\n            consumable_params.codecs.push(consumable_rtx_codec);\n        }\n    }\n\n    for cap_ext in &caps.header_extensions {\n        // Just take RTP header extension that can be used in Consumers.\n        if cap_ext.kind != kind {\n            continue;\n        }\n        if !matches!(\n            cap_ext.direction,\n            RtpHeaderExtensionDirection::SendRecv | RtpHeaderExtensionDirection::SendOnly\n        ) {\n            continue;\n        }\n\n        let consumable_ext = RtpHeaderExtensionParameters {\n            uri: cap_ext.uri,\n            id: cap_ext.preferred_id,\n            encrypt: cap_ext.preferred_encrypt,\n        };\n\n        consumable_params.header_extensions.push(consumable_ext);\n    }\n\n    for (consumable_encoding, mapped_ssrc) in params.encodings.iter().zip(\n        rtp_mapping\n            .encodings\n            .iter()\n            .map(|encoding| encoding.mapped_ssrc),\n    ) {\n        let mut consumable_encoding = consumable_encoding.clone();\n        // Remove useless fields.\n        consumable_encoding.rid.take();\n        consumable_encoding.rtx.take();\n        consumable_encoding.codec_payload_type.take();\n\n        // Set the mapped ssrc.\n        consumable_encoding.ssrc = Some(mapped_ssrc);\n\n        consumable_params.encodings.push(consumable_encoding);\n    }\n\n    consumable_params.rtcp = RtcpParameters {\n        cname: params.rtcp.cname.clone(),\n        reduced_size: true,\n    };\n\n    consumable_params.msid.clone_from(&params.msid);\n\n    consumable_params\n}\n\n/// Check whether the given RTP capabilities can consume the given Producer.\npub(crate) fn can_consume(\n    consumable_params: &RtpParameters,\n    caps: &RtpCapabilities,\n) -> Result<bool, RtpCapabilitiesError> {\n    validate_rtp_capabilities(caps)?;\n\n    let mut matching_codecs = Vec::<&RtpCodecParameters>::new();\n\n    for codec in &consumable_params.codecs {\n        if caps\n            .codecs\n            .iter()\n            .any(|cap_codec| match_codecs(cap_codec.into(), codec.into(), true).is_ok())\n        {\n            matching_codecs.push(codec);\n        }\n    }\n\n    // Ensure there is at least one media codec.\n    Ok(matching_codecs\n        .first()\n        .map(|codec| !codec.is_rtx())\n        .unwrap_or_default())\n}\n\n/// Generate RTP parameters for a specific Consumer.\n///\n/// It reduces encodings to just one and takes into account given RTP capabilities to reduce codecs,\n/// codecs' RTCP feedback and header extensions, and also enables or disabled RTX.\n#[allow(clippy::suspicious_operation_groupings)]\npub(crate) fn get_consumer_rtp_parameters(\n    consumable_rtp_parameters: &RtpParameters,\n    remote_rtp_capabilities: &RtpCapabilities,\n    pipe: bool,\n    enable_rtx: bool,\n) -> Result<RtpParameters, ConsumerRtpParametersError> {\n    let mut consumer_params = RtpParameters {\n        rtcp: consumable_rtp_parameters.rtcp.clone(),\n        msid: consumable_rtp_parameters.msid.clone(),\n        ..RtpParameters::default()\n    };\n\n    for cap_codec in &remote_rtp_capabilities.codecs {\n        validate_rtp_codec_capability(cap_codec)\n            .map_err(ConsumerRtpParametersError::InvalidCapabilities)?;\n    }\n\n    let mut rtx_supported = false;\n\n    for mut codec in consumable_rtp_parameters.codecs.clone() {\n        if !enable_rtx && codec.is_rtx() {\n            continue;\n        }\n\n        if let Some(matched_cap_codec) = remote_rtp_capabilities\n            .codecs\n            .iter()\n            .find(|cap_codec| match_codecs((*cap_codec).into(), (&codec).into(), true).is_ok())\n        {\n            *codec.rtcp_feedback_mut() = matched_cap_codec\n                .rtcp_feedback()\n                .iter()\n                .filter(|&&fb| enable_rtx || fb != RtcpFeedback::Nack)\n                .copied()\n                .collect();\n\n            consumer_params.codecs.push(codec);\n        }\n    }\n    // Must sanitize the list of matched codecs by removing useless RTX codecs.\n    let mut remove_codecs = Vec::new();\n    for (idx, codec) in consumer_params.codecs.iter().enumerate() {\n        if codec.is_rtx() {\n            // Search for the associated media codec.\n            let associated_media_codec = consumer_params.codecs.iter().find(|media_codec| {\n                match codec.parameters().get(\"apt\") {\n                    Some(RtpCodecParametersParametersValue::Number(apt)) => {\n                        u8::try_from(*apt).is_ok_and(|apt| media_codec.payload_type() == apt)\n                    }\n                    _ => false,\n                }\n            });\n\n            if associated_media_codec.is_some() {\n                rtx_supported = true;\n            } else {\n                remove_codecs.push(idx);\n            }\n        }\n    }\n    for idx in remove_codecs.into_iter().rev() {\n        consumer_params.codecs.remove(idx);\n    }\n\n    // Ensure there is at least one media codec.\n    if consumer_params.codecs.is_empty() || consumer_params.codecs[0].is_rtx() {\n        return Err(ConsumerRtpParametersError::NoCompatibleMediaCodecs);\n    }\n\n    consumer_params.header_extensions = consumable_rtp_parameters\n        .header_extensions\n        .iter()\n        .filter(|ext| {\n            remote_rtp_capabilities\n                .header_extensions\n                .iter()\n                .any(|cap_ext| cap_ext.preferred_id == ext.id && cap_ext.uri == ext.uri)\n        })\n        .cloned()\n        .collect();\n\n    // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise.\n    if consumer_params\n        .header_extensions\n        .iter()\n        .any(|ext| ext.uri == RtpHeaderExtensionUri::TransportWideCcDraft01)\n    {\n        for codec in &mut consumer_params.codecs {\n            codec\n                .rtcp_feedback_mut()\n                .retain(|fb| fb != &RtcpFeedback::GoogRemb);\n        }\n    } else if consumer_params\n        .header_extensions\n        .iter()\n        .any(|ext| ext.uri == RtpHeaderExtensionUri::AbsSendTime)\n    {\n        for codec in &mut consumer_params.codecs {\n            codec\n                .rtcp_feedback_mut()\n                .retain(|fb| fb != &RtcpFeedback::TransportCc);\n        }\n    } else {\n        for codec in &mut consumer_params.codecs {\n            codec\n                .rtcp_feedback_mut()\n                .retain(|fb| !matches!(fb, RtcpFeedback::GoogRemb | RtcpFeedback::TransportCc));\n        }\n    }\n\n    if pipe {\n        for ((encoding, ssrc), rtx_ssrc) in consumable_rtp_parameters\n            .encodings\n            .iter()\n            .zip(generate_ssrc()..)\n            .zip(generate_ssrc()..)\n        {\n            consumer_params.encodings.push(RtpEncodingParameters {\n                ssrc: Some(ssrc),\n                rtx: if rtx_supported {\n                    Some(RtpEncodingParametersRtx { ssrc: rtx_ssrc })\n                } else {\n                    None\n                },\n                ..encoding.clone()\n            });\n        }\n    } else {\n        let mut consumer_encoding = RtpEncodingParameters {\n            ssrc: Some(generate_ssrc()),\n            ..RtpEncodingParameters::default()\n        };\n\n        if rtx_supported {\n            consumer_encoding.rtx = Some(RtpEncodingParametersRtx {\n                ssrc: consumer_encoding.ssrc.unwrap() + 1,\n            });\n        }\n\n        // If any of the consumable_rtp_parameters.encodings has scalability_mode, process it\n        // (assume all encodings have the same value).\n        let mut scalability_mode = consumable_rtp_parameters\n            .encodings\n            .first()\n            .map(|encoding| encoding.scalability_mode.clone())\n            .unwrap_or_default();\n\n        // If there is simulcast, mangle spatial layers in scalabilityMode.\n        if consumable_rtp_parameters.encodings.len() > 1 {\n            scalability_mode = format!(\n                \"L{}T{}\",\n                consumable_rtp_parameters.encodings.len(),\n                scalability_mode.temporal_layers()\n            )\n            .parse()\n            .unwrap();\n        }\n\n        consumer_encoding.scalability_mode = scalability_mode;\n\n        // Use the maximum max_bitrate in any encoding and honor it in the Consumer's encoding.\n        consumer_encoding.max_bitrate = consumable_rtp_parameters\n            .encodings\n            .iter()\n            .map(|encoding| encoding.max_bitrate)\n            .max()\n            .flatten();\n\n        // Set a single encoding for the Consumer.\n        consumer_params.encodings.push(consumer_encoding);\n    }\n\n    Ok(consumer_params)\n}\n\n/// Generate RTP parameters for a pipe Consumer.\n///\n/// It keeps all original consumable encodings and removes support for BWE. If\n/// enableRtx is false, it also removes RTX and NACK support.\npub(crate) fn get_pipe_consumer_rtp_parameters(\n    consumable_rtp_parameters: &RtpParameters,\n    enable_rtx: bool,\n) -> RtpParameters {\n    let mut consumer_params = RtpParameters {\n        mid: None,\n        codecs: vec![],\n        header_extensions: vec![],\n        encodings: vec![],\n        rtcp: consumable_rtp_parameters.rtcp.clone(),\n        msid: consumable_rtp_parameters.msid.clone(),\n    };\n\n    for codec in &consumable_rtp_parameters.codecs {\n        if !enable_rtx && codec.is_rtx() {\n            continue;\n        }\n\n        let mut codec = codec.clone();\n\n        codec.rtcp_feedback_mut().retain(|fb| {\n            matches!(fb, RtcpFeedback::NackPli | RtcpFeedback::CcmFir)\n                || (enable_rtx && fb == &RtcpFeedback::Nack)\n        });\n\n        consumer_params.codecs.push(codec);\n    }\n\n    // Reduce RTP extensions by disabling transport MID and BWE related ones.\n    consumer_params.header_extensions = consumable_rtp_parameters\n        .header_extensions\n        .iter()\n        .filter(|ext| {\n            !matches!(\n                ext.uri,\n                RtpHeaderExtensionUri::Mid\n                    | RtpHeaderExtensionUri::AbsSendTime\n                    | RtpHeaderExtensionUri::TransportWideCcDraft01\n            )\n        })\n        .cloned()\n        .collect();\n\n    // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header\n    // extension.\n    //\n    // We need to add Dependency-Descriptor header extension manually since it's\n    // 'recvonly' so it's not present in received `consumableRtpParameters`.\n    let cache = CACHE.lock();\n    if let Some(dependency_descriptor) =\n        &cache.dependency_descriptor_header_extension_parameters_for_pipe_consumer\n    {\n        consumer_params\n            .header_extensions\n            .push(dependency_descriptor.clone());\n\n        // Sort header extensions by ID.\n        consumer_params.header_extensions.sort_by_key(|ext| ext.id);\n    }\n\n    for ((encoding, ssrc), rtx_ssrc) in consumable_rtp_parameters\n        .encodings\n        .iter()\n        .zip(generate_ssrc()..)\n        .zip(generate_ssrc()..)\n    {\n        consumer_params.encodings.push(RtpEncodingParameters {\n            ssrc: Some(ssrc),\n            rtx: if enable_rtx {\n                Some(RtpEncodingParametersRtx { ssrc: rtx_ssrc })\n            } else {\n                None\n            },\n            ..encoding.clone()\n        });\n    }\n\n    consumer_params\n}\n\nstruct CodecToMatch<'a> {\n    channels: Option<NonZeroU8>,\n    clock_rate: NonZeroU32,\n    mime_type: MimeType,\n    parameters: &'a RtpCodecParametersParameters,\n}\n\nimpl<'a> From<&'a RtpCodecCapability> for CodecToMatch<'a> {\n    fn from(rtp_codec_capability: &'a RtpCodecCapability) -> Self {\n        match rtp_codec_capability {\n            RtpCodecCapability::Audio {\n                mime_type,\n                channels,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: Some(*channels),\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Audio(*mime_type),\n                parameters,\n            },\n            RtpCodecCapability::Video {\n                mime_type,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: None,\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Video(*mime_type),\n                parameters,\n            },\n        }\n    }\n}\n\nimpl<'a> From<&'a RtpCodecCapabilityFinalized> for CodecToMatch<'a> {\n    fn from(rtp_codec_capability: &'a RtpCodecCapabilityFinalized) -> Self {\n        match rtp_codec_capability {\n            RtpCodecCapabilityFinalized::Audio {\n                mime_type,\n                channels,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: Some(*channels),\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Audio(*mime_type),\n                parameters,\n            },\n            RtpCodecCapabilityFinalized::Video {\n                mime_type,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: None,\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Video(*mime_type),\n                parameters,\n            },\n        }\n    }\n}\n\nimpl<'a> From<&'a RtpCodecParameters> for CodecToMatch<'a> {\n    fn from(rtp_codec_parameters: &'a RtpCodecParameters) -> Self {\n        match rtp_codec_parameters {\n            RtpCodecParameters::Audio {\n                mime_type,\n                channels,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: Some(*channels),\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Audio(*mime_type),\n                parameters,\n            },\n            RtpCodecParameters::Video {\n                mime_type,\n                clock_rate,\n                parameters,\n                ..\n            } => Self {\n                channels: None,\n                clock_rate: *clock_rate,\n                mime_type: MimeType::Video(*mime_type),\n                parameters,\n            },\n        }\n    }\n}\n\n/// Returns selected `Ok(Some(profile-level-id))` for H264 codec and `Ok(None)` for others\nfn match_codecs(\n    codec_a: CodecToMatch<'_>,\n    codec_b: CodecToMatch<'_>,\n    strict: bool,\n) -> Result<Option<String>, ()> {\n    if codec_a.mime_type != codec_b.mime_type {\n        return Err(());\n    }\n\n    if codec_a.channels != codec_b.channels {\n        return Err(());\n    }\n\n    if codec_a.clock_rate != codec_b.clock_rate {\n        return Err(());\n    }\n    // Per codec special checks.\n    match codec_a.mime_type {\n        MimeType::Audio(MimeTypeAudio::MultiChannelOpus) => {\n            let num_streams_a = codec_a.parameters.get(\"num_streams\");\n            let num_streams_b = codec_b.parameters.get(\"num_streams\");\n\n            if num_streams_a != num_streams_b {\n                return Err(());\n            }\n\n            let coupled_streams_a = codec_a.parameters.get(\"coupled_streams\");\n            let coupled_streams_b = codec_b.parameters.get(\"coupled_streams\");\n\n            if coupled_streams_a != coupled_streams_b {\n                return Err(());\n            }\n        }\n        MimeType::Video(MimeTypeVideo::H264) => {\n            if strict {\n                let packetization_mode_a = codec_a\n                    .parameters\n                    .get(\"packetization-mode\")\n                    .unwrap_or(&RtpCodecParametersParametersValue::Number(0));\n                let packetization_mode_b = codec_b\n                    .parameters\n                    .get(\"packetization-mode\")\n                    .unwrap_or(&RtpCodecParametersParametersValue::Number(0));\n\n                if packetization_mode_a != packetization_mode_b {\n                    return Err(());\n                }\n\n                let profile_level_id_a =\n                    codec_a\n                        .parameters\n                        .get(\"profile-level-id\")\n                        .and_then(|p| match p {\n                            RtpCodecParametersParametersValue::String(s) => Some(s.as_ref()),\n                            RtpCodecParametersParametersValue::Number(_) => None,\n                        });\n                let profile_level_id_b =\n                    codec_b\n                        .parameters\n                        .get(\"profile-level-id\")\n                        .and_then(|p| match p {\n                            RtpCodecParametersParametersValue::String(s) => Some(s.as_ref()),\n                            RtpCodecParametersParametersValue::Number(_) => None,\n                        });\n\n                let (profile_level_id_a, profile_level_id_b) =\n                    match h264_profile_level_id::is_same_profile(\n                        profile_level_id_a,\n                        profile_level_id_b,\n                    ) {\n                        Some((profile_level_id_a, profile_level_id_b)) => {\n                            (profile_level_id_a, profile_level_id_b)\n                        }\n                        None => {\n                            return Err(());\n                        }\n                    };\n\n                let selected_profile_level_id =\n                    h264_profile_level_id::generate_profile_level_id_for_answer(\n                        Some(profile_level_id_a),\n                        codec_a\n                            .parameters\n                            .get(\"level-asymmetry-allowed\")\n                            .map(|p| p == &RtpCodecParametersParametersValue::Number(1))\n                            .unwrap_or_default(),\n                        Some(profile_level_id_b),\n                        codec_b\n                            .parameters\n                            .get(\"level-asymmetry-allowed\")\n                            .map(|p| p == &RtpCodecParametersParametersValue::Number(1))\n                            .unwrap_or_default(),\n                    );\n\n                return match selected_profile_level_id {\n                    Ok(selected_profile_level_id) => {\n                        Ok(Some(selected_profile_level_id.to_string()))\n                    }\n                    Err(_) => Err(()),\n                };\n            }\n        }\n        MimeType::Video(MimeTypeVideo::Vp9) => {\n            // If strict matching check profile-id.\n            if strict {\n                let profile_id_a = codec_a\n                    .parameters\n                    .get(\"profile-id\")\n                    .unwrap_or(&RtpCodecParametersParametersValue::Number(0));\n                let profile_id_b = codec_b\n                    .parameters\n                    .get(\"profile-id\")\n                    .unwrap_or(&RtpCodecParametersParametersValue::Number(0));\n\n                if profile_id_a != profile_id_b {\n                    return Err(());\n                }\n            }\n        }\n\n        _ => {}\n    }\n\n    Ok(None)\n}\n"
  },
  {
    "path": "rust/src/prelude.rs",
    "content": "//! mediasoup prelude.\n//!\n//! Re-exports commonly used traits and structs from this crate.\n//!\n//! # Examples\n//!\n//! Import the prelude with:\n//!\n//! ```\n//! # #[allow(unused_imports)]\n//! use mediasoup::prelude::*;\n//! ```\npub use crate::worker_manager::WorkerManager;\n\npub use crate::worker::{Worker, WorkerSettings};\n\npub use crate::router::{\n    PipeDataProducerToRouterError, PipeDataProducerToRouterPair, PipeProducerToRouterError,\n    PipeProducerToRouterPair, PipeToRouterOptions, Router, RouterOptions,\n};\n\npub use crate::webrtc_server::{\n    WebRtcServer, WebRtcServerId, WebRtcServerListenInfos, WebRtcServerOptions,\n};\n\npub use crate::direct_transport::{DirectTransport, DirectTransportOptions, WeakDirectTransport};\npub use crate::pipe_transport::{\n    PipeTransport, PipeTransportOptions, PipeTransportRemoteParameters, WeakPipeTransport,\n};\npub use crate::plain_transport::{\n    PlainTransport, PlainTransportOptions, PlainTransportRemoteParameters, WeakPlainTransport,\n};\npub use crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, Transport, TransportGeneric,\n    TransportId,\n};\npub use crate::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n    WebRtcTransportRemoteParameters,\n};\n\npub use crate::active_speaker_observer::{\n    ActiveSpeakerObserver, ActiveSpeakerObserverDominantSpeaker, ActiveSpeakerObserverOptions,\n    WeakActiveSpeakerObserver,\n};\npub use crate::audio_level_observer::{\n    AudioLevelObserver, AudioLevelObserverOptions, AudioLevelObserverVolume, WeakAudioLevelObserver,\n};\npub use crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId};\n\npub use crate::consumer::{Consumer, ConsumerId, ConsumerLayers, ConsumerOptions, WeakConsumer};\npub use crate::data_consumer::{\n    DataConsumer, DataConsumerId, DataConsumerOptions, DirectDataConsumer, RegularDataConsumer,\n    WeakDataConsumer,\n};\npub use crate::data_producer::{\n    DataProducer, DataProducerId, DataProducerOptions, DirectDataProducer, NonClosingDataProducer,\n    RegularDataProducer, WeakDataProducer,\n};\npub use crate::producer::{Producer, ProducerId, ProducerOptions, WeakProducer};\n\npub use mediasoup_types::data_structures::{\n    AppData, DtlsParameters, IceCandidate, IceParameters, ListenInfo, Protocol, WebRtcMessage,\n};\npub use mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities,\n    RtpCapabilitiesFinalized, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters,\n    RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtensionParameters,\n    RtpHeaderExtensionUri, RtpParameters,\n};\npub use mediasoup_types::sctp_parameters::SctpStreamParameters;\npub use mediasoup_types::srtp_parameters::SrtpCryptoSuite;\n"
  },
  {
    "path": "rust/src/router/active_speaker_observer/tests.rs",
    "content": "use crate::active_speaker_observer::ActiveSpeakerObserverOptions;\nuse crate::router::RouterOptions;\nuse crate::rtp_observer::RtpObserver;\nuse crate::worker::{Worker, WorkerSettings};\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse std::env;\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn router_close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::default())\n            .await\n            .expect(\"Failed to create router\");\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = active_speaker_observer.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = active_speaker_observer.on_router_close(Box::new(move || {\n            let _ = router_close_tx.send(());\n        }));\n\n        router.close();\n\n        router_close_rx\n            .await\n            .expect(\"Failed to receive router_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(active_speaker_observer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/active_speaker_observer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::fbs::TryFromFbs;\nuse crate::messages::{\n    RtpObserverAddProducerRequest, RtpObserverCloseRequest, RtpObserverPauseRequest,\n    RtpObserverRemoveProducerRequest, RtpObserverResumeRequest,\n};\nuse crate::producer::{Producer, ProducerId};\nuse crate::router::Router;\nuse crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId};\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::notification;\nuse mediasoup_types::data_structures::AppData;\nuse parking_lot::Mutex;\nuse serde::Deserialize;\nuse std::fmt;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\n/// [`ActiveSpeakerObserver`] options\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct ActiveSpeakerObserverOptions {\n    /// Interval in ms for checking audio volumes.\n    /// Default 300.\n    pub interval: u16,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl Default for ActiveSpeakerObserverOptions {\n    fn default() -> Self {\n        Self {\n            interval: 300,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n/// Represents dominant speaker.\n#[derive(Debug, Clone)]\npub struct ActiveSpeakerObserverDominantSpeaker {\n    /// The audio producer instance.\n    pub producer: Producer,\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    dominant_speaker: Bag<\n        Arc<dyn Fn(&ActiveSpeakerObserverDominantSpeaker) + Send + Sync>,\n        ActiveSpeakerObserverDominantSpeaker,\n    >,\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    add_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    remove_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct DominantSpeakerNotification {\n    producer_id: ProducerId,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    DominantSpeaker(DominantSpeakerNotification),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::ActivespeakerobserverDominantSpeaker => {\n                let Ok(Some(\n                    notification::BodyRef::ActiveSpeakerObserverDominantSpeakerNotification(body),\n                )) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let dominant_speaker_notification = DominantSpeakerNotification {\n                    producer_id: body.producer_id().unwrap().parse().unwrap(),\n                };\n                Ok(Notification::DominantSpeaker(dominant_speaker_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: RtpObserverId,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    paused: AtomicBool,\n    app_data: AppData,\n    // Make sure router is not dropped until this active speaker observer is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to audio speaker observer-specific notifications when observer itself is\n    // dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = RtpObserverCloseRequest {\n                    rtp_observer_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"active speaker observer closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"active speaker observer closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// An active speaker observer monitors the volume of the selected audio producers.\n///\n/// It just handles audio producers (if [`ActiveSpeakerObserver::add_producer()`] is called with a\n/// video producer it will fail).\n///\n/// Audio levels are read from an RTP header extension. No decoding of audio data is done. See\n/// [RFC6464](https://tools.ietf.org/html/rfc6464) for more information.\n#[derive(Clone)]\n#[must_use = \"Active speaker observer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct ActiveSpeakerObserver {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for ActiveSpeakerObserver {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"ActiveSpeakerObserver\")\n            .field(\"id\", &self.inner.id)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl RtpObserver for ActiveSpeakerObserver {\n    fn id(&self) -> RtpObserverId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn paused(&self) -> bool {\n        self.inner.paused.load(Ordering::SeqCst)\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverPauseRequest {})\n            .await?;\n\n        let was_paused = self.inner.paused.swap(true, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner.handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverResumeRequest {})\n            .await?;\n\n        let was_paused = self.inner.paused.swap(false, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner.handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    async fn add_producer(\n        &self,\n        RtpObserverAddProducerOptions { producer_id }: RtpObserverAddProducerOptions,\n    ) -> Result<(), RequestError> {\n        let producer = match self.inner.router.get_producer(&producer_id) {\n            Some(producer) => producer,\n            None => {\n                return Ok(());\n            }\n        };\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverAddProducerRequest { producer_id })\n            .await?;\n\n        self.inner.handlers.add_producer.call_simple(&producer);\n\n        Ok(())\n    }\n\n    async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError> {\n        let producer = match self.inner.router.get_producer(&producer_id) {\n            Some(producer) => producer,\n            None => {\n                return Ok(());\n            }\n        };\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverRemoveProducerRequest { producer_id })\n            .await?;\n\n        self.inner.handlers.remove_producer.call_simple(&producer);\n\n        Ok(())\n    }\n\n    fn on_pause(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId {\n        self.inner.handlers.pause.add(Arc::new(callback))\n    }\n\n    fn on_resume(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId {\n        self.inner.handlers.resume.add(Arc::new(callback))\n    }\n\n    fn on_add_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.add_producer.add(Arc::new(callback))\n    }\n\n    fn on_remove_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.remove_producer.add(Arc::new(callback))\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(Box::new(callback))\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\nimpl ActiveSpeakerObserver {\n    pub(super) fn new(\n        id: RtpObserverId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        router: Router,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let paused = AtomicBool::new(false);\n\n        let subscription_handler = {\n            let router = router.clone();\n            let handlers = Arc::clone(&handlers);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::DominantSpeaker(dominant_speaker) => {\n                            let DominantSpeakerNotification { producer_id } = dominant_speaker;\n                            match router.get_producer(&producer_id) {\n                                Some(producer) => {\n                                    let dominant_speaker =\n                                        ActiveSpeakerObserverDominantSpeaker { producer };\n\n                                    handlers.dominant_speaker.call_simple(&dominant_speaker);\n                                }\n                                None => {\n                                    error!(\n                                        \"Producer for dominant speaker event not found: {}\",\n                                        producer_id\n                                    );\n                                }\n                            };\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            executor,\n            channel,\n            handlers,\n            paused,\n            app_data,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Callback is called at most every interval (see [`ActiveSpeakerObserverOptions`]).\n    pub fn on_dominant_speaker<\n        F: Fn(&ActiveSpeakerObserverDominantSpeaker) + Send + Sync + 'static,\n    >(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.dominant_speaker.add(Arc::new(callback))\n    }\n\n    /// Downgrade `ActiveSpeakerObserver` to [`WeakActiveSpeakerObserver`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakActiveSpeakerObserver {\n        WeakActiveSpeakerObserver {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakActiveSpeakerObserver`] doesn't own active speaker observer instance on mediasoup-worker\n/// and will not prevent one from being destroyed once last instance of regular\n/// [`ActiveSpeakerObserver`] is dropped.\n///\n/// [`WeakActiveSpeakerObserver`] vs [`ActiveSpeakerObserver`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakActiveSpeakerObserver {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakActiveSpeakerObserver {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakActiveSpeakerObserver\").finish()\n    }\n}\n\nimpl WeakActiveSpeakerObserver {\n    /// Attempts to upgrade `WeakActiveSpeakerObserver` to [`ActiveSpeakerObserver`] if last instance of one wasn't\n    /// dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<ActiveSpeakerObserver> {\n        let inner = self.inner.upgrade()?;\n\n        Some(ActiveSpeakerObserver { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/audio_level_observer/tests.rs",
    "content": "use crate::audio_level_observer::AudioLevelObserverOptions;\nuse crate::router::RouterOptions;\nuse crate::rtp_observer::RtpObserver;\nuse crate::worker::{Worker, WorkerSettings};\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse std::env;\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn router_close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::default())\n            .await\n            .expect(\"Failed to create router\");\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_level_observer.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_level_observer.on_router_close(Box::new(move || {\n            let _ = router_close_tx.send(());\n        }));\n\n        router.close();\n\n        router_close_rx\n            .await\n            .expect(\"Failed to receive router_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(audio_level_observer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/audio_level_observer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::fbs::TryFromFbs;\nuse crate::messages::{\n    RtpObserverAddProducerRequest, RtpObserverCloseRequest, RtpObserverPauseRequest,\n    RtpObserverRemoveProducerRequest, RtpObserverResumeRequest,\n};\nuse crate::producer::{Producer, ProducerId};\nuse crate::router::Router;\nuse crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId};\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{audio_level_observer, notification};\nuse mediasoup_types::data_structures::AppData;\nuse parking_lot::Mutex;\nuse serde::Deserialize;\nuse std::fmt;\nuse std::num::NonZeroU16;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\n/// [`AudioLevelObserver`] options\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct AudioLevelObserverOptions {\n    /// Maximum number of entries in the 'volumes' event.\n    /// Default 1.\n    pub max_entries: NonZeroU16,\n    /// Minimum average volume (in dBvo from -127 to 0) for entries in the 'volumes' event.\n    /// Default -80.\n    pub threshold: i8,\n    /// Interval in ms for checking audio volumes.\n    /// Default 1000.\n    pub interval: u16,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl Default for AudioLevelObserverOptions {\n    fn default() -> Self {\n        Self {\n            max_entries: NonZeroU16::new(1).unwrap(),\n            threshold: -80,\n            interval: 1000,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n/// Represents volume of one audio producer.\n#[derive(Debug, Clone)]\npub struct AudioLevelObserverVolume {\n    /// The audio producer instance.\n    pub producer: Producer,\n    /// The average volume (in dBvo from -127 to 0) of the audio producer in the last interval.\n    pub volume: i8,\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    volumes: Bag<Arc<dyn Fn(&[AudioLevelObserverVolume]) + Send + Sync>>,\n    silence: Bag<Arc<dyn Fn() + Send + Sync>>,\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    add_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    remove_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(rename_all = \"camelCase\")]\nstruct VolumeNotification {\n    producer_id: ProducerId,\n    volume: i8,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    Volumes(Vec<VolumeNotification>),\n    Silence,\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::AudiolevelobserverVolumes => {\n                let Ok(Some(notification::BodyRef::AudioLevelObserverVolumesNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let volumes_fbs: Vec<_> = body\n                    .volumes()\n                    .unwrap()\n                    .iter()\n                    .map(|volume| audio_level_observer::Volume::try_from(volume.unwrap()).unwrap())\n                    .collect();\n                let volumes = volumes_fbs\n                    .iter()\n                    .map(|volume| VolumeNotification {\n                        producer_id: volume.producer_id.parse().unwrap(),\n                        volume: volume.volume,\n                    })\n                    .collect();\n\n                Ok(Notification::Volumes(volumes))\n            }\n            notification::Event::AudiolevelobserverSilence => Ok(Notification::Silence),\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: RtpObserverId,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    paused: AtomicBool,\n    app_data: AppData,\n    // Make sure router is not dropped until this audio level observer is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to audio level observer-specific notifications when observer itself is\n    // dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = RtpObserverCloseRequest {\n                    rtp_observer_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"audio level observer closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"audio level observer closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// An audio level observer monitors the volume of the selected audio producers.\n///\n/// It just handles audio producers (if [`AudioLevelObserver::add_producer()`] is called with a\n/// video producer it will fail).\n///\n/// Audio levels are read from an RTP header extension. No decoding of audio data is done. See\n/// [RFC6464](https://tools.ietf.org/html/rfc6464) for more information.\n#[derive(Clone)]\n#[must_use = \"Audio level observer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct AudioLevelObserver {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for AudioLevelObserver {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"AudioLevelObserver\")\n            .field(\"id\", &self.inner.id)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl RtpObserver for AudioLevelObserver {\n    fn id(&self) -> RtpObserverId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn paused(&self) -> bool {\n        self.inner.paused.load(Ordering::SeqCst)\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverPauseRequest {})\n            .await?;\n\n        let was_paused = self.inner.paused.swap(true, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner.handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverResumeRequest {})\n            .await?;\n\n        let was_paused = self.inner.paused.swap(false, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner.handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    async fn add_producer(\n        &self,\n        RtpObserverAddProducerOptions { producer_id }: RtpObserverAddProducerOptions,\n    ) -> Result<(), RequestError> {\n        let producer = match self.inner.router.get_producer(&producer_id) {\n            Some(producer) => producer,\n            None => {\n                return Ok(());\n            }\n        };\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverAddProducerRequest { producer_id })\n            .await?;\n\n        self.inner.handlers.add_producer.call_simple(&producer);\n\n        Ok(())\n    }\n\n    async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError> {\n        let producer = match self.inner.router.get_producer(&producer_id) {\n            Some(producer) => producer,\n            None => {\n                return Ok(());\n            }\n        };\n        self.inner\n            .channel\n            .request(self.id(), RtpObserverRemoveProducerRequest { producer_id })\n            .await?;\n\n        self.inner.handlers.remove_producer.call_simple(&producer);\n\n        Ok(())\n    }\n\n    fn on_pause(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId {\n        self.inner.handlers.pause.add(Arc::new(callback))\n    }\n\n    fn on_resume(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId {\n        self.inner.handlers.resume.add(Arc::new(callback))\n    }\n\n    fn on_add_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.add_producer.add(Arc::new(callback))\n    }\n\n    fn on_remove_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.remove_producer.add(Arc::new(callback))\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(Box::new(callback))\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\nimpl AudioLevelObserver {\n    pub(super) fn new(\n        id: RtpObserverId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        router: Router,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let paused = AtomicBool::new(false);\n\n        let subscription_handler = {\n            let router = router.clone();\n            let handlers = Arc::clone(&handlers);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::Volumes(volumes) => {\n                            let volumes = volumes\n                                .iter()\n                                .filter_map(|notification| {\n                                    let VolumeNotification {\n                                        producer_id,\n                                        volume,\n                                    } = notification;\n                                    router.get_producer(producer_id).map(|producer| {\n                                        AudioLevelObserverVolume {\n                                            producer,\n                                            volume: *volume,\n                                        }\n                                    })\n                                })\n                                .collect::<Vec<_>>();\n\n                            handlers.volumes.call(|callback| {\n                                callback(&volumes);\n                            });\n                        }\n                        Notification::Silence => {\n                            handlers.silence.call_simple();\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            executor,\n            channel,\n            handlers,\n            paused,\n            app_data,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Callback is called at most every interval (see [`AudioLevelObserverOptions`]).\n    ///\n    /// Audio volumes entries ordered by volume (louder ones go first).\n    pub fn on_volumes<F: Fn(&[AudioLevelObserverVolume]) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.volumes.add(Arc::new(callback))\n    }\n\n    /// Callback is called when no one of the producers in this RTP observer is generating audio with a volume\n    /// beyond the given threshold.\n    pub fn on_silence<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.silence.add(Arc::new(callback))\n    }\n\n    /// Downgrade `AudioLevelObserver` to [`WeakAudioLevelObserver`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakAudioLevelObserver {\n        WeakAudioLevelObserver {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakAudioLevelObserver`] doesn't own audio level observer instance on mediasoup-worker and\n/// will not prevent one from being destroyed once last instance of regular [`AudioLevelObserver`]\n/// is dropped.\n///\n/// [`WeakAudioLevelObserver`] vs [`AudioLevelObserver`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakAudioLevelObserver {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakAudioLevelObserver {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakAudioLevelObserver\").finish()\n    }\n}\n\nimpl WeakAudioLevelObserver {\n    /// Attempts to upgrade `WeakAudioLevelObserver` to [`AudioLevelObserver`] if last instance of one wasn't\n    /// dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<AudioLevelObserver> {\n        let inner = self.inner.upgrade()?;\n\n        Some(AudioLevelObserver { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/consumer/tests.rs",
    "content": "use crate::consumer::ConsumerOptions;\nuse crate::producer::ProducerOptions;\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, RtpCapabilities, RtpCodecCapability, RtpCodecParameters,\n    RtpCodecParametersParameters, RtpParameters,\n};\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::Opus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(2).unwrap(),\n        parameters: RtpCodecParametersParameters::default(),\n        rtcp_feedback: vec![],\n    }]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 111,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            }],\n            ..RtpParameters::default()\n        },\n    )\n}\n\nfn consumer_device_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: Some(100),\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        }],\n        ..RtpCapabilities::default()\n    }\n}\n\nasync fn init() -> (Router, WebRtcTransport, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport_1 = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (router, transport_1, transport_2)\n}\n\n#[test]\nfn producer_close_event() {\n    future::block_on(async move {\n        let (_router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_consumer = transport_2\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_consumer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut producer_close_tx, producer_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_consumer.on_producer_close(move || {\n            let _ = producer_close_tx.send(());\n        });\n\n        drop(audio_producer);\n\n        producer_close_rx\n            .await\n            .expect(\"Failed to receive producer_close event\");\n\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(audio_consumer.closed());\n    });\n}\n\n#[test]\nfn transport_close_event() {\n    future::block_on(async move {\n        let (router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_consumer = transport_2\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_consumer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_consumer.on_transport_close(move || {\n            let _ = transport_close_tx.send(());\n        });\n\n        router.close();\n\n        transport_close_rx\n            .await\n            .expect(\"Failed to receive transport_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(audio_consumer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/consumer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::fbs::{FromFbs, ToFbs, TryFromFbs};\nuse crate::messages::{\n    ConsumerCloseRequest, ConsumerDumpRequest, ConsumerEnableTraceEventRequest,\n    ConsumerGetStatsRequest, ConsumerPauseRequest, ConsumerRequestKeyFrameRequest,\n    ConsumerResumeRequest, ConsumerSetPreferredLayersRequest, ConsumerSetPriorityRequest,\n};\nuse crate::producer::{Producer, ProducerId, ProducerStat, ProducerType, WeakProducer};\nuse crate::transport::Transport;\nuse crate::uuid_based_wrapper_type;\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{\n    consumer, notification, response, rtp_parameters, rtp_stream, rtx_stream,\n};\nuse mediasoup_types::data_structures::{\n    AppData, RtpPacketTraceInfo, SsrcTraceInfo, TraceEventDirection,\n};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeType, RtpCapabilities, RtpEncodingParameters, RtpParameters,\n};\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt;\nuse std::fmt::Debug;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\nuuid_based_wrapper_type!(\n    /// [`Consumer`] identifier.\n    ConsumerId\n);\n\n/// Spatial/temporal layers of the consumer.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ConsumerLayers {\n    /// The spatial layer index (from 0 to N).\n    pub spatial_layer: u8,\n    /// The temporal layer index (from 0 to N).\n    pub temporal_layer: Option<u8>,\n}\n\nimpl ToFbs for ConsumerLayers {\n    type FbsType = consumer::ConsumerLayers;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        consumer::ConsumerLayers {\n            spatial_layer: self.spatial_layer,\n            temporal_layer: self.temporal_layer,\n        }\n    }\n}\n\nimpl FromFbs for ConsumerLayers {\n    type FbsType = consumer::ConsumerLayers;\n\n    fn from_fbs(consumer_layers: &Self::FbsType) -> Self {\n        Self {\n            spatial_layer: consumer_layers.spatial_layer,\n            temporal_layer: consumer_layers.temporal_layer,\n        }\n    }\n}\n\n/// Score of consumer and corresponding producer.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ConsumerScore {\n    /// Score of the RTP stream in the consumer (from 0 to 10) representing its transmission\n    /// quality.\n    pub score: u8,\n    /// Score of the currently selected RTP stream in the associated producer (from 0 to 10)\n    /// representing its transmission quality.\n    pub producer_score: u8,\n    /// The scores of all RTP streams in the producer ordered by encoding (just useful when the\n    /// producer uses simulcast).\n    pub producer_scores: Vec<u8>,\n}\n\nimpl FromFbs for ConsumerScore {\n    type FbsType = consumer::ConsumerScore;\n\n    fn from_fbs(consumer_score: &Self::FbsType) -> Self {\n        Self {\n            score: consumer_score.score,\n            producer_score: consumer_score.producer_score,\n            producer_scores: consumer_score.producer_scores.clone().into_iter().collect(),\n        }\n    }\n}\n\n/// [`Consumer`] options.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct ConsumerOptions {\n    /// The id of the Producer to consume.\n    pub producer_id: ProducerId,\n    /// RTP capabilities of the consuming endpoint.\n    pub rtp_capabilities: RtpCapabilities,\n    /// Whether the Consumer must start in paused mode. Default false.\n    ///\n    /// When creating a video Consumer, it's recommended to set paused to true, then transmit the\n    /// Consumer parameters to the consuming endpoint and, once the consuming endpoint has created\n    /// its local side Consumer, unpause the server side Consumer using the resume() method. This is\n    /// an optimization to make it possible for the consuming endpoint to render the video as far as\n    /// possible. If the server side Consumer was created with paused: false, mediasoup will\n    /// immediately request a key frame to the remote Producer and such a key frame may reach the\n    /// consuming endpoint even before it's ready to consume it, generating “black” video until the\n    /// device requests a keyframe by itself.\n    pub paused: bool,\n    /// The MID for the Consumer. If not specified, a sequentially growing number will be assigned.\n    pub mid: Option<String>,\n    /// Preferred spatial and temporal layer for simulcast or SVC media sources.\n    /// If `None`, the highest ones are selected.\n    pub preferred_layers: Option<ConsumerLayers>,\n    /// Whether this Consumer should enable RTP retransmissions, storing sent RTP and processing the\n    /// incoming RTCP NACK from the remote Consumer. If not set it's true by default for video codecs\n    /// and false for audio codecs. If set to true, NACK will be enabled if both endpoints (mediasoup\n    /// and the remote Consumer) support NACK for this codec. When it comes to audio codecs, just\n    ///  OPUS supports NACK.\n    pub enable_rtx: Option<bool>,\n    /// Whether this Consumer should ignore DTX packets (only valid for Opus codec).\n    /// If set, DTX packets are not forwarded to the remote Consumer.\n    pub ignore_dtx: bool,\n    /// Whether this Consumer should consume all RTP streams generated by the Producer.\n    pub pipe: bool,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl ConsumerOptions {\n    /// Create consumer options with given producer ID and RTP capabilities.\n    #[must_use]\n    pub fn new(producer_id: ProducerId, rtp_capabilities: RtpCapabilities) -> Self {\n        Self {\n            producer_id,\n            rtp_capabilities,\n            paused: false,\n            preferred_layers: None,\n            ignore_dtx: false,\n            enable_rtx: None,\n            pipe: false,\n            mid: None,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtpStreamParams {\n    pub clock_rate: u32,\n    pub cname: String,\n    pub encoding_idx: u32,\n    pub mime_type: MimeType,\n    pub payload_type: u8,\n    pub spatial_layers: u8,\n    pub ssrc: u32,\n    pub temporal_layers: u8,\n    pub use_dtx: bool,\n    pub use_in_band_fec: bool,\n    pub use_nack: bool,\n    pub use_pli: bool,\n    pub rid: Option<String>,\n    pub rtx_ssrc: Option<u32>,\n    pub rtx_payload_type: Option<u8>,\n}\n\nimpl<'a> TryFromFbs<'a> for RtpStreamParams {\n    type FbsType = rtp_stream::ParamsRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(params: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            clock_rate: params.clock_rate()?,\n            cname: params.cname()?.to_string(),\n            encoding_idx: params.encoding_idx()?,\n            mime_type: params.mime_type()?.parse()?,\n            payload_type: params.payload_type()?,\n            spatial_layers: params.spatial_layers()?,\n            ssrc: params.ssrc()?,\n            temporal_layers: params.temporal_layers()?,\n            use_dtx: params.use_dtx()?,\n            use_in_band_fec: params.use_in_band_fec()?,\n            use_nack: params.use_nack()?,\n            use_pli: params.use_pli()?,\n            rid: params.rid()?.map(|rid| rid.to_string()),\n            rtx_ssrc: params.rtx_ssrc()?,\n            rtx_payload_type: params.rtx_payload_type()?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtxStreamParams {\n    pub clock_rate: u32,\n    pub cname: String,\n    pub mime_type: MimeType,\n    pub payload_type: u8,\n    pub ssrc: u32,\n    pub rrid: Option<String>,\n}\n\nimpl<'a> TryFromFbs<'a> for RtxStreamParams {\n    type FbsType = rtx_stream::ParamsRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(params: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            clock_rate: params.clock_rate()?,\n            cname: params.cname()?.to_string(),\n            mime_type: params.mime_type()?.parse()?,\n            payload_type: params.payload_type()?,\n            ssrc: params.ssrc()?,\n            rrid: params.rrid()?.map(|rrid| rrid.to_string()),\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtpStream {\n    pub params: RtpStreamParams,\n    pub score: u8,\n}\n\nimpl<'a> TryFromFbs<'a> for RtpStream {\n    type FbsType = rtp_stream::DumpRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            params: RtpStreamParams::try_from_fbs(dump.params()?)?,\n            score: dump.score()?,\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtpRtxParameters {\n    pub ssrc: Option<u32>,\n}\n\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct ConsumerDump {\n    pub id: ConsumerId,\n    pub kind: MediaKind,\n    pub paused: bool,\n    pub priority: u8,\n    pub producer_id: ProducerId,\n    pub producer_paused: bool,\n    pub rtp_parameters: RtpParameters,\n    pub supported_codec_payload_types: Vec<u8>,\n    pub trace_event_types: Vec<ConsumerTraceEventType>,\n    pub r#type: ConsumerType,\n    pub consumable_rtp_encodings: Vec<RtpEncodingParameters>,\n    pub rtp_streams: Vec<RtpStream>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub preferred_spatial_layer: Option<i16>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub target_spatial_layer: Option<i16>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub current_spatial_layer: Option<i16>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub preferred_temporal_layer: Option<i16>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub target_temporal_layer: Option<i16>,\n    /// Essentially `Option<u8>` or `Option<-1>`\n    pub current_temporal_layer: Option<i16>,\n}\n\nimpl<'a> TryFromFbs<'a> for ConsumerDump {\n    type FbsType = consumer::DumpResponseRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        let dump = dump.data();\n\n        Ok(Self {\n            id: dump?.base()?.id()?.parse()?,\n            kind: MediaKind::from_fbs(&dump?.base()?.kind()?),\n            paused: dump?.base()?.paused()?,\n            priority: dump?.base()?.priority()?,\n            producer_id: dump?.base()?.producer_id()?.parse()?,\n            producer_paused: dump?.base()?.producer_paused()?,\n            rtp_parameters: RtpParameters::try_from_fbs(dump?.base()?.rtp_parameters()?)?,\n            supported_codec_payload_types: Vec::from(\n                dump?.base()?.supported_codec_payload_types()?,\n            ),\n            trace_event_types: dump?\n                .base()?\n                .trace_event_types()?\n                .iter()\n                .map(|trace_event_type| Ok(ConsumerTraceEventType::from_fbs(&trace_event_type?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            r#type: ConsumerType::from_fbs(&dump?.base()?.type_()?),\n            consumable_rtp_encodings: dump?\n                .base()?\n                .consumable_rtp_encodings()?\n                .iter()\n                .map(|encoding_parameters| {\n                    RtpEncodingParameters::try_from_fbs(encoding_parameters?)\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            rtp_streams: dump?\n                .rtp_streams()?\n                .iter()\n                .map(|stream| RtpStream::try_from_fbs(stream?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            preferred_spatial_layer: dump?.preferred_spatial_layer()?,\n            target_spatial_layer: dump?.target_spatial_layer()?,\n            current_spatial_layer: dump?.current_spatial_layer()?,\n            preferred_temporal_layer: dump?.preferred_temporal_layer()?,\n            target_temporal_layer: dump?.target_temporal_layer()?,\n            current_temporal_layer: dump?.current_temporal_layer()?,\n        })\n    }\n}\n\n/// Consumer type.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum ConsumerType {\n    /// A single RTP stream is sent with no spatial/temporal layers.\n    Simple,\n    /// Two or more RTP streams are sent, each of them with one or more temporal layers.\n    Simulcast,\n    /// A single RTP stream is sent with spatial/temporal layers.\n    Svc,\n    /// Special type for consumers created on a\n    /// [`PipeTransport`](crate::pipe_transport::PipeTransport).\n    Pipe,\n}\n\nimpl From<ProducerType> for ConsumerType {\n    fn from(producer_type: ProducerType) -> Self {\n        match producer_type {\n            ProducerType::Simple => ConsumerType::Simple,\n            ProducerType::Simulcast => ConsumerType::Simulcast,\n            ProducerType::Svc => ConsumerType::Svc,\n        }\n    }\n}\n\nimpl ToFbs for ConsumerType {\n    type FbsType = rtp_parameters::Type;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            ConsumerType::Simple => rtp_parameters::Type::Simple,\n            ConsumerType::Simulcast => rtp_parameters::Type::Simulcast,\n            ConsumerType::Svc => rtp_parameters::Type::Svc,\n            ConsumerType::Pipe => rtp_parameters::Type::Pipe,\n        }\n    }\n}\n\nimpl FromFbs for ConsumerType {\n    type FbsType = rtp_parameters::Type;\n\n    fn from_fbs(r#type: &Self::FbsType) -> Self {\n        match r#type {\n            rtp_parameters::Type::Simple => ConsumerType::Simple,\n            rtp_parameters::Type::Simulcast => ConsumerType::Simulcast,\n            rtp_parameters::Type::Svc => ConsumerType::Svc,\n            rtp_parameters::Type::Pipe => ConsumerType::Pipe,\n        }\n    }\n}\n\n/// RTC statistics of the consumer alone.\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[allow(missing_docs)]\n#[non_exhaustive]\npub struct ConsumerStat {\n    // Common to all RtpStreams.\n    // `type` field is present in worker, but ignored here\n    pub timestamp: u64,\n    pub ssrc: u32,\n    pub rtx_ssrc: Option<u32>,\n    pub kind: MediaKind,\n    pub mime_type: MimeType,\n    pub packets_lost: i32,\n    pub fraction_lost: u8,\n    pub jitter: u32,\n    pub packets_discarded: u64,\n    pub packets_retransmitted: u64,\n    pub packets_repaired: u64,\n    pub nack_count: u64,\n    pub nack_packet_count: u64,\n    pub pli_count: u64,\n    pub fir_count: u64,\n    pub packet_count: u64,\n    pub byte_count: u64,\n    pub bitrate: u32,\n    pub round_trip_time: Option<f32>,\n    pub rtx_packets_discarded: Option<u64>,\n    pub score: u8,\n}\n\nimpl FromFbs for ConsumerStat {\n    type FbsType = rtp_stream::Stats;\n\n    fn from_fbs(stats: &Self::FbsType) -> Self {\n        let rtp_stream::StatsData::SendStats(ref stats) = stats.data else {\n            panic!(\"Wrong message from worker: {stats:?}\");\n        };\n\n        let rtp_stream::StatsData::BaseStats(ref base) = stats.base.data else {\n            panic!(\"Wrong message from worker: {stats:?}\");\n        };\n\n        Self {\n            timestamp: base.timestamp,\n            ssrc: base.ssrc,\n            rtx_ssrc: base.rtx_ssrc,\n            kind: MediaKind::from_fbs(&base.kind),\n            mime_type: base.mime_type.to_string().parse().unwrap(),\n            packets_lost: base.packets_lost,\n            fraction_lost: base.fraction_lost,\n            jitter: base.jitter,\n            packets_discarded: base.packets_discarded,\n            packets_retransmitted: base.packets_retransmitted,\n            packets_repaired: base.packets_repaired,\n            nack_count: base.nack_count,\n            nack_packet_count: base.nack_packet_count,\n            pli_count: base.pli_count,\n            fir_count: base.fir_count,\n            packet_count: stats.packet_count,\n            byte_count: stats.byte_count,\n            bitrate: stats.bitrate,\n            round_trip_time: Some(base.round_trip_time),\n            rtx_packets_discarded: Some(base.rtx_packets_discarded),\n            score: base.score,\n        }\n    }\n}\n\n/// RTC statistics of the consumer, may or may not include producer statistics.\n#[allow(clippy::large_enum_variant)]\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum ConsumerStats {\n    /// RTC statistics without producer\n    JustConsumer((ConsumerStat,)),\n    /// RTC statistics with producer\n    WithProducer((ConsumerStat, ProducerStat)),\n    /// RTC statistics with multiple consumers (pipe).\n    MultipleConsumers(Vec<ConsumerStat>),\n}\n\nimpl ConsumerStats {\n    /// RTC statistics of the consumer\n    pub fn consumer_stats(&self) -> &ConsumerStat {\n        match self {\n            ConsumerStats::JustConsumer((consumer_stat,)) => consumer_stat,\n            ConsumerStats::WithProducer((consumer_stat, _)) => consumer_stat,\n            ConsumerStats::MultipleConsumers(consumer_stats) => &consumer_stats[0],\n        }\n    }\n}\n\n/// 'trace' event data.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"type\", rename_all = \"lowercase\")]\npub enum ConsumerTraceEventData {\n    /// RTP packet.\n    Rtp {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// RTP packet info.\n        info: RtpPacketTraceInfo,\n    },\n    /// RTP video keyframe packet.\n    KeyFrame {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// RTP packet info.\n        info: RtpPacketTraceInfo,\n    },\n    /// RTCP NACK packet.\n    Nack {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n    },\n    /// RTCP PLI packet.\n    Pli {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// SSRC info.\n        info: SsrcTraceInfo,\n    },\n    /// RTCP FIR packet.\n    Fir {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// SSRC info.\n        info: SsrcTraceInfo,\n    },\n}\n\nimpl FromFbs for ConsumerTraceEventData {\n    type FbsType = consumer::TraceNotification;\n\n    fn from_fbs(data: &Self::FbsType) -> Self {\n        match data.type_ {\n            consumer::TraceEventType::Rtp => ConsumerTraceEventData::Rtp {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(consumer::TraceInfo::RtpTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    RtpPacketTraceInfo {\n                        is_rtx: info.is_rtx,\n                        ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref())\n                    }\n                },\n            },\n            consumer::TraceEventType::Keyframe => ConsumerTraceEventData::KeyFrame {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(consumer::TraceInfo::KeyFrameTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    RtpPacketTraceInfo {\n                        is_rtx: info.is_rtx,\n                        ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref())\n                    }\n                },\n            },\n            consumer::TraceEventType::Nack => ConsumerTraceEventData::Nack {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n            },\n            consumer::TraceEventType::Pli => ConsumerTraceEventData::Pli {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(consumer::TraceInfo::PliTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    SsrcTraceInfo { ssrc: info.ssrc }\n                },\n            },\n            consumer::TraceEventType::Fir => ConsumerTraceEventData::Fir {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(consumer::TraceInfo::FirTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    SsrcTraceInfo { ssrc: info.ssrc }\n                },\n            },\n        }\n    }\n}\n\n/// Types of consumer trace events.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum ConsumerTraceEventType {\n    /// RTP packet.\n    Rtp,\n    /// RTP video keyframe packet.\n    KeyFrame,\n    /// RTCP NACK packet.\n    Nack,\n    /// RTCP PLI packet.\n    Pli,\n    /// RTCP FIR packet.\n    Fir,\n}\n\nimpl ToFbs for ConsumerTraceEventType {\n    type FbsType = consumer::TraceEventType;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            ConsumerTraceEventType::Rtp => consumer::TraceEventType::Rtp,\n            ConsumerTraceEventType::KeyFrame => consumer::TraceEventType::Keyframe,\n            ConsumerTraceEventType::Nack => consumer::TraceEventType::Nack,\n            ConsumerTraceEventType::Pli => consumer::TraceEventType::Pli,\n            ConsumerTraceEventType::Fir => consumer::TraceEventType::Fir,\n        }\n    }\n}\n\nimpl FromFbs for ConsumerTraceEventType {\n    type FbsType = consumer::TraceEventType;\n\n    fn from_fbs(event_type: &Self::FbsType) -> Self {\n        match event_type {\n            consumer::TraceEventType::Rtp => ConsumerTraceEventType::Rtp,\n            consumer::TraceEventType::Keyframe => ConsumerTraceEventType::KeyFrame,\n            consumer::TraceEventType::Nack => ConsumerTraceEventType::Nack,\n            consumer::TraceEventType::Pli => ConsumerTraceEventType::Pli,\n            consumer::TraceEventType::Fir => ConsumerTraceEventType::Fir,\n        }\n    }\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    ProducerClose,\n    ProducerPause,\n    ProducerResume,\n    Rtp(Vec<u8>),\n    Score(ConsumerScore),\n    LayersChange(Option<ConsumerLayers>),\n    Trace(ConsumerTraceEventData),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::ConsumerProducerClose => Ok(Notification::ProducerClose),\n            notification::Event::ConsumerProducerPause => Ok(Notification::ProducerPause),\n            notification::Event::ConsumerProducerResume => Ok(Notification::ProducerResume),\n            notification::Event::ConsumerRtp => {\n                let Ok(Some(notification::BodyRef::ConsumerRtpNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let rtp_notification_fbs = consumer::RtpNotification::try_from(body).unwrap();\n\n                Ok(Notification::Rtp(rtp_notification_fbs.data))\n            }\n            notification::Event::ConsumerScore => {\n                let Ok(Some(notification::BodyRef::ConsumerScoreNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let score_fbs = consumer::ConsumerScore::try_from(body.score().unwrap()).unwrap();\n                let score = ConsumerScore::from_fbs(&score_fbs);\n\n                Ok(Notification::Score(score))\n            }\n            notification::Event::ConsumerLayersChange => {\n                let Ok(Some(notification::BodyRef::ConsumerLayersChangeNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                match body.layers().unwrap() {\n                    Some(layers) => {\n                        let layers_fbs = consumer::ConsumerLayers::try_from(layers).unwrap();\n                        let layers = ConsumerLayers::from_fbs(&layers_fbs);\n\n                        Ok(Notification::LayersChange(Some(layers)))\n                    }\n                    None => Ok(Notification::LayersChange(None)),\n                }\n            }\n            notification::Event::ConsumerTrace => {\n                let Ok(Some(notification::BodyRef::ConsumerTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = consumer::TraceNotification::try_from(body).unwrap();\n                let trace_notification = ConsumerTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    rtp: Bag<Arc<dyn Fn(&[u8]) + Send + Sync>>,\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    producer_pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    producer_resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    score: Bag<Arc<dyn Fn(&ConsumerScore) + Send + Sync>, ConsumerScore>,\n    #[allow(clippy::type_complexity)]\n    layers_change: Bag<Arc<dyn Fn(&Option<ConsumerLayers>) + Send + Sync>, Option<ConsumerLayers>>,\n    trace: Bag<Arc<dyn Fn(&ConsumerTraceEventData) + Send + Sync>, ConsumerTraceEventData>,\n    producer_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    transport_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: ConsumerId,\n    producer_id: ProducerId,\n    kind: MediaKind,\n    r#type: ConsumerType,\n    rtp_parameters: RtpParameters,\n    paused: Arc<Mutex<bool>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    producer_paused: Arc<Mutex<bool>>,\n    priority: Mutex<u8>,\n    score: Arc<Mutex<ConsumerScore>>,\n    preferred_layers: Mutex<Option<ConsumerLayers>>,\n    current_layers: Arc<Mutex<Option<ConsumerLayers>>>,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    transport: Arc<dyn Transport>,\n    weak_producer: WeakProducer,\n    closed: Arc<AtomicBool>,\n    // Drop subscription to consumer-specific notifications when consumer itself is dropped\n    _subscription_handlers: Mutex<Vec<Option<SubscriptionHandler>>>,\n    _on_transport_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let transport_id = self.transport.id();\n                let request = ConsumerCloseRequest {\n                    consumer_id: self.id,\n                };\n                let weak_producer = self.weak_producer.clone();\n\n                self.executor\n                    .spawn(async move {\n                        if weak_producer.upgrade().is_some() {\n                            match channel.request(transport_id, request).await {\n                                Err(RequestError::ChannelClosed) => {\n                                    debug!(\n                                        \"consumer closing failed on drop: Channel already closed\"\n                                    );\n                                }\n                                Err(error) => {\n                                    error!(\"consumer closing failed on drop: {}\", error);\n                                }\n                                Ok(_) => {}\n                            }\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A consumer represents an audio or video source being forwarded from a mediasoup router to an\n/// endpoint. It's created on top of a transport that defines how the media packets are carried.\n#[derive(Clone)]\n#[must_use = \"Consumer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct Consumer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for Consumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Consumer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"producer_id\", &self.inner.producer_id)\n            .field(\"kind\", &self.inner.kind)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"rtp_parameters\", &self.inner.rtp_parameters)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"producer_paused\", &self.inner.producer_paused)\n            .field(\"priority\", &self.inner.priority)\n            .field(\"score\", &self.inner.score)\n            .field(\"preferred_layers\", &self.inner.preferred_layers)\n            .field(\"current_layers\", &self.inner.current_layers)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl Consumer {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn new(\n        id: ConsumerId,\n        producer: Producer,\n        r#type: ConsumerType,\n        rtp_parameters: RtpParameters,\n        paused: bool,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        producer_paused: bool,\n        score: ConsumerScore,\n        preferred_layers: Option<ConsumerLayers>,\n        app_data: AppData,\n        transport: Arc<dyn Transport>,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let score = Arc::new(Mutex::new(score));\n        let closed = Arc::new(AtomicBool::new(false));\n        #[allow(clippy::mutex_atomic)]\n        let paused = Arc::new(Mutex::new(paused));\n        #[allow(clippy::mutex_atomic)]\n        let producer_paused = Arc::new(Mutex::new(producer_paused));\n        let current_layers = Arc::<Mutex<Option<ConsumerLayers>>>::default();\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let closed = Arc::clone(&closed);\n            let paused = Arc::clone(&paused);\n            let producer_paused = Arc::clone(&producer_paused);\n            let score = Arc::clone(&score);\n            let current_layers = Arc::clone(&current_layers);\n            let inner_weak = Arc::clone(&inner_weak);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::ProducerClose => {\n                            if !closed.load(Ordering::SeqCst) {\n                                handlers.producer_close.call_simple();\n\n                                let maybe_inner =\n                                    inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                                if let Some(inner) = maybe_inner {\n                                    inner\n                                        .executor\n                                        .clone()\n                                        .spawn(async move {\n                                            // Potential drop needs to happen from a different\n                                            // thread to prevent potential deadlock\n                                            inner.close(false);\n                                        })\n                                        .detach();\n                                }\n                            }\n                        }\n                        Notification::ProducerPause => {\n                            let paused = {\n                                let paused = paused.lock();\n                                let mut producer_paused = producer_paused.lock();\n                                *producer_paused = true;\n\n                                *paused\n                            };\n\n                            handlers.producer_pause.call_simple();\n\n                            if !paused {\n                                handlers.pause.call_simple();\n                            }\n                        }\n                        Notification::ProducerResume => {\n                            let paused = {\n                                let paused = paused.lock();\n                                let mut producer_paused = producer_paused.lock();\n                                *producer_paused = false;\n\n                                *paused\n                            };\n\n                            handlers.producer_resume.call_simple();\n\n                            if !paused {\n                                handlers.resume.call_simple();\n                            }\n                        }\n                        Notification::Rtp(data) => {\n                            handlers.rtp.call(|callback| {\n                                callback(&data);\n                            });\n                        }\n                        Notification::Score(consumer_score) => {\n                            *score.lock() = consumer_score.clone();\n                            handlers.score.call_simple(&consumer_score);\n                        }\n                        Notification::LayersChange(consumer_layers) => {\n                            *current_layers.lock() = consumer_layers;\n                            handlers.layers_change.call_simple(&consumer_layers);\n                        }\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let on_transport_close_handler = transport.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            Box::new(move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.transport_close.call_simple();\n                    inner.close(false);\n                }\n            })\n        });\n        let inner = Arc::new(Inner {\n            id,\n            producer_id: producer.id(),\n            kind: producer.kind(),\n            r#type,\n            rtp_parameters,\n            paused,\n            producer_paused,\n            priority: Mutex::new(1_u8),\n            score,\n            preferred_layers: Mutex::new(preferred_layers),\n            current_layers,\n            executor,\n            channel,\n            handlers,\n            app_data,\n            transport,\n            weak_producer: producer.downgrade(),\n            closed,\n            _subscription_handlers: Mutex::new(vec![subscription_handler]),\n            _on_transport_close_handler: Mutex::new(on_transport_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Consumer id.\n    #[must_use]\n    pub fn id(&self) -> ConsumerId {\n        self.inner.id\n    }\n\n    /// Associated Producer id.\n    #[must_use]\n    pub fn producer_id(&self) -> ProducerId {\n        self.inner.producer_id\n    }\n\n    /// Transport to which consumer belongs.\n    pub fn transport(&self) -> &Arc<dyn Transport> {\n        &self.inner.transport\n    }\n\n    /// Media kind.\n    #[must_use]\n    pub fn kind(&self) -> MediaKind {\n        self.inner.kind\n    }\n\n    /// Consumer RTP parameters.\n    /// # Notes on usage\n    /// Check the\n    /// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    #[must_use]\n    pub fn rtp_parameters(&self) -> &RtpParameters {\n        &self.inner.rtp_parameters\n    }\n\n    /// Consumer type.\n    #[must_use]\n    pub fn r#type(&self) -> ConsumerType {\n        self.inner.r#type\n    }\n\n    /// Whether the consumer is paused. It does not take into account whether the associated\n    /// producer is paused.\n    #[must_use]\n    pub fn paused(&self) -> bool {\n        *self.inner.paused.lock()\n    }\n\n    /// Whether the associate Producer is paused.\n    #[must_use]\n    pub fn producer_paused(&self) -> bool {\n        *self.inner.producer_paused.lock()\n    }\n\n    /// Consumer priority (see [`Consumer::set_priority`] method).\n    #[must_use]\n    pub fn priority(&self) -> u8 {\n        *self.inner.priority.lock()\n    }\n\n    /// The score of the RTP stream being sent, representing its transmission quality.\n    #[must_use]\n    pub fn score(&self) -> ConsumerScore {\n        self.inner.score.lock().clone()\n    }\n\n    /// Preferred spatial and temporal layers (see [`Consumer::set_preferred_layers`] method). For\n    /// simulcast and SVC consumers, `None` otherwise.\n    #[must_use]\n    pub fn preferred_layers(&self) -> Option<ConsumerLayers> {\n        *self.inner.preferred_layers.lock()\n    }\n\n    /// Currently active spatial and temporal layers (for `Simulcast` and `SVC` consumers only).\n    /// It's `None` if no layers are being sent to the consuming endpoint at this time (or if the\n    /// consumer is consuming from a `Simulcast` or `SVC` producer).\n    #[must_use]\n    pub fn current_layers(&self) -> Option<ConsumerLayers> {\n        *self.inner.current_layers.lock()\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    /// Whether the consumer is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump Consumer.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<ConsumerDump, RequestError> {\n        debug!(\"dump()\");\n\n        self.inner\n            .channel\n            .request(self.id(), ConsumerDumpRequest {})\n            .await\n    }\n\n    /// Returns current RTC statistics of the consumer.\n    ///\n    /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    pub async fn get_stats(&self) -> Result<ConsumerStats, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self\n            .inner\n            .channel\n            .request(self.id(), ConsumerGetStatsRequest {})\n            .await?;\n\n        if let response::Body::ConsumerGetStatsResponse(data) = response {\n            match self.r#type() {\n                ConsumerType::Simple | ConsumerType::Simulcast | ConsumerType::Svc => {\n                    match data.stats.len() {\n                        0 => panic!(\"Empty stats response from worker\"),\n                        1 => {\n                            let consumer_stat = ConsumerStat::from_fbs(&data.stats[0]);\n\n                            Ok(ConsumerStats::JustConsumer((consumer_stat,)))\n                        }\n                        2 => {\n                            let consumer_stat = ConsumerStat::from_fbs(&data.stats[0]);\n                            let producer_stat = ProducerStat::from_fbs(&data.stats[1]);\n\n                            Ok(ConsumerStats::WithProducer((consumer_stat, producer_stat)))\n                        }\n                        _ => panic!(\"More than two stats response from worker\"),\n                    }\n                }\n                ConsumerType::Pipe => {\n                    let mut stats = Vec::<ConsumerStat>::with_capacity(data.stats.len());\n                    for stat in data.stats {\n                        stats.push(ConsumerStat::from_fbs(&stat));\n                    }\n\n                    Ok(ConsumerStats::MultipleConsumers(stats))\n                }\n            }\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Pauses the consumer (no RTP is sent to the consuming endpoint).\n    pub async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner\n            .channel\n            .request(self.id(), ConsumerPauseRequest {})\n            .await?;\n\n        let was_paused = {\n            let mut paused = self.inner.paused.lock();\n            let was_paused = *paused || *self.inner.producer_paused.lock();\n            *paused = true;\n\n            was_paused\n        };\n\n        if !was_paused {\n            self.inner.handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Resumes the consumer (RTP is sent again to the consuming endpoint).\n    pub async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner\n            .channel\n            .request(self.id(), ConsumerResumeRequest {})\n            .await?;\n\n        let was_paused = {\n            let mut paused = self.inner.paused.lock();\n            let was_paused = *paused || *self.inner.producer_paused.lock();\n            *paused = false;\n\n            was_paused\n        };\n\n        if was_paused {\n            self.inner.handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Sets the preferred (highest) spatial and temporal layers to be sent to the consuming\n    /// endpoint. Just valid for `Simulcast` and `SVC` consumers.\n    pub async fn set_preferred_layers(\n        &self,\n        consumer_layers: ConsumerLayers,\n    ) -> Result<(), RequestError> {\n        debug!(\"set_preferred_layers()\");\n\n        let consumer_layers = self\n            .inner\n            .channel\n            .request(\n                self.id(),\n                ConsumerSetPreferredLayersRequest {\n                    data: consumer_layers,\n                },\n            )\n            .await?;\n\n        *self.inner.preferred_layers.lock() = consumer_layers;\n\n        Ok(())\n    }\n\n    /// Sets the priority for this consumer. It affects how the estimated outgoing bitrate in the\n    /// transport (obtained via transport-cc or REMB) is distributed among all video consumers, by\n    /// prioritizing those with higher priority.\n    pub async fn set_priority(&self, priority: u8) -> Result<(), RequestError> {\n        debug!(\"set_preferred_layers()\");\n\n        let result = self\n            .inner\n            .channel\n            .request(self.id(), ConsumerSetPriorityRequest { priority })\n            .await?;\n\n        *self.inner.priority.lock() = result.priority;\n\n        Ok(())\n    }\n\n    /// Unsets the priority for this consumer (it sets it to its default value `1`).\n    pub async fn unset_priority(&self) -> Result<(), RequestError> {\n        debug!(\"unset_priority()\");\n\n        let priority = 1;\n\n        let result = self\n            .inner\n            .channel\n            .request(self.id(), ConsumerSetPriorityRequest { priority })\n            .await?;\n\n        *self.inner.priority.lock() = result.priority;\n\n        Ok(())\n    }\n\n    /// Request a key frame from associated producer. Just valid for video consumers.\n    pub async fn request_key_frame(&self) -> Result<(), RequestError> {\n        debug!(\"request_key_frame()\");\n\n        self.inner\n            .channel\n            .request(self.id(), ConsumerRequestKeyFrameRequest {})\n            .await\n    }\n\n    /// Instructs the consumer to emit \"trace\" events. For monitoring purposes. Use with caution.\n    pub async fn enable_trace_event(\n        &self,\n        types: Vec<ConsumerTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.inner\n            .channel\n            .request(self.id(), ConsumerEnableTraceEventRequest { types })\n            .await\n    }\n\n    /// Callback is called when the consumer receives through its router a RTP packet from the\n    /// associated producer.\n    ///\n    /// # Notes on usage\n    /// Just available in direct transports, this is, those created via\n    /// [`Router::create_direct_transport`](crate::router::Router::create_direct_transport).\n    pub fn on_rtp<F: Fn(&[u8]) + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.rtp.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the consumer or its associated producer is paused and, as result,\n    /// the consumer becomes paused.\n    pub fn on_pause<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.pause.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the consumer or its associated producer is resumed and, as result,\n    /// the consumer is no longer paused.\n    pub fn on_resume<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.resume.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated producer is paused.\n    pub fn on_producer_pause<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.producer_pause.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated producer is resumed.\n    pub fn on_producer_resume<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.producer_resume.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the consumer score changes.\n    pub fn on_score<F: Fn(&ConsumerScore) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.score.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the spatial/temporal layers being sent to the endpoint change. Just\n    /// for `Simulcast` or `SVC` consumers.\n    ///\n    /// # Notes on usage\n    /// This callback is called under various circumstances in `SVC` or `Simulcast` consumers\n    /// (assuming the consumer endpoints supports BWE via REMB or Transport-CC):\n    /// * When the consumer (or its associated producer) is paused.\n    /// * When all the RTP streams of the associated producer become inactive (no RTP received for a\n    ///   while).\n    /// * When the available bitrate of the BWE makes the consumer upgrade or downgrade the spatial\n    ///   and/or temporal layers.\n    /// * When there is no available bitrate for this consumer (even for the lowest layers) so the\n    ///   callback is called with `None` as argument.\n    ///\n    /// The Rust application can detect the latter (consumer deactivated due to not enough\n    /// bandwidth) by checking if both `consumer.paused()` and `consumer.producer_paused()` are\n    /// falsy after the consumer has called this callback with `None` as argument.\n    pub fn on_layers_change<F: Fn(&Option<ConsumerLayers>) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.layers_change.add(Arc::new(callback))\n    }\n\n    /// See [`Consumer::enable_trace_event`] method.\n    pub fn on_trace<F: Fn(&ConsumerTraceEventData) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.trace.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated producer is closed for whatever reason. The consumer\n    /// itself is also closed.\n    pub fn on_producer_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.producer_close.add(Box::new(callback))\n    }\n\n    /// Callback is called when the transport this consumer belongs to is closed for whatever\n    /// reason. The consumer itself is also closed.\n    pub fn on_transport_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.transport_close.add(Box::new(callback))\n    }\n\n    /// Callback is called when the consumer is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if consumer is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    /// Downgrade `Consumer` to [`WeakConsumer`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakConsumer {\n        WeakConsumer {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakConsumer`] doesn't own consumer instance on mediasoup-worker and will not prevent one from\n/// being destroyed once last instance of regular [`Consumer`] is dropped.\n///\n/// [`WeakConsumer`] vs [`Consumer`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakConsumer {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakConsumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakConsumer\").finish()\n    }\n}\n\nimpl WeakConsumer {\n    /// Attempts to upgrade `WeakConsumer` to [`Consumer`] if last instance of one wasn't dropped\n    /// yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<Consumer> {\n        let inner = self.inner.upgrade()?;\n\n        Some(Consumer { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/data_consumer/tests.rs",
    "content": "use crate::data_consumer::DataConsumerOptions;\nuse crate::data_producer::{DataProducer, DataProducerOptions};\nuse crate::plain_transport::PlainTransportOptions;\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions};\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\n\nasync fn init() -> (Router, DataProducer) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport = router\n        .create_webrtc_transport({\n            let mut transport_options =\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }));\n\n            transport_options.enable_sctp = true;\n\n            transport_options\n        })\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let data_producer = transport\n        .produce_data(DataProducerOptions::new_sctp(\n            SctpStreamParameters::new_unordered_with_life_time(12345, 5000),\n        ))\n        .await\n        .expect(\"Failed to create data producer\");\n\n    (router, data_producer)\n}\n\n#[test]\nfn data_producer_close_event() {\n    future::block_on(async move {\n        let (router, data_producer) = init().await;\n\n        let transport2 = router\n            .create_plain_transport({\n                let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                transport_options.enable_sctp = true;\n\n                transport_options\n            })\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let data_consumer = transport2\n            .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time(\n                data_producer.id(),\n                4000,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut data_producer_close_tx, data_producer_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_data_producer_close(move || {\n            let _ = data_producer_close_tx.send(());\n        });\n\n        drop(data_producer);\n\n        data_producer_close_rx\n            .await\n            .expect(\"Failed to receive data_producer_close event\");\n\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(data_consumer.closed());\n    });\n}\n\n#[test]\nfn transport_close_event() {\n    future::block_on(async move {\n        let (router, data_producer) = init().await;\n\n        let transport2 = router\n            .create_plain_transport({\n                let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                transport_options.enable_sctp = true;\n\n                transport_options\n            })\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let data_consumer = transport2\n            .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time(\n                data_producer.id(),\n                4000,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_transport_close(move || {\n            let _ = transport_close_tx.send(());\n        });\n\n        router.close();\n\n        transport_close_rx\n            .await\n            .expect(\"Failed to receive transport_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(data_consumer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/data_consumer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::data_producer::{DataProducer, DataProducerId, WeakDataProducer};\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{\n    DataConsumerAddSubchannelRequest, DataConsumerCloseRequest, DataConsumerDumpRequest,\n    DataConsumerGetBufferedAmountRequest, DataConsumerGetStatsRequest, DataConsumerPauseRequest,\n    DataConsumerRemoveSubchannelRequest, DataConsumerResumeRequest, DataConsumerSendRequest,\n    DataConsumerSetBufferedAmountLowThresholdRequest, DataConsumerSetSubchannelsRequest,\n};\nuse crate::transport::Transport;\nuse crate::uuid_based_wrapper_type;\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{data_consumer, data_producer, notification, response};\nuse mediasoup_types::data_structures::{AppData, WebRtcMessage};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::borrow::Cow;\nuse std::error::Error;\n// TODO.\n// use std::borrow::Cow;\nuse std::fmt;\nuse std::fmt::Debug;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\nuuid_based_wrapper_type!(\n    /// [`DataConsumer`] identifier.\n    DataConsumerId\n);\n\n/// [`DataConsumer`] options.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct DataConsumerOptions {\n    // The id of the [`DataProducer`](crate::data_producer::DataProducer) to consume.\n    pub(super) data_producer_id: DataProducerId,\n    /// Just if consuming over SCTP.\n    /// Whether data messages must be received in order. If true the messages will be sent reliably.\n    /// Defaults to the value in the DataProducer if it has type `Sctp` or to true if it has type\n    /// `Direct`.\n    pub(super) ordered: Option<bool>,\n    /// Just if consuming over SCTP.\n    /// When ordered is false indicates the time (in milliseconds) after which a SCTP packet will\n    /// stop being retransmitted.\n    /// Defaults to the value in the DataProducer if it has type `Sctp` or unset if it has type\n    /// `Direct`.\n    pub(super) max_packet_life_time: Option<u16>,\n    /// Just if consuming over SCTP.\n    /// When ordered is false indicates the maximum number of times a packet will be retransmitted.\n    /// Defaults to the value in the [`DataProducer`](crate::data_producer::DataProducer) if it\n    /// has type `Sctp` or unset if it has type `Direct`.\n    pub(super) max_retransmits: Option<u16>,\n    /// Whether the DataConsumer must start in paused mode. Default false.\n    pub paused: bool,\n    /// Subchannels this DataConsumer initially subscribes to.\n    /// Only used in case this DataConsumer receives messages from a local DataProducer\n    /// that specifies subchannel(s) when calling send().\n    pub subchannels: Option<Vec<u16>>,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl DataConsumerOptions {\n    /// Inherits parameters of corresponding\n    /// [`DataProducer`](crate::data_producer::DataProducer).\n    #[must_use]\n    pub fn new_sctp(data_producer_id: DataProducerId) -> Self {\n        Self {\n            data_producer_id,\n            ordered: None,\n            max_packet_life_time: None,\n            max_retransmits: None,\n            subchannels: None,\n            paused: false,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// For [`DirectTransport`](crate::direct_transport::DirectTransport).\n    #[must_use]\n    pub fn new_direct(data_producer_id: DataProducerId, subchannels: Option<Vec<u16>>) -> Self {\n        Self {\n            data_producer_id,\n            ordered: Some(true),\n            max_packet_life_time: None,\n            max_retransmits: None,\n            paused: false,\n            subchannels,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Messages will be sent reliably in order.\n    #[must_use]\n    pub fn new_sctp_ordered(data_producer_id: DataProducerId) -> Self {\n        Self {\n            data_producer_id,\n            ordered: Some(true),\n            max_packet_life_time: None,\n            max_retransmits: None,\n            paused: false,\n            subchannels: None,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Messages will be sent unreliably with time (in milliseconds) after which a SCTP packet will\n    /// stop being retransmitted.\n    #[must_use]\n    pub fn new_sctp_unordered_with_life_time(\n        data_producer_id: DataProducerId,\n        max_packet_life_time: u16,\n    ) -> Self {\n        Self {\n            data_producer_id,\n            ordered: Some(false),\n            max_packet_life_time: Some(max_packet_life_time),\n            max_retransmits: None,\n            paused: false,\n            subchannels: None,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Messages will be sent unreliably with a limited number of retransmission attempts.\n    #[must_use]\n    pub fn new_sctp_unordered_with_retransmits(\n        data_producer_id: DataProducerId,\n        max_retransmits: u16,\n    ) -> Self {\n        Self {\n            data_producer_id,\n            ordered: Some(false),\n            max_packet_life_time: None,\n            max_retransmits: Some(max_retransmits),\n            paused: false,\n            subchannels: None,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct DataConsumerDump {\n    pub id: DataConsumerId,\n    pub data_producer_id: DataProducerId,\n    pub r#type: DataConsumerType,\n    pub label: String,\n    pub protocol: String,\n    pub sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub buffered_amount_low_threshold: u32,\n    pub paused: bool,\n    pub subchannels: Vec<u16>,\n    pub data_producer_paused: bool,\n}\n\nimpl<'a> TryFromFbs<'a> for DataConsumerDump {\n    type FbsType = data_consumer::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            id: dump.id.parse()?,\n            data_producer_id: dump.data_producer_id.parse()?,\n            r#type: if dump.type_ == data_producer::Type::Sctp {\n                DataConsumerType::Sctp\n            } else {\n                DataConsumerType::Direct\n            },\n            label: dump.label.clone(),\n            protocol: dump.protocol.clone(),\n            sctp_stream_parameters: dump\n                .sctp_stream_parameters\n                .as_ref()\n                .map(|parameters| SctpStreamParameters::from_fbs(parameters.as_ref())),\n            buffered_amount_low_threshold: dump.buffered_amount_low_threshold,\n            paused: dump.paused,\n            subchannels: dump.subchannels.clone(),\n            data_producer_paused: dump.data_producer_paused,\n        })\n    }\n}\n\n/// RTC statistics of the data consumer.\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct DataConsumerStat {\n    // `type` field is present in worker, but ignored here\n    pub timestamp: u64,\n    pub label: String,\n    pub protocol: String,\n    pub messages_sent: u64,\n    pub bytes_sent: u64,\n    pub buffered_amount: u32,\n}\n\nimpl FromFbs for DataConsumerStat {\n    type FbsType = data_consumer::GetStatsResponse;\n\n    fn from_fbs(stats: &Self::FbsType) -> Self {\n        Self {\n            timestamp: stats.timestamp,\n            label: stats.label.to_string(),\n            protocol: stats.protocol.to_string(),\n            messages_sent: stats.messages_sent,\n            bytes_sent: stats.bytes_sent,\n            buffered_amount: stats.buffered_amount,\n        }\n    }\n}\n\n/// Data consumer type.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum DataConsumerType {\n    /// The endpoint receives messages using the SCTP protocol.\n    Sctp,\n    /// Messages are received directly by the Rust process over a direct transport.\n    Direct,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    DataProducerClose,\n    DataProducerPause,\n    DataProducerResume,\n    SctpSendBufferFull,\n    Message {\n        ppid: u32,\n        data: Vec<u8>,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    BufferedAmountLow {\n        buffered_amount: u32,\n    },\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::DataconsumerDataproducerClose => {\n                Ok(Notification::DataProducerClose)\n            }\n            notification::Event::DataconsumerDataproducerPause => {\n                Ok(Notification::DataProducerPause)\n            }\n            notification::Event::DataconsumerDataproducerResume => {\n                Ok(Notification::DataProducerResume)\n            }\n            notification::Event::DataconsumerSctpSendbufferFull => {\n                Ok(Notification::SctpSendBufferFull)\n            }\n            notification::Event::DataconsumerMessage => {\n                let Ok(Some(notification::BodyRef::DataConsumerMessageNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                Ok(Notification::Message {\n                    ppid: body.ppid().unwrap(),\n                    data: body.data().unwrap().into(),\n                })\n            }\n            notification::Event::DataconsumerBufferedAmountLow => {\n                let Ok(Some(notification::BodyRef::DataConsumerBufferedAmountLowNotification(\n                    body,\n                ))) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                Ok(Notification::BufferedAmountLow {\n                    buffered_amount: body.buffered_amount().unwrap(),\n                })\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    message: Bag<Arc<dyn Fn(&WebRtcMessage<'_>) + Send + Sync>>,\n    sctp_send_buffer_full: Bag<Arc<dyn Fn() + Send + Sync>>,\n    buffered_amount_low: Bag<Arc<dyn Fn(u32) + Send + Sync>>,\n    data_producer_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    data_producer_pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    data_producer_resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    transport_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: DataConsumerId,\n    r#type: DataConsumerType,\n    sctp_stream_parameters: Option<SctpStreamParameters>,\n    label: String,\n    protocol: String,\n    data_producer_id: DataProducerId,\n    direct: bool,\n    paused: Arc<Mutex<bool>>,\n    subchannels: Arc<Mutex<Vec<u16>>>,\n    data_producer_paused: Arc<Mutex<bool>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    transport: Arc<dyn Transport>,\n    weak_data_producer: WeakDataProducer,\n    closed: Arc<AtomicBool>,\n    // Drop subscription to data consumer-specific notifications when data consumer itself is\n    // dropped\n    _subscription_handlers: Mutex<Vec<Option<SubscriptionHandler>>>,\n    _on_transport_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let transport_id = self.transport.id();\n                let request = DataConsumerCloseRequest {\n                    data_consumer_id: self.id,\n                };\n                let weak_data_producer = self.weak_data_producer.clone();\n\n                self.executor\n                    .spawn(async move {\n                        if weak_data_producer.upgrade().is_some() {\n                            match channel.request(transport_id, request).await {\n                                Err(RequestError::ChannelClosed) => {\n                                    debug!(\"data consumer closing failed on drop: Channel already closed\");\n                                }\n                                Err(error) => {\n                                    error!(\"data consumer closing failed on drop: {}\", error);\n                                }\n                                Ok(_) => {}\n                            }\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// Data consumer created on transport other than\n/// [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Data consumer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct RegularDataConsumer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for RegularDataConsumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"RegularDataConsumer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"sctp_stream_parameters\", &self.inner.sctp_stream_parameters)\n            .field(\"label\", &self.inner.label)\n            .field(\"protocol\", &self.inner.protocol)\n            .field(\"data_producer_id\", &self.inner.data_producer_id)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"data_producer_paused\", &self.inner.data_producer_paused)\n            .field(\"subchannels\", &self.inner.subchannels)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<RegularDataConsumer> for DataConsumer {\n    fn from(producer: RegularDataConsumer) -> Self {\n        DataConsumer::Regular(producer)\n    }\n}\n\n/// Data consumer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Data consumer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct DirectDataConsumer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for DirectDataConsumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"DirectDataConsumer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"sctp_stream_parameters\", &self.inner.sctp_stream_parameters)\n            .field(\"label\", &self.inner.label)\n            .field(\"protocol\", &self.inner.protocol)\n            .field(\"data_producer_id\", &self.inner.data_producer_id)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"data_producer_paused\", &self.inner.data_producer_paused)\n            .field(\"subchannels\", &self.inner.subchannels)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<DirectDataConsumer> for DataConsumer {\n    fn from(producer: DirectDataConsumer) -> Self {\n        DataConsumer::Direct(producer)\n    }\n}\n\n/// A data consumer represents an endpoint capable of receiving data messages from a mediasoup\n/// [`Router`](crate::router::Router).\n///\n/// A data consumer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA\n/// DataChannel) to receive those messages, or can directly receive them in the Rust application if\n/// the data consumer was created on top of a\n/// [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[non_exhaustive]\n#[must_use = \"Data consumer will be closed on drop, make sure to keep it around for as long as needed\"]\npub enum DataConsumer {\n    /// Data consumer created on transport other than\n    /// [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Regular(RegularDataConsumer),\n    /// Data consumer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Direct(DirectDataConsumer),\n}\n\nimpl fmt::Debug for DataConsumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self {\n            DataConsumer::Regular(producer) => f.debug_tuple(\"Regular\").field(&producer).finish(),\n            DataConsumer::Direct(producer) => f.debug_tuple(\"Direct\").field(&producer).finish(),\n        }\n    }\n}\n\nimpl DataConsumer {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn new(\n        id: DataConsumerId,\n        r#type: DataConsumerType,\n        sctp_stream_parameters: Option<SctpStreamParameters>,\n        label: String,\n        protocol: String,\n        paused: bool,\n        data_producer: DataProducer,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        data_producer_paused: bool,\n        subchannels: Vec<u16>,\n        app_data: AppData,\n        transport: Arc<dyn Transport>,\n        direct: bool,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let closed = Arc::new(AtomicBool::new(false));\n        let paused = Arc::new(Mutex::new(paused));\n        #[allow(clippy::mutex_atomic)]\n        let data_producer_paused = Arc::new(Mutex::new(data_producer_paused));\n        let subchannels = Arc::new(Mutex::new(subchannels));\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let closed = Arc::clone(&closed);\n            let paused = Arc::clone(&paused);\n            let data_producer_paused = Arc::clone(&data_producer_paused);\n            let inner_weak = Arc::clone(&inner_weak);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::DataProducerClose => {\n                            if !closed.load(Ordering::SeqCst) {\n                                handlers.data_producer_close.call_simple();\n\n                                let maybe_inner =\n                                    inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                                if let Some(inner) = maybe_inner {\n                                    inner\n                                        .executor\n                                        .clone()\n                                        .spawn(async move {\n                                            // Potential drop needs to happen from a different\n                                            // thread to prevent potential deadlock\n                                            inner.close(false);\n                                        })\n                                        .detach();\n                                }\n                            }\n                        }\n                        Notification::DataProducerPause => {\n                            let mut data_producer_paused = data_producer_paused.lock();\n                            let paused = *paused.lock();\n                            *data_producer_paused = true;\n\n                            handlers.data_producer_pause.call_simple();\n\n                            if !paused {\n                                handlers.pause.call_simple();\n                            }\n                        }\n                        Notification::DataProducerResume => {\n                            let mut data_producer_paused = data_producer_paused.lock();\n                            let paused = *paused.lock();\n                            *data_producer_paused = false;\n\n                            handlers.data_producer_resume.call_simple();\n\n                            if !paused {\n                                handlers.resume.call_simple();\n                            }\n                        }\n                        Notification::SctpSendBufferFull => {\n                            handlers.sctp_send_buffer_full.call_simple();\n                        }\n                        Notification::Message { ppid, data } => {\n                            match WebRtcMessage::new(ppid, Cow::from(data)) {\n                                Ok(message) => {\n                                    handlers.message.call(|callback| {\n                                        callback(&message);\n                                    });\n                                }\n                                Err(ppid) => {\n                                    error!(\"Bad ppid {}\", ppid);\n                                }\n                            }\n                        }\n                        Notification::BufferedAmountLow { buffered_amount } => {\n                            handlers.buffered_amount_low.call(|callback| {\n                                callback(buffered_amount);\n                            });\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let on_transport_close_handler = transport.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            Box::new(move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.transport_close.call_simple();\n                    inner.close(false);\n                }\n            })\n        });\n        let inner = Arc::new(Inner {\n            id,\n            r#type,\n            sctp_stream_parameters,\n            label,\n            protocol,\n            data_producer_id: data_producer.id(),\n            paused,\n            data_producer_paused,\n            direct,\n            executor,\n            channel,\n            handlers,\n            subchannels,\n            app_data,\n            transport,\n            weak_data_producer: data_producer.downgrade(),\n            closed,\n            _subscription_handlers: Mutex::new(vec![subscription_handler]),\n            _on_transport_close_handler: Mutex::new(on_transport_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        if direct {\n            Self::Direct(DirectDataConsumer { inner })\n        } else {\n            Self::Regular(RegularDataConsumer { inner })\n        }\n    }\n\n    /// Data consumer identifier.\n    #[must_use]\n    pub fn id(&self) -> DataConsumerId {\n        self.inner().id\n    }\n\n    /// The associated data producer identifier.\n    #[must_use]\n    pub fn data_producer_id(&self) -> DataProducerId {\n        self.inner().data_producer_id\n    }\n\n    /// Transport to which data consumer belongs.\n    pub fn transport(&self) -> &Arc<dyn Transport> {\n        &self.inner().transport\n    }\n\n    /// The type of the data consumer.\n    #[must_use]\n    pub fn r#type(&self) -> DataConsumerType {\n        self.inner().r#type\n    }\n\n    /// Whether the data consumer is paused. It does not take into account whether the\n    /// associated data producer is paused.\n    #[must_use]\n    pub fn paused(&self) -> bool {\n        *self.inner().paused.lock()\n    }\n\n    /// Whether the associate data producer is paused.\n    #[must_use]\n    pub fn producer_paused(&self) -> bool {\n        *self.inner().data_producer_paused.lock()\n    }\n\n    /// The SCTP stream parameters (just if the data consumer type is `Sctp`).\n    #[must_use]\n    pub fn sctp_stream_parameters(&self) -> Option<SctpStreamParameters> {\n        self.inner().sctp_stream_parameters\n    }\n\n    /// The data consumer label.\n    #[must_use]\n    pub fn label(&self) -> &String {\n        &self.inner().label\n    }\n\n    /// The data consumer sub-protocol.\n    #[must_use]\n    pub fn protocol(&self) -> &String {\n        &self.inner().protocol\n    }\n\n    /// The data consumer subchannels.\n    #[must_use]\n    pub fn subchannels(&self) -> Vec<u16> {\n        self.inner().subchannels.lock().clone()\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner().app_data\n    }\n\n    /// Whether the data consumer is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner().closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump DataConsumer.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<DataConsumerDump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataConsumerDumpRequest {})\n            .await?;\n\n        if let response::Body::DataConsumerDumpResponse(data) = response {\n            Ok(DataConsumerDump::try_from_fbs(*data).expect(\"Error parsing dump response\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Returns current statistics of the data consumer.\n    ///\n    /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    pub async fn get_stats(&self) -> Result<Vec<DataConsumerStat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataConsumerGetStatsRequest {})\n            .await?;\n\n        if let response::Body::DataConsumerGetStatsResponse(data) = response {\n            Ok(vec![DataConsumerStat::from_fbs(&data)])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Pauses the data consumer (no mossage is sent to the consuming endpoint).\n    pub async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), DataConsumerPauseRequest {})\n            .await?;\n\n        let mut paused = self.inner().paused.lock();\n        let was_paused = *paused || *self.inner().data_producer_paused.lock();\n        *paused = true;\n\n        if !was_paused {\n            self.inner().handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Resumes the data consumer (messages are sent again to the consuming endpoint).\n    pub async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), DataConsumerResumeRequest {})\n            .await?;\n\n        let mut paused = self.inner().paused.lock();\n        let was_paused = *paused || *self.inner().data_producer_paused.lock();\n        *paused = false;\n\n        if was_paused {\n            self.inner().handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Returns the number of bytes of data currently buffered to be sent over the underlying SCTP\n    /// association.\n    ///\n    /// # Notes on usage\n    /// The underlying SCTP association uses a common send buffer for all data consumers, hence the\n    /// value given by this method indicates the data buffered for all data consumers in the\n    /// transport.\n    pub async fn get_buffered_amount(&self) -> Result<u32, RequestError> {\n        debug!(\"get_buffered_amount()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataConsumerGetBufferedAmountRequest {})\n            .await?;\n\n        Ok(response.buffered_amount)\n    }\n\n    /// Whenever the underlying SCTP association buffered bytes drop to this value,\n    /// `on_buffered_amount_low` callback is called.\n    pub async fn set_buffered_amount_low_threshold(\n        &self,\n        threshold: u32,\n    ) -> Result<(), RequestError> {\n        debug!(\n            \"set_buffered_amount_low_threshold() [threshold:{}]\",\n            threshold\n        );\n\n        self.inner()\n            .channel\n            .request(\n                self.id(),\n                DataConsumerSetBufferedAmountLowThresholdRequest { threshold },\n            )\n            .await\n    }\n\n    /// Sets subchannels to the worker DataConsumer.\n    pub async fn set_subchannels(&self, subchannels: Vec<u16>) -> Result<(), RequestError> {\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataConsumerSetSubchannelsRequest { subchannels })\n            .await?;\n\n        *self.inner().subchannels.lock() = response.subchannels;\n\n        Ok(())\n    }\n\n    /// Adds a subchannel to the worker DataConsumer.\n    pub async fn add_subchannel(&self, subchannel: u16) -> Result<(), RequestError> {\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataConsumerAddSubchannelRequest { subchannel })\n            .await?;\n\n        *self.inner().subchannels.lock() = response.subchannels;\n\n        Ok(())\n    }\n\n    /// Removes a subchannel to the worker DataConsumer.\n    pub async fn remove_subchannel(&self, subchannel: u16) -> Result<(), RequestError> {\n        let response = self\n            .inner()\n            .channel\n            .request(\n                self.id(),\n                DataConsumerRemoveSubchannelRequest { subchannel },\n            )\n            .await?;\n\n        *self.inner().subchannels.lock() = response.subchannels;\n\n        Ok(())\n    }\n\n    /// Callback is called when a message has been received from the corresponding data producer.\n    ///\n    /// # Notes on usage\n    /// Just available in direct transports, this is, those created via\n    /// [`Router::create_direct_transport`](crate::router::Router::create_direct_transport).\n    pub fn on_message<F: Fn(&WebRtcMessage<'_>) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner().handlers.message.add(Arc::new(callback))\n    }\n\n    /// Callback is called when a message could not be sent because the SCTP send buffer was full.\n    pub fn on_sctp_send_buffer_full<F: Fn() + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner()\n            .handlers\n            .sctp_send_buffer_full\n            .add(Arc::new(callback))\n    }\n\n    /// Emitted when the underlying SCTP association buffered bytes drop down to the value set with\n    /// [`DataConsumer::set_buffered_amount_low_threshold`].\n    ///\n    /// # Notes on usage\n    /// Only applicable for consumers of type `Sctp`.\n    pub fn on_buffered_amount_low<F: Fn(u32) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner()\n            .handlers\n            .buffered_amount_low\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated data producer is closed for whatever reason. The data\n    /// consumer itself is also closed.\n    pub fn on_data_producer_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner()\n            .handlers\n            .data_producer_close\n            .add(Box::new(callback))\n    }\n\n    /// Callback is called when the data consumer or its associated data producer is\n    /// paused and, as result, the data consumer becomes paused.\n    pub fn on_pause<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.pause.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the data consumer or its associated data producer is\n    /// resumed and, as result, the data consumer is no longer paused.\n    pub fn on_resume<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.resume.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated data producer is paused.\n    pub fn on_data_producer_pause<F: Fn() + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner()\n            .handlers\n            .data_producer_pause\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the associated data producer is resumed.\n    pub fn on_data_producer_resume<F: Fn() + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner()\n            .handlers\n            .data_producer_resume\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport this data consumer belongs to is closed for whatever\n    /// reason. The data consumer itself is also closed.\n    pub fn on_transport_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner()\n            .handlers\n            .transport_close\n            .add(Box::new(callback))\n    }\n\n    /// Callback is called when the data consumer is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if data consumer is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner().handlers.close.add(Box::new(callback));\n        if self.inner().closed.load(Ordering::Relaxed) {\n            self.inner().handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    /// Downgrade `DataConsumer` to [`WeakDataConsumer`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakDataConsumer {\n        WeakDataConsumer {\n            inner: Arc::downgrade(self.inner()),\n        }\n    }\n\n    fn inner(&self) -> &Arc<Inner> {\n        match self {\n            DataConsumer::Regular(data_consumer) => &data_consumer.inner,\n            DataConsumer::Direct(data_consumer) => &data_consumer.inner,\n        }\n    }\n}\n\nimpl DirectDataConsumer {\n    /// Sends direct messages from the Rust process.\n    pub async fn send(&self, message: WebRtcMessage<'_>) -> Result<(), RequestError> {\n        let (ppid, payload) = message.into_ppid_and_payload();\n\n        self.inner\n            .channel\n            .request(\n                self.inner.id,\n                DataConsumerSendRequest {\n                    ppid,\n                    payload: payload.into_owned(),\n                },\n            )\n            .await\n    }\n}\n\n/// [`WeakDataConsumer`] doesn't own data consumer instance on mediasoup-worker and will not prevent\n/// one from being destroyed once last instance of regular [`DataConsumer`] is dropped.\n///\n/// [`WeakDataConsumer`] vs [`DataConsumer`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakDataConsumer {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakDataConsumer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakDataConsumer\").finish()\n    }\n}\n\nimpl WeakDataConsumer {\n    /// Attempts to upgrade `WeakDataConsumer` to [`DataConsumer`] if last instance of one wasn't\n    /// dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<DataConsumer> {\n        let inner = self.inner.upgrade()?;\n\n        let data_consumer = if inner.direct {\n            DataConsumer::Direct(DirectDataConsumer { inner })\n        } else {\n            DataConsumer::Regular(RegularDataConsumer { inner })\n        };\n\n        Some(data_consumer)\n    }\n}\n"
  },
  {
    "path": "rust/src/router/data_producer/tests.rs",
    "content": "use crate::data_producer::DataProducerOptions;\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\n\nasync fn init() -> (Router, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport1 = router\n        .create_webrtc_transport({\n            let mut transport_options =\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }));\n\n            transport_options.enable_sctp = true;\n\n            transport_options\n        })\n        .await\n        .expect(\"Failed to create transport1\");\n\n    (router, transport1)\n}\n\n#[test]\nfn transport_close_event() {\n    future::block_on(async move {\n        let (router, transport1) = init().await;\n\n        let data_producer = transport1\n            .produce_data(DataProducerOptions::new_sctp(\n                SctpStreamParameters::new_ordered(666),\n            ))\n            .await\n            .expect(\"Failed to produce data\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_producer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_producer.on_transport_close(move || {\n            let _ = transport_close_tx.send(());\n        });\n\n        router.close();\n\n        transport_close_rx\n            .await\n            .expect(\"Failed to receive transport_close event\");\n\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(data_producer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/data_producer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{\n    DataProducerCloseRequest, DataProducerDumpRequest, DataProducerGetStatsRequest,\n    DataProducerPauseRequest, DataProducerResumeRequest, DataProducerSendNotification,\n};\nuse crate::transport::Transport;\nuse crate::uuid_based_wrapper_type;\nuse crate::worker::{Channel, NotificationError, RequestError};\nuse async_executor::Executor;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{data_producer, response};\nuse mediasoup_types::data_structures::{AppData, WebRtcMessage};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt;\nuse std::fmt::Debug;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\nuuid_based_wrapper_type!(\n    /// [`DataProducer`] identifier.\n    DataProducerId\n);\n\n/// [`DataProducer`] options.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct DataProducerOptions {\n    /// DataProducer id (just for\n    /// [`Router::pipe_data_producer_to_router`](crate::router::Router::pipe_producer_to_router)\n    /// method).\n    pub(super) id: Option<DataProducerId>,\n    /// SCTP parameters defining how the endpoint is sending the data.\n    /// Required if SCTP/DataChannel is used.\n    /// Must not be given if the data producer is created on a DirectTransport.\n    pub(super) sctp_stream_parameters: Option<SctpStreamParameters>,\n    /// A label which can be used to distinguish this DataChannel from others.\n    pub label: String,\n    /// Name of the sub-protocol used by this DataChannel.\n    pub protocol: String,\n    /// Whether the data producer must start in paused mode. Default false.\n    pub paused: bool,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl DataProducerOptions {\n    #[must_use]\n    pub(super) fn new_pipe_transport(\n        data_producer_id: DataProducerId,\n        sctp_stream_parameters: SctpStreamParameters,\n    ) -> Self {\n        Self {\n            id: Some(data_producer_id),\n            sctp_stream_parameters: Some(sctp_stream_parameters),\n            label: \"\".to_string(),\n            protocol: \"\".to_string(),\n            paused: false,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Data producer options for non-Direct transport.\n    #[must_use]\n    pub fn new_sctp(sctp_stream_parameters: SctpStreamParameters) -> Self {\n        Self {\n            id: None,\n            sctp_stream_parameters: Some(sctp_stream_parameters),\n            label: \"\".to_string(),\n            protocol: \"\".to_string(),\n            paused: false,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Data producer options for Direct transport.\n    #[must_use]\n    pub fn new_direct() -> Self {\n        Self {\n            id: None,\n            sctp_stream_parameters: None,\n            label: \"\".to_string(),\n            protocol: \"\".to_string(),\n            paused: false,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n/// Data consumer type.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum DataProducerType {\n    /// The endpoint sends messages using the SCTP protocol.\n    Sctp,\n    /// Messages are sent directly from the Rust process over a direct transport.\n    Direct,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct DataProducerDump {\n    pub id: DataProducerId,\n    pub r#type: DataProducerType,\n    pub label: String,\n    pub protocol: String,\n    pub sctp_stream_parameters: Option<SctpStreamParameters>,\n    pub paused: bool,\n}\n\nimpl<'a> TryFromFbs<'a> for DataProducerDump {\n    type FbsType = data_producer::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            id: dump.id.parse()?,\n            r#type: if dump.type_ == data_producer::Type::Sctp {\n                DataProducerType::Sctp\n            } else {\n                DataProducerType::Direct\n            },\n            label: dump.label,\n            protocol: dump.protocol,\n            sctp_stream_parameters: dump\n                .sctp_stream_parameters\n                .map(|parameters| SctpStreamParameters::from_fbs(parameters.as_ref())),\n            paused: dump.paused,\n        })\n    }\n}\n\n/// RTC statistics of the data producer.\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct DataProducerStat {\n    // `type` field is present in worker, but ignored here\n    pub timestamp: u64,\n    pub label: String,\n    pub protocol: String,\n    pub messages_received: u64,\n    pub bytes_received: u64,\n}\n\nimpl FromFbs for DataProducerStat {\n    type FbsType = data_producer::GetStatsResponse;\n\n    fn from_fbs(stats: &Self::FbsType) -> Self {\n        Self {\n            timestamp: stats.timestamp,\n            label: stats.label.to_string(),\n            protocol: stats.protocol.to_string(),\n            messages_received: stats.messages_received,\n            bytes_received: stats.bytes_received,\n        }\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    transport_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: DataProducerId,\n    r#type: DataProducerType,\n    sctp_stream_parameters: Option<SctpStreamParameters>,\n    label: String,\n    protocol: String,\n    paused: AtomicBool,\n    direct: bool,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    transport: Arc<dyn Transport>,\n    closed: AtomicBool,\n    _on_transport_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let transport_id = self.transport.id();\n                let request = DataProducerCloseRequest {\n                    data_producer_id: self.id,\n                };\n                self.executor\n                    .spawn(async move {\n                        match channel.request(transport_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\n                                    \"data producer closing failed on drop: Channel already closed\"\n                                );\n                            }\n                            Err(error) => {\n                                error!(\"data producer closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// Data producer created on transport other than\n/// [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Data producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct RegularDataProducer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for RegularDataProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"RegularDataProducer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"sctp_stream_parameters\", &self.inner.sctp_stream_parameters)\n            .field(\"label\", &self.inner.label)\n            .field(\"protocol\", &self.inner.protocol)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<RegularDataProducer> for DataProducer {\n    fn from(producer: RegularDataProducer) -> Self {\n        DataProducer::Regular(producer)\n    }\n}\n\n/// Data producer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Data producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct DirectDataProducer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for DirectDataProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"DirectDataProducer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"sctp_stream_parameters\", &self.inner.sctp_stream_parameters)\n            .field(\"label\", &self.inner.label)\n            .field(\"protocol\", &self.inner.protocol)\n            .field(\"paused\", &self.inner.paused)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<DirectDataProducer> for DataProducer {\n    fn from(producer: DirectDataProducer) -> Self {\n        DataProducer::Direct(producer)\n    }\n}\n\n/// A data producer represents an endpoint capable of injecting data messages into a mediasoup\n/// [`Router`](crate::router::Router).\n///\n/// A data producer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA DataChannel) to deliver\n/// those messages, or can directly send them from the Rust application if the data producer was\n/// created on top of a [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[non_exhaustive]\n#[must_use = \"Data producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub enum DataProducer {\n    /// Data producer created on transport other than\n    /// [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Regular(RegularDataProducer),\n    /// Data producer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Direct(DirectDataProducer),\n}\n\nimpl fmt::Debug for DataProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self {\n            DataProducer::Regular(producer) => f.debug_tuple(\"Regular\").field(&producer).finish(),\n            DataProducer::Direct(producer) => f.debug_tuple(\"Direct\").field(&producer).finish(),\n        }\n    }\n}\n\nimpl DataProducer {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn new(\n        id: DataProducerId,\n        r#type: DataProducerType,\n        sctp_stream_parameters: Option<SctpStreamParameters>,\n        label: String,\n        protocol: String,\n        paused: bool,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        transport: Arc<dyn Transport>,\n        direct: bool,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_transport_close_handler = transport.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            Box::new(move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.transport_close.call_simple();\n                    inner.close(false);\n                }\n            })\n        });\n        let inner = Arc::new(Inner {\n            id,\n            r#type,\n            sctp_stream_parameters,\n            label,\n            protocol,\n            paused: AtomicBool::new(paused),\n            direct,\n            executor,\n            channel,\n            handlers,\n            app_data,\n            transport,\n            closed: AtomicBool::new(false),\n            _on_transport_close_handler: Mutex::new(on_transport_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        if direct {\n            Self::Direct(DirectDataProducer { inner })\n        } else {\n            Self::Regular(RegularDataProducer { inner })\n        }\n    }\n\n    /// Data producer identifier.\n    #[must_use]\n    pub fn id(&self) -> DataProducerId {\n        self.inner().id\n    }\n\n    /// Transport to which data producer belongs.\n    pub fn transport(&self) -> &Arc<dyn Transport> {\n        &self.inner().transport\n    }\n\n    /// The type of the data producer.\n    #[must_use]\n    pub fn r#type(&self) -> DataProducerType {\n        self.inner().r#type\n    }\n\n    /// The SCTP stream parameters (just if the data producer type is `Sctp`).\n    #[must_use]\n    pub fn sctp_stream_parameters(&self) -> Option<SctpStreamParameters> {\n        self.inner().sctp_stream_parameters\n    }\n\n    /// Whether the DataProducer is paused.\n    #[must_use]\n    pub fn paused(&self) -> bool {\n        self.inner().paused.load(Ordering::SeqCst)\n    }\n\n    /// The data producer label.\n    #[must_use]\n    pub fn label(&self) -> &String {\n        &self.inner().label\n    }\n\n    /// The data producer sub-protocol.\n    #[must_use]\n    pub fn protocol(&self) -> &String {\n        &self.inner().protocol\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner().app_data\n    }\n\n    /// Whether the data producer is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner().closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump DataProducer.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<DataProducerDump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataProducerDumpRequest {})\n            .await?;\n\n        if let response::Body::DataProducerDumpResponse(data) = response {\n            Ok(DataProducerDump::try_from_fbs(*data).expect(\"Error parsing dump response\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Returns current statistics of the data producer.\n    ///\n    /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    pub async fn get_stats(&self) -> Result<Vec<DataProducerStat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), DataProducerGetStatsRequest {})\n            .await?;\n\n        if let response::Body::DataProducerGetStatsResponse(data) = response {\n            Ok(vec![DataProducerStat::from_fbs(&data)])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Pauses the data producer (no message is sent to its associated data consumers).\n    /// Calls [`DataConsumer::on_data_producer_pause`](crate::data_consumer::DataConsumer::on_data_producer_pause)\n    /// callback on all its associated data consumers.\n    pub async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), DataProducerPauseRequest {})\n            .await?;\n\n        let was_paused = self.inner().paused.swap(true, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner().handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Resumes the data producer (messages are sent to its associated data consumers).\n    /// Calls [`DataConsumer::on_data_producer_resume`](crate::data_consumer::DataConsumer::on_data_producer_resume)\n    /// callback on all its associated data consumers.\n    pub async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), DataProducerResumeRequest {})\n            .await?;\n\n        let was_paused = self.inner().paused.swap(false, Ordering::SeqCst);\n\n        if was_paused {\n            self.inner().handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Callback is called when the transport this data producer belongs to is closed for whatever\n    /// reason. The producer itself is also closed. A `on_data_producer_close` callback is called on\n    /// all its associated consumers.\n    pub fn on_transport_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner()\n            .handlers\n            .transport_close\n            .add(Box::new(callback))\n    }\n\n    /// Callback is called when the data producer is paused.\n    pub fn on_pause<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.pause.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the data producer is resumed.\n    pub fn on_resume<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.resume.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the producer is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if data producer is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner().handlers.close.add(Box::new(callback));\n        if self.inner().closed.load(Ordering::Relaxed) {\n            self.inner().handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    pub(super) fn close(&self) {\n        self.inner().close(true);\n    }\n\n    /// Downgrade `DataProducer` to [`WeakDataProducer`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakDataProducer {\n        WeakDataProducer {\n            inner: Arc::downgrade(self.inner()),\n        }\n    }\n\n    fn inner(&self) -> &Arc<Inner> {\n        match self {\n            DataProducer::Regular(data_producer) => &data_producer.inner,\n            DataProducer::Direct(data_producer) => &data_producer.inner,\n        }\n    }\n}\n\nimpl DirectDataProducer {\n    /// Sends direct messages from the Rust to the worker.\n    pub fn send(\n        &self,\n        message: WebRtcMessage<'_>,\n        subchannels: Option<Vec<u16>>,\n        required_subchannel: Option<u16>,\n    ) -> Result<(), NotificationError> {\n        let (ppid, payload) = message.into_ppid_and_payload();\n\n        self.inner.channel.notify(\n            self.inner.id,\n            DataProducerSendNotification {\n                ppid,\n                payload: payload.into_owned(),\n                subchannels,\n                required_subchannel,\n            },\n        )\n    }\n}\n\n/// Same as [`DataProducer`], but will not be closed when dropped.\n///\n/// Use [`NonClosingDataProducer::into_inner()`] method to get regular [`DataProducer`] instead and\n/// restore regular behavior of [`Drop`] implementation.\npub struct NonClosingDataProducer {\n    data_producer: DataProducer,\n    on_drop: Option<Box<dyn FnOnce(DataProducer) + Send + 'static>>,\n}\n\nimpl fmt::Debug for NonClosingDataProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"NonClosingDataProducer\")\n            .field(\"data_producer\", &self.data_producer)\n            .finish()\n    }\n}\n\nimpl Drop for NonClosingDataProducer {\n    fn drop(&mut self) {\n        if let Some(on_drop) = self.on_drop.take() {\n            on_drop(self.data_producer.clone())\n        }\n    }\n}\n\nimpl NonClosingDataProducer {\n    /// * `on_drop` - Callback that takes last `Producer` instance and must do something with it to\n    ///   prevent dropping and thus closing\n    pub(crate) fn new<F: FnOnce(DataProducer) + Send + 'static>(\n        data_producer: DataProducer,\n        on_drop: F,\n    ) -> Self {\n        Self {\n            data_producer,\n            on_drop: Some(Box::new(on_drop)),\n        }\n    }\n\n    /// Get inner [`DataProducer`] (which will close on drop in contrast to\n    /// `NonClosingDataProducer`).\n    pub fn into_inner(mut self) -> DataProducer {\n        self.on_drop.take();\n        self.data_producer.clone()\n    }\n}\n\n/// [`WeakDataProducer`] doesn't own data producer instance on mediasoup-worker and will not prevent\n/// one from being destroyed once last instance of regular [`DataProducer`] is dropped.\n///\n/// [`WeakDataProducer`] vs [`DataProducer`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakDataProducer {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakDataProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakDataProducer\").finish()\n    }\n}\n\nimpl WeakDataProducer {\n    /// Attempts to upgrade `WeakDataProducer` to [`DataProducer`] if last instance of one wasn't\n    /// dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<DataProducer> {\n        let inner = self.inner.upgrade()?;\n\n        let data_producer = if inner.direct {\n            DataProducer::Direct(DirectDataProducer { inner })\n        } else {\n            DataProducer::Regular(RegularDataProducer { inner })\n        };\n\n        Some(data_producer)\n    }\n}\n"
  },
  {
    "path": "rust/src/router/direct_transport/tests.rs",
    "content": "use crate::direct_transport::{DirectTransport, DirectTransportOptions};\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse std::env;\n\nasync fn init() -> (Router, DirectTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport = router\n        .create_direct_transport(DirectTransportOptions::default())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    (router, transport)\n}\n\n#[test]\nfn router_close_event() {\n    future::block_on(async move {\n        let (router, transport) = init().await;\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_router_close(Box::new(move || {\n            let _ = router_close_tx.send(());\n        }));\n\n        router.close();\n\n        router_close_rx\n            .await\n            .expect(\"Failed to receive router_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(transport.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/direct_transport.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::consumer::{Consumer, ConsumerId, ConsumerOptions};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType};\nuse crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType};\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{TransportCloseRequest, TransportSendRtcpNotification};\nuse crate::producer::{Producer, ProducerId, ProducerOptions};\nuse crate::router::transport::{TransportImpl, TransportType};\nuse crate::router::Router;\nuse crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions,\n    RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData,\n    TransportTraceEventType,\n};\nuse crate::worker::{\n    Channel, NotificationError, NotificationParseError, RequestError, SubscriptionHandler,\n};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{direct_transport, notification, response, transport};\nuse mediasoup_types::data_structures::{AppData, SctpState};\nuse mediasoup_types::sctp_parameters::SctpParameters;\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::{Arc, Weak};\n\n/// [`DirectTransport`] options.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct DirectTransportOptions {\n    /// Maximum allowed size for direct messages sent from DataProducers.\n    /// Default 262_144.\n    pub max_message_size: u32,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl Default for DirectTransportOptions {\n    fn default() -> Self {\n        Self {\n            max_message_size: 262_144,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct DirectTransportDump {\n    // Common to all Transports.\n    pub id: TransportId,\n    pub direct: bool,\n    pub producer_ids: Vec<ProducerId>,\n    pub consumer_ids: Vec<ConsumerId>,\n    pub map_ssrc_consumer_id: Vec<(u32, ConsumerId)>,\n    pub map_rtx_ssrc_consumer_id: Vec<(u32, ConsumerId)>,\n    pub data_producer_ids: Vec<DataProducerId>,\n    pub data_consumer_ids: Vec<DataConsumerId>,\n    pub recv_rtp_header_extensions: RecvRtpHeaderExtensions,\n    pub rtp_listener: RtpListener,\n    pub max_message_size: u32,\n    pub sctp_parameters: Option<SctpParameters>,\n    pub sctp_state: Option<SctpState>,\n    pub sctp_listener: Option<SctpListener>,\n    pub trace_event_types: Vec<TransportTraceEventType>,\n}\n\nimpl<'a> TryFromFbs<'a> for DirectTransportDump {\n    type FbsType = direct_transport::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            id: dump.base.id.parse()?,\n            direct: true,\n            producer_ids: dump\n                .base\n                .producer_ids\n                .iter()\n                .map(|producer_id| Ok(producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            consumer_ids: dump\n                .base\n                .consumer_ids\n                .iter()\n                .map(|consumer_id| Ok(consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_ssrc_consumer_id: dump\n                .base\n                .map_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_rtx_ssrc_consumer_id: dump\n                .base\n                .map_rtx_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_producer_ids: dump\n                .base\n                .data_producer_ids\n                .iter()\n                .map(|data_producer_id| Ok(data_producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_consumer_ids: dump\n                .base\n                .data_consumer_ids\n                .iter()\n                .map(|data_consumer_id| Ok(data_consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs(\n                dump.base.recv_rtp_header_extensions.as_ref(),\n            ),\n            rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?,\n            max_message_size: dump.base.max_message_size,\n            sctp_parameters: dump\n                .base\n                .sctp_parameters\n                .as_ref()\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: FromFbs::from_fbs(&dump.base.sctp_state),\n            sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| {\n                SctpListener::try_from_fbs(listener.as_ref().clone())\n                    .expect(\"Error parsing SctpListner\")\n            }),\n            trace_event_types: FromFbs::from_fbs(&dump.base.trace_event_types),\n        })\n    }\n}\n\n/// RTC statistics of the direct transport.\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct DirectTransportStat {\n    // Common to all Transports.\n    pub transport_id: TransportId,\n    pub timestamp: u64,\n    pub sctp_state: Option<SctpState>,\n    pub bytes_received: u64,\n    pub recv_bitrate: u32,\n    pub bytes_sent: u64,\n    pub send_bitrate: u32,\n    pub rtp_bytes_received: u64,\n    pub rtp_recv_bitrate: u32,\n    pub rtp_bytes_sent: u64,\n    pub rtp_send_bitrate: u32,\n    pub rtx_bytes_received: u64,\n    pub rtx_recv_bitrate: u32,\n    pub rtx_bytes_sent: u64,\n    pub rtx_send_bitrate: u32,\n    pub probation_bytes_sent: u64,\n    pub probation_send_bitrate: u32,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_incoming_bitrate: Option<u32>,\n    pub max_incoming_bitrate: Option<u32>,\n    pub max_outgoing_bitrate: Option<u32>,\n    pub min_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_received: Option<f64>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_sent: Option<f64>,\n}\n\nimpl<'a> TryFromFbs<'a> for DirectTransportStat {\n    type FbsType = direct_transport::GetStatsResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(stats: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            transport_id: stats.base.transport_id.parse()?,\n            timestamp: stats.base.timestamp,\n            sctp_state: FromFbs::from_fbs(&stats.base.sctp_state),\n            bytes_received: stats.base.bytes_received,\n            recv_bitrate: stats.base.recv_bitrate,\n            bytes_sent: stats.base.bytes_sent,\n            send_bitrate: stats.base.send_bitrate,\n            rtp_bytes_received: stats.base.rtp_bytes_received,\n            rtp_recv_bitrate: stats.base.rtp_recv_bitrate,\n            rtp_bytes_sent: stats.base.rtp_bytes_sent,\n            rtp_send_bitrate: stats.base.rtp_send_bitrate,\n            rtx_bytes_received: stats.base.rtx_bytes_received,\n            rtx_recv_bitrate: stats.base.rtx_recv_bitrate,\n            rtx_bytes_sent: stats.base.rtx_bytes_sent,\n            rtx_send_bitrate: stats.base.rtx_send_bitrate,\n            probation_bytes_sent: stats.base.probation_bytes_sent,\n            probation_send_bitrate: stats.base.probation_send_bitrate,\n            available_outgoing_bitrate: stats.base.available_outgoing_bitrate,\n            available_incoming_bitrate: stats.base.available_incoming_bitrate,\n            max_incoming_bitrate: stats.base.max_incoming_bitrate,\n            max_outgoing_bitrate: stats.base.max_outgoing_bitrate,\n            min_outgoing_bitrate: stats.base.min_outgoing_bitrate,\n            rtp_packet_loss_received: stats.base.rtp_packet_loss_received,\n            rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent,\n        })\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    rtcp: Bag<Arc<dyn Fn(&[u8]) + Send + Sync>>,\n    new_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    new_consumer: Bag<Arc<dyn Fn(&Consumer) + Send + Sync>, Consumer>,\n    new_data_producer: Bag<Arc<dyn Fn(&DataProducer) + Send + Sync>, DataProducer>,\n    new_data_consumer: Bag<Arc<dyn Fn(&DataConsumer) + Send + Sync>, DataConsumer>,\n    trace: Bag<Arc<dyn Fn(&TransportTraceEventData) + Send + Sync>, TransportTraceEventData>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    Trace(TransportTraceEventData),\n    Rtcp(Vec<u8>),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::TransportTrace => {\n                let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap();\n                let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            notification::Event::DirecttransportRtcp => {\n                let Ok(Some(notification::BodyRef::DirectTransportRtcpNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let rtcp_notification_fbs =\n                    direct_transport::RtcpNotification::try_from(body).unwrap();\n\n                Ok(Notification::Rtcp(rtcp_notification_fbs.data))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: TransportId,\n    next_mid_for_consumers: AtomicUsize,\n    used_sctp_stream_ids: Mutex<IntMap<u16, bool>>,\n    cname_for_producers: Mutex<Option<String>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    // Make sure router is not dropped until this transport is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to transport-specific notifications when transport itself is dropped\n    _subscription_handlers: Mutex<Vec<Option<SubscriptionHandler>>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = TransportCloseRequest {\n                    transport_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"transport closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"transport closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A direct transport represents a direct connection between the mediasoup Rust process and a\n/// [`Router`] instance in a mediasoup-worker thread.\n///\n/// A direct transport can be used to directly send and receive data messages from/to Rust by means\n/// of [`DataProducer`]s and [`DataConsumer`]s of type `Direct` created on a direct transport.\n/// Direct messages sent by a [`DataProducer`] in a direct transport can be consumed by endpoints\n/// connected through a SCTP capable transport ([`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport),\n/// [`PlainTransport`](crate::plain_transport::PlainTransport), [`PipeTransport`](crate::pipe_transport::PipeTransport)\n/// and also by the Rust application by means of a [`DataConsumer`] created on a [`DirectTransport`]\n/// (and vice-versa: messages sent over SCTP/DataChannel can be consumed by the Rust application by\n/// means of a [`DataConsumer`] created on a [`DirectTransport`]).\n///\n/// A direct transport can also be used to inject and directly consume RTP and RTCP packets in Rust\n/// by using the [`DirectProducer::send`](crate::producer::DirectProducer::send) and\n/// [`Consumer::on_rtp`] API (plus [`DirectTransport::send_rtcp`] and [`DirectTransport::on_rtcp`]\n/// API).\n#[derive(Clone)]\n#[must_use = \"Transport will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct DirectTransport {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for DirectTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"DirectTransport\")\n            .field(\"id\", &self.inner.id)\n            .field(\"next_mid_for_consumers\", &self.inner.next_mid_for_consumers)\n            .field(\"used_sctp_stream_ids\", &self.inner.used_sctp_stream_ids)\n            .field(\"cname_for_producers\", &self.inner.cname_for_producers)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl Transport for DirectTransport {\n    fn id(&self) -> TransportId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn produce(&self, producer_options: ProducerOptions) -> Result<Producer, ProduceError> {\n        debug!(\"produce()\");\n\n        let producer = self\n            .produce_impl(producer_options, TransportType::Direct)\n            .await?;\n\n        self.inner.handlers.new_producer.call_simple(&producer);\n\n        Ok(producer)\n    }\n\n    async fn consume(&self, consumer_options: ConsumerOptions) -> Result<Consumer, ConsumeError> {\n        debug!(\"consume()\");\n\n        let consumer = self\n            .consume_impl(consumer_options, TransportType::Direct, false)\n            .await?;\n\n        self.inner.handlers.new_consumer.call_simple(&consumer);\n\n        Ok(consumer)\n    }\n\n    async fn produce_data(\n        &self,\n        data_producer_options: DataProducerOptions,\n    ) -> Result<DataProducer, ProduceDataError> {\n        debug!(\"produce_data()\");\n\n        let data_producer = self\n            .produce_data_impl(\n                DataProducerType::Direct,\n                data_producer_options,\n                TransportType::Direct,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_producer\n            .call_simple(&data_producer);\n\n        Ok(data_producer)\n    }\n\n    async fn consume_data(\n        &self,\n        data_consumer_options: DataConsumerOptions,\n    ) -> Result<DataConsumer, ConsumeDataError> {\n        debug!(\"consume_data()\");\n\n        let data_consumer = self\n            .consume_data_impl(\n                DataConsumerType::Direct,\n                data_consumer_options,\n                TransportType::Direct,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_consumer\n            .call_simple(&data_consumer);\n\n        Ok(data_consumer)\n    }\n\n    async fn enable_trace_event(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.enable_trace_event_impl(types).await\n    }\n\n    fn on_new_producer(\n        &self,\n        callback: Arc<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_producer.add(callback)\n    }\n\n    fn on_new_consumer(\n        &self,\n        callback: Arc<dyn Fn(&Consumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_consumer.add(callback)\n    }\n\n    fn on_new_data_producer(\n        &self,\n        callback: Arc<dyn Fn(&DataProducer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_producer.add(callback)\n    }\n\n    fn on_new_data_consumer(\n        &self,\n        callback: Arc<dyn Fn(&DataConsumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_consumer.add(callback)\n    }\n\n    fn on_trace(\n        &self,\n        callback: Arc<dyn Fn(&TransportTraceEventData) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.trace.add(callback)\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(callback)\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\n#[async_trait]\nimpl TransportGeneric for DirectTransport {\n    type Dump = DirectTransportDump;\n    type Stat = DirectTransportStat;\n\n    /// Dump Transport.\n    #[doc(hidden)]\n    async fn dump(&self) -> Result<Self::Dump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self.dump_impl().await?;\n\n        if let response::Body::DirectTransportDumpResponse(data) = response {\n            Ok(DirectTransportDump::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Returns current RTC statistics of the transport.\n    ///\n    /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    async fn get_stats(&self) -> Result<Vec<Self::Stat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self.get_stats_impl().await?;\n\n        if let response::Body::DirectTransportGetStatsResponse(data) = response {\n            Ok(vec![DirectTransportStat::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\")])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n}\n\nimpl TransportImpl for DirectTransport {\n    fn channel(&self) -> &Channel {\n        &self.inner.channel\n    }\n\n    fn executor(&self) -> &Arc<Executor<'static>> {\n        &self.inner.executor\n    }\n\n    fn next_mid_for_consumers(&self) -> &AtomicUsize {\n        &self.inner.next_mid_for_consumers\n    }\n\n    fn used_sctp_stream_ids(&self) -> &Mutex<IntMap<u16, bool>> {\n        &self.inner.used_sctp_stream_ids\n    }\n\n    fn cname_for_producers(&self) -> &Mutex<Option<String>> {\n        &self.inner.cname_for_producers\n    }\n}\n\nimpl DirectTransport {\n    pub(super) fn new(\n        id: TransportId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        router: Router,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                        Notification::Rtcp(data) => {\n                            handlers.rtcp.call(|callback| {\n                                callback(&data);\n                            });\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let next_mid_for_consumers = AtomicUsize::default();\n        let used_sctp_stream_ids = Mutex::new(IntMap::default());\n        let cname_for_producers = Mutex::new(None);\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            next_mid_for_consumers,\n            used_sctp_stream_ids,\n            cname_for_producers,\n            executor,\n            channel,\n            handlers,\n            app_data,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handlers: Mutex::new(vec![subscription_handler]),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Send a RTCP packet from the Rust process.\n    ///\n    /// * `rtcp_packet` - Bytes containing a valid RTCP packet (can be a compound packet).\n    pub fn send_rtcp(&self, rtcp_packet: Vec<u8>) -> Result<(), NotificationError> {\n        self.inner\n            .channel\n            .notify(self.id(), TransportSendRtcpNotification { rtcp_packet })\n    }\n\n    /// Callback is called when the direct transport receives a RTCP packet from its router.\n    pub fn on_rtcp<F: Fn(&[u8]) + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.rtcp.add(Arc::new(callback))\n    }\n\n    /// Downgrade `DirectTransport` to [`WeakDirectTransport`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakDirectTransport {\n        WeakDirectTransport {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakDirectTransport`] doesn't own direct transport instance on mediasoup-worker and will not\n/// prevent one from being destroyed once last instance of regular [`DirectTransport`] is dropped.\n///\n/// [`WeakDirectTransport`] vs [`DirectTransport`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakDirectTransport {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakDirectTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakDirectTransport\").finish()\n    }\n}\n\nimpl WeakDirectTransport {\n    /// Attempts to upgrade `WeakDirectTransport` to [`DirectTransport`] if last instance of one\n    /// wasn't dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<DirectTransport> {\n        let inner = self.inner.upgrade()?;\n\n        Some(DirectTransport { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/pipe_transport/tests.rs",
    "content": "use crate::consumer::ConsumerOptions;\nuse crate::data_consumer::DataConsumerOptions;\nuse crate::data_producer::DataProducerOptions;\nuse crate::producer::ProducerOptions;\nuse crate::router::{\n    PipeDataProducerToRouterPair, PipeProducerToRouterPair, PipeToRouterOptions, Router,\n    RouterOptions,\n};\nuse crate::transport::Transport;\nuse crate::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeVideo, RtpCapabilities, RtpCodecCapability, RtpCodecParameters,\n    RtpCodecParametersParameters, RtpParameters,\n};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::NonZeroU32;\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Video {\n        mime_type: MimeTypeVideo::Vp8,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(90000).unwrap(),\n        parameters: RtpCodecParametersParameters::default(),\n        rtcp_feedback: vec![],\n    }]\n}\n\nfn video_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Video,\n        RtpParameters {\n            mid: Some(\"VIDEO\".to_string()),\n            codecs: vec![RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                payload_type: 112,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            }],\n            ..RtpParameters::default()\n        },\n    )\n}\n\nfn data_producer_options() -> DataProducerOptions {\n    DataProducerOptions::new_sctp(SctpStreamParameters::new_unordered_with_life_time(\n        666, 5000,\n    ))\n}\n\nfn consumer_device_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: Some(101),\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        }],\n        header_extensions: vec![],\n    }\n}\n\nasync fn init() -> (Router, Router, WebRtcTransport, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker1 = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let worker2 = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router1 = worker1\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let router2 = worker2\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let mut transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n    transport_options.enable_sctp = true;\n\n    let transport_1 = router1\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router2\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (router1, router2, transport_1, transport_2)\n}\n\n#[test]\nfn producer_close_is_transmitted_to_pipe_consumer() {\n    future::block_on(async move {\n        let (router1, router2, transport1, transport2) = init().await;\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        let PipeProducerToRouterPair {\n            pipe_producer,\n            pipe_consumer: _,\n        } = router1\n            .pipe_producer_to_router(\n                video_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe video producer to router\");\n\n        let pipe_producer = pipe_producer.into_inner();\n\n        let video_consumer = transport2\n            .consume(ConsumerOptions::new(\n                pipe_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume video\");\n\n        let (mut producer_close_tx, producer_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = video_consumer.on_producer_close(move || {\n            let _ = producer_close_tx.send(());\n        });\n\n        drop(video_producer);\n\n        producer_close_rx\n            .await\n            .expect(\"Failed to receive producer close event\");\n    });\n}\n\n#[test]\nfn data_producer_close_is_transmitted_to_pipe_data_consumer() {\n    future::block_on(async move {\n        let (router1, router2, transport1, transport2) = init().await;\n\n        let data_producer = transport1\n            .produce_data(data_producer_options())\n            .await\n            .expect(\"Failed to produce data\");\n\n        let PipeDataProducerToRouterPair {\n            pipe_data_producer,\n            pipe_data_consumer: _,\n        } = router1\n            .pipe_data_producer_to_router(\n                data_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe data producer to router\");\n\n        let pipe_data_producer = pipe_data_producer.into_inner();\n\n        let data_consumer = transport2\n            .consume_data(DataConsumerOptions::new_sctp(pipe_data_producer.id()))\n            .await\n            .expect(\"Failed to create data consumer\");\n\n        let (mut data_producer_close_tx, data_producer_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_data_producer_close(move || {\n            let _ = data_producer_close_tx.send(());\n        });\n\n        data_producer.close();\n\n        data_producer_close_rx\n            .await\n            .expect(\"Failed to receive data producer close event\");\n    });\n}\n"
  },
  {
    "path": "rust/src/router/pipe_transport.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::consumer::{Consumer, ConsumerId, ConsumerOptions};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType};\nuse crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType};\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{PipeTransportConnectRequest, PipeTransportData, TransportCloseRequest};\nuse crate::producer::{Producer, ProducerId, ProducerOptions};\nuse crate::router::transport::{TransportImpl, TransportType};\nuse crate::router::Router;\nuse crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions,\n    RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData,\n    TransportTraceEventType,\n};\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{notification, pipe_transport, response, transport};\nuse mediasoup_types::data_structures::{AppData, ListenInfo, SctpState, TransportTuple};\nuse mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters};\nuse mediasoup_types::srtp_parameters::SrtpParameters;\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt;\nuse std::net::IpAddr;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::{Arc, Weak};\n\n/// [`PipeTransport`] options.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct PipeTransportOptions {\n    /// Listening info.\n    pub listen_info: ListenInfo,\n    /// Create a SCTP association.\n    /// Default false.\n    pub enable_sctp: bool,\n    /// SCTP streams number.\n    pub num_sctp_streams: NumSctpStreams,\n    /// Maximum allowed size for SCTP messages sent by DataProducers.\n    /// Default 268_435_456.\n    pub max_sctp_message_size: u32,\n    /// Maximum SCTP send buffer used by DataConsumers.\n    /// Default 268_435_456.\n    pub sctp_send_buffer_size: u32,\n    /// Enable RTX and NACK for RTP retransmission. Useful if both Routers are located in different\n    /// hosts and there is packet lost in the link. For this to work, both PipeTransports must\n    /// enable this setting.\n    /// Default false.\n    pub enable_rtx: bool,\n    /// Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers are located in\n    /// different hosts. For this to work, connect() must be called with remote SRTP parameters.\n    /// Default false.\n    pub enable_srtp: bool,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl PipeTransportOptions {\n    /// Create Pipe transport options with given listen IP.\n    #[must_use]\n    pub fn new(listen_info: ListenInfo) -> Self {\n        Self {\n            listen_info,\n            enable_sctp: false,\n            num_sctp_streams: NumSctpStreams::default(),\n            max_sctp_message_size: 268_435_456,\n            sctp_send_buffer_size: 268_435_456,\n            enable_rtx: false,\n            enable_srtp: false,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct PipeTransportDump {\n    // Common to all Transports.\n    pub id: TransportId,\n    pub direct: bool,\n    pub producer_ids: Vec<ProducerId>,\n    pub consumer_ids: Vec<ConsumerId>,\n    pub map_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub map_rtx_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub data_producer_ids: Vec<DataProducerId>,\n    pub data_consumer_ids: Vec<DataConsumerId>,\n    pub recv_rtp_header_extensions: RecvRtpHeaderExtensions,\n    pub rtp_listener: RtpListener,\n    pub max_message_size: u32,\n    pub sctp_parameters: Option<SctpParameters>,\n    pub sctp_state: Option<SctpState>,\n    pub sctp_listener: Option<SctpListener>,\n    pub trace_event_types: Vec<TransportTraceEventType>,\n    // PipeTransport specific.\n    pub tuple: TransportTuple,\n    pub rtx: bool,\n    pub srtp_parameters: Option<SrtpParameters>,\n}\n\nimpl<'a> TryFromFbs<'a> for PipeTransportDump {\n    type FbsType = pipe_transport::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            // Common to all Transports.\n            id: dump.base.id.parse()?,\n            direct: false,\n            producer_ids: dump\n                .base\n                .producer_ids\n                .iter()\n                .map(|producer_id| Ok(producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            consumer_ids: dump\n                .base\n                .consumer_ids\n                .iter()\n                .map(|consumer_id| Ok(consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_ssrc_consumer_id: dump\n                .base\n                .map_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_rtx_ssrc_consumer_id: dump\n                .base\n                .map_rtx_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_producer_ids: dump\n                .base\n                .data_producer_ids\n                .iter()\n                .map(|data_producer_id| Ok(data_producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_consumer_ids: dump\n                .base\n                .data_consumer_ids\n                .iter()\n                .map(|data_consumer_id| Ok(data_consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs(\n                dump.base.recv_rtp_header_extensions.as_ref(),\n            ),\n            rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?,\n            max_message_size: dump.base.max_message_size,\n            sctp_parameters: dump\n                .base\n                .sctp_parameters\n                .as_ref()\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: FromFbs::from_fbs(&dump.base.sctp_state),\n            sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| {\n                SctpListener::try_from_fbs(listener.as_ref().clone())\n                    .expect(\"Error parsing SctpListner\")\n            }),\n            trace_event_types: FromFbs::from_fbs(&dump.base.trace_event_types),\n            // PipeTransport specific.\n            tuple: TransportTuple::from_fbs(&dump.tuple),\n            rtx: dump.rtx,\n            srtp_parameters: dump\n                .srtp_parameters\n                .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())),\n        })\n    }\n}\n\n/// RTC statistics of the pipe transport.\n#[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct PipeTransportStat {\n    // Common to all Transports.\n    // `type` field is present in worker, but ignored here\n    pub transport_id: TransportId,\n    pub timestamp: u64,\n    pub sctp_state: Option<SctpState>,\n    pub bytes_received: u64,\n    pub recv_bitrate: u32,\n    pub bytes_sent: u64,\n    pub send_bitrate: u32,\n    pub rtp_bytes_received: u64,\n    pub rtp_recv_bitrate: u32,\n    pub rtp_bytes_sent: u64,\n    pub rtp_send_bitrate: u32,\n    pub rtx_bytes_received: u64,\n    pub rtx_recv_bitrate: u32,\n    pub rtx_bytes_sent: u64,\n    pub rtx_send_bitrate: u32,\n    pub probation_bytes_sent: u64,\n    pub probation_send_bitrate: u32,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_incoming_bitrate: Option<u32>,\n    pub max_incoming_bitrate: Option<u32>,\n    pub max_outgoing_bitrate: Option<u32>,\n    pub min_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_received: Option<f64>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_sent: Option<f64>,\n    // PipeTransport specific.\n    pub tuple: TransportTuple,\n}\n\nimpl<'a> TryFromFbs<'a> for PipeTransportStat {\n    type FbsType = pipe_transport::GetStatsResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(stats: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            transport_id: stats.base.transport_id.parse()?,\n            timestamp: stats.base.timestamp,\n            sctp_state: FromFbs::from_fbs(&stats.base.sctp_state),\n            bytes_received: stats.base.bytes_received,\n            recv_bitrate: stats.base.recv_bitrate,\n            bytes_sent: stats.base.bytes_sent,\n            send_bitrate: stats.base.send_bitrate,\n            rtp_bytes_received: stats.base.rtp_bytes_received,\n            rtp_recv_bitrate: stats.base.rtp_recv_bitrate,\n            rtp_bytes_sent: stats.base.rtp_bytes_sent,\n            rtp_send_bitrate: stats.base.rtp_send_bitrate,\n            rtx_bytes_received: stats.base.rtx_bytes_received,\n            rtx_recv_bitrate: stats.base.rtx_recv_bitrate,\n            rtx_bytes_sent: stats.base.rtx_bytes_sent,\n            rtx_send_bitrate: stats.base.rtx_send_bitrate,\n            probation_bytes_sent: stats.base.probation_bytes_sent,\n            probation_send_bitrate: stats.base.probation_send_bitrate,\n            available_outgoing_bitrate: stats.base.available_outgoing_bitrate,\n            available_incoming_bitrate: stats.base.available_incoming_bitrate,\n            max_incoming_bitrate: stats.base.max_incoming_bitrate,\n            max_outgoing_bitrate: stats.base.max_outgoing_bitrate,\n            min_outgoing_bitrate: stats.base.min_outgoing_bitrate,\n            rtp_packet_loss_received: stats.base.rtp_packet_loss_received,\n            rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent,\n            // PlainTransport specific.\n            tuple: TransportTuple::from_fbs(stats.tuple.as_ref()),\n        })\n    }\n}\n\n/// Remote parameters for pipe transport.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PipeTransportRemoteParameters {\n    /// Remote IPv4 or IPv6.\n    pub ip: IpAddr,\n    /// Remote port.\n    pub port: u16,\n    /// SRTP parameters used by the paired `PipeTransport` to encrypt its RTP and RTCP.\n    pub srtp_parameters: Option<SrtpParameters>,\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    new_consumer: Bag<Arc<dyn Fn(&Consumer) + Send + Sync>, Consumer>,\n    new_data_producer: Bag<Arc<dyn Fn(&DataProducer) + Send + Sync>, DataProducer>,\n    new_data_consumer: Bag<Arc<dyn Fn(&DataConsumer) + Send + Sync>, DataConsumer>,\n    tuple: Bag<Arc<dyn Fn(&TransportTuple) + Send + Sync>, TransportTuple>,\n    sctp_state_change: Bag<Arc<dyn Fn(SctpState) + Send + Sync>>,\n    trace: Bag<Arc<dyn Fn(&TransportTraceEventData) + Send + Sync>, TransportTraceEventData>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    #[serde(rename_all = \"camelCase\")]\n    SctpStateChange {\n        sctp_state: SctpState,\n    },\n    Trace(TransportTraceEventData),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::TransportSctpStateChange => {\n                let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap());\n\n                Ok(Notification::SctpStateChange { sctp_state })\n            }\n            notification::Event::TransportTrace => {\n                let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap();\n                let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: TransportId,\n    next_mid_for_consumers: AtomicUsize,\n    used_sctp_stream_ids: Mutex<IntMap<u16, bool>>,\n    cname_for_producers: Mutex<Option<String>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    data: Arc<PipeTransportData>,\n    app_data: AppData,\n    // Make sure router is not dropped until this transport is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to transport-specific notifications when transport itself is dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = TransportCloseRequest {\n                    transport_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"transport closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"transport closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A pipe transport represents a network path through which RTP, RTCP (optionally secured with\n/// SRTP) and SCTP (DataChannel) is transmitted. Pipe transports are intended to intercommunicate\n/// two [`Router`] instances collocated on the same host or on separate hosts.\n///\n/// # Notes on usage\n/// When calling [`PipeTransport::consume`], all RTP streams of the [`Producer`] are transmitted\n/// verbatim (in contrast to what happens in [`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport)\n/// and [`PlainTransport`](crate::plain_transport::PlainTransport) in which a single and continuous\n/// RTP stream is sent to the consuming endpoint).\n#[derive(Clone)]\n#[must_use = \"Transport will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct PipeTransport {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for PipeTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"PipeTransport\")\n            .field(\"id\", &self.inner.id)\n            .field(\"next_mid_for_consumers\", &self.inner.next_mid_for_consumers)\n            .field(\"used_sctp_stream_ids\", &self.inner.used_sctp_stream_ids)\n            .field(\"cname_for_producers\", &self.inner.cname_for_producers)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl Transport for PipeTransport {\n    fn id(&self) -> TransportId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn produce(&self, producer_options: ProducerOptions) -> Result<Producer, ProduceError> {\n        debug!(\"produce()\");\n\n        let producer = self\n            .produce_impl(producer_options, TransportType::Pipe)\n            .await?;\n\n        self.inner.handlers.new_producer.call_simple(&producer);\n\n        Ok(producer)\n    }\n\n    async fn consume(&self, consumer_options: ConsumerOptions) -> Result<Consumer, ConsumeError> {\n        debug!(\"consume()\");\n\n        let consumer = self\n            .consume_impl(consumer_options, TransportType::Pipe, self.inner.data.rtx)\n            .await?;\n\n        self.inner.handlers.new_consumer.call_simple(&consumer);\n\n        Ok(consumer)\n    }\n\n    async fn produce_data(\n        &self,\n        data_producer_options: DataProducerOptions,\n    ) -> Result<DataProducer, ProduceDataError> {\n        debug!(\"produce_data()\");\n\n        let data_producer = self\n            .produce_data_impl(\n                DataProducerType::Sctp,\n                data_producer_options,\n                TransportType::Pipe,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_producer\n            .call_simple(&data_producer);\n\n        Ok(data_producer)\n    }\n\n    async fn consume_data(\n        &self,\n        data_consumer_options: DataConsumerOptions,\n    ) -> Result<DataConsumer, ConsumeDataError> {\n        debug!(\"consume_data()\");\n\n        let data_consumer = self\n            .consume_data_impl(\n                DataConsumerType::Sctp,\n                data_consumer_options,\n                TransportType::Pipe,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_consumer\n            .call_simple(&data_consumer);\n\n        Ok(data_consumer)\n    }\n\n    async fn enable_trace_event(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.enable_trace_event_impl(types).await\n    }\n\n    fn on_new_producer(\n        &self,\n        callback: Arc<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_producer.add(callback)\n    }\n\n    fn on_new_consumer(\n        &self,\n        callback: Arc<dyn Fn(&Consumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_consumer.add(callback)\n    }\n\n    fn on_new_data_producer(\n        &self,\n        callback: Arc<dyn Fn(&DataProducer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_producer.add(callback)\n    }\n\n    fn on_new_data_consumer(\n        &self,\n        callback: Arc<dyn Fn(&DataConsumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_consumer.add(callback)\n    }\n\n    fn on_trace(\n        &self,\n        callback: Arc<dyn Fn(&TransportTraceEventData) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.trace.add(callback)\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(callback)\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\n#[async_trait]\nimpl TransportGeneric for PipeTransport {\n    type Dump = PipeTransportDump;\n    type Stat = PipeTransportStat;\n\n    #[doc(hidden)]\n    async fn dump(&self) -> Result<Self::Dump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self.dump_impl().await?;\n\n        if let response::Body::PipeTransportDumpResponse(data) = response {\n            Ok(PipeTransportDump::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    async fn get_stats(&self) -> Result<Vec<Self::Stat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self.get_stats_impl().await?;\n\n        if let response::Body::PipeTransportGetStatsResponse(data) = response {\n            Ok(vec![PipeTransportStat::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\")])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n}\n\nimpl TransportImpl for PipeTransport {\n    fn channel(&self) -> &Channel {\n        &self.inner.channel\n    }\n\n    fn executor(&self) -> &Arc<Executor<'static>> {\n        &self.inner.executor\n    }\n\n    fn next_mid_for_consumers(&self) -> &AtomicUsize {\n        &self.inner.next_mid_for_consumers\n    }\n\n    fn used_sctp_stream_ids(&self) -> &Mutex<IntMap<u16, bool>> {\n        &self.inner.used_sctp_stream_ids\n    }\n\n    fn cname_for_producers(&self) -> &Mutex<Option<String>> {\n        &self.inner.cname_for_producers\n    }\n}\n\nimpl PipeTransport {\n    pub(super) fn new(\n        id: TransportId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        data: PipeTransportData,\n        app_data: AppData,\n        router: Router,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let data = Arc::new(data);\n\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let data = Arc::clone(&data);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::SctpStateChange { sctp_state } => {\n                            data.sctp_state.lock().replace(sctp_state);\n\n                            handlers.sctp_state_change.call(|callback| {\n                                callback(sctp_state);\n                            });\n                        }\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let next_mid_for_consumers = AtomicUsize::default();\n        let used_sctp_stream_ids = Mutex::new({\n            let mut used_used_sctp_stream_ids = IntMap::default();\n            if let Some(sctp_parameters) = &data.sctp_parameters {\n                for i in 0..sctp_parameters.mis {\n                    used_used_sctp_stream_ids.insert(i, false);\n                }\n            }\n            used_used_sctp_stream_ids\n        });\n        let cname_for_producers = Mutex::new(None);\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            next_mid_for_consumers,\n            used_sctp_stream_ids,\n            cname_for_producers,\n            executor,\n            channel,\n            handlers,\n            data,\n            app_data,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Provide the [`PipeTransport`] with remote parameters.\n    pub async fn connect(\n        &self,\n        remote_parameters: PipeTransportRemoteParameters,\n    ) -> Result<(), RequestError> {\n        debug!(\"connect()\");\n\n        let response = self\n            .inner\n            .channel\n            .request(\n                self.id(),\n                PipeTransportConnectRequest {\n                    ip: remote_parameters.ip,\n                    port: remote_parameters.port,\n                    srtp_parameters: remote_parameters.srtp_parameters,\n                },\n            )\n            .await?;\n\n        *self.inner.data.tuple.lock() = response.tuple;\n\n        Ok(())\n    }\n\n    /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this\n    /// transport.\n    pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> {\n        debug!(\"set_max_incoming_bitrate() [bitrate:{}]\", bitrate);\n\n        self.set_max_incoming_bitrate_impl(bitrate).await\n    }\n\n    /// The transport tuple. It refers to both RTP and RTCP since pipe transports use RTCP-mux by\n    /// design.\n    ///\n    /// # Notes on usage\n    /// * Once the pipe transport is created, `transport.tuple()` will contain information about\n    ///   its `local_address`, `local_port` and `protocol`.\n    /// * Information about `remote_ip` and `remote_port` will be set after calling `connect()`\n    ///   method.\n    #[must_use]\n    pub fn tuple(&self) -> TransportTuple {\n        self.inner.data.tuple.lock().clone()\n    }\n\n    /// Local SCTP parameters. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_parameters(&self) -> Option<SctpParameters> {\n        self.inner.data.sctp_parameters\n    }\n\n    /// Current SCTP state. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_state(&self) -> Option<SctpState> {\n        *self.inner.data.sctp_state.lock()\n    }\n\n    /// Local SRTP parameters representing the crypto suite and key material used to encrypt sending\n    /// RTP and SRTP. Those parameters must be given to the paired `PipeTransport` in the\n    /// `connect()` method.\n    #[must_use]\n    pub fn srtp_parameters(&self) -> Option<SrtpParameters> {\n        self.inner.data.srtp_parameters.lock().clone()\n    }\n\n    /// Callback is called after the remote RTP origin has been discovered. Only if `comedia` mode\n    /// was set.\n    pub fn on_tuple<F: Fn(&TransportTuple) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.tuple.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport SCTP state changes.\n    pub fn on_sctp_state_change<F: Fn(SctpState) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .sctp_state_change\n            .add(Arc::new(callback))\n    }\n\n    /// Downgrade `PipeTransport` to [`WeakPipeTransport`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakPipeTransport {\n        WeakPipeTransport {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakPipeTransport`] doesn't own pipe transport instance on mediasoup-worker and will not\n/// prevent one from being destroyed once last instance of regular [`PipeTransport`] is dropped.\n///\n/// [`WeakPipeTransport`] vs [`PipeTransport`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakPipeTransport {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakPipeTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakPipeTransport\").finish()\n    }\n}\n\nimpl WeakPipeTransport {\n    /// Attempts to upgrade `WeakPipeTransport` to [`PipeTransport`] if last instance of one\n    /// wasn't dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<PipeTransport> {\n        let inner = self.inner.upgrade()?;\n\n        Some(PipeTransport { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/plain_transport/tests.rs",
    "content": "use crate::plain_transport::PlainTransportOptions;\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\n\nasync fn init() -> Router {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\")\n}\n\n#[test]\nfn router_close_event() {\n    future::block_on(async move {\n        let router = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_router_close(Box::new(move || {\n            let _ = router_close_tx.send(());\n        }));\n\n        router.close();\n\n        router_close_rx\n            .await\n            .expect(\"Failed to receive router_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(transport.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/plain_transport.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::consumer::{Consumer, ConsumerId, ConsumerOptions};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType};\nuse crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType};\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{PlainTransportData, TransportCloseRequest, TransportConnectPlainRequest};\nuse crate::producer::{Producer, ProducerId, ProducerOptions};\nuse crate::router::transport::{TransportImpl, TransportType};\nuse crate::router::Router;\nuse crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions,\n    RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData,\n    TransportTraceEventType,\n};\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{notification, plain_transport, response, transport};\nuse mediasoup_types::data_structures::{AppData, ListenInfo, SctpState, TransportTuple};\nuse mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters};\nuse mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters};\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt;\nuse std::net::IpAddr;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::{Arc, Weak};\n\n/// [`PlainTransport`] options.\n///\n/// # Notes on usage\n/// * Note that `comedia` mode just makes sense when the remote endpoint is gonna produce RTP on\n///   this plain transport. Otherwise, if the remote endpoint does not send any RTP (or SCTP) packet\n///   to mediasoup, there is no way to detect its remote RTP IP and port, so the endpoint won't\n///   receive any packet from mediasoup.\n/// * In other words, do not use `comedia` mode if the remote endpoint is not going to produce RTP\n///   but just consume it. In those cases, do not set `comedia` flag and call\n///   [`PlainTransport::connect()`] with the IP and port(s) of the remote endpoint.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct PlainTransportOptions {\n    /// Listening info.\n    pub listen_info: ListenInfo,\n    /// Optional listening info for RTCP.\n    pub rtcp_listen_info: Option<ListenInfo>,\n    /// Use RTCP-mux (RTP and RTCP in the same port).\n    /// Default true.\n    pub rtcp_mux: bool,\n    /// Whether remote IP:port should be auto-detected based on first RTP/RTCP\n    /// packet received. If enabled, connect() method must not be called unless\n    /// SRTP is enabled. If so, it must be called with just remote SRTP parameters.\n    /// Default false.\n    pub comedia: bool,\n    /// Create a SCTP association.\n    /// Default false.\n    pub enable_sctp: bool,\n    /// SCTP streams number.\n    pub num_sctp_streams: NumSctpStreams,\n    /// Maximum allowed size for SCTP messages sent by DataProducers.\n    /// Default 262144.\n    pub max_sctp_message_size: u32,\n    /// Maximum SCTP send buffer used by DataConsumers.\n    /// Default 262144.\n    pub sctp_send_buffer_size: u32,\n    /// Enable SRTP. For this to work, connect() must be called with remote SRTP parameters.\n    /// Default false.\n    pub enable_srtp: bool,\n    /// The SRTP crypto suite to be used if enableSrtp is set.\n    /// Default 'AesCm128HmacSha180'.\n    pub srtp_crypto_suite: SrtpCryptoSuite,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl PlainTransportOptions {\n    /// Create Plain transport options with given listen IP.\n    #[must_use]\n    pub fn new(listen_info: ListenInfo) -> Self {\n        Self {\n            listen_info,\n            rtcp_listen_info: None,\n            rtcp_mux: true,\n            comedia: false,\n            enable_sctp: false,\n            num_sctp_streams: NumSctpStreams::default(),\n            max_sctp_message_size: 262_144,\n            sctp_send_buffer_size: 262_144,\n            enable_srtp: false,\n            srtp_crypto_suite: SrtpCryptoSuite::default(),\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct PlainTransportDump {\n    // Common to all Transports.\n    pub id: TransportId,\n    pub direct: bool,\n    pub producer_ids: Vec<ProducerId>,\n    pub consumer_ids: Vec<ConsumerId>,\n    pub map_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub map_rtx_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub data_producer_ids: Vec<DataProducerId>,\n    pub data_consumer_ids: Vec<DataConsumerId>,\n    pub recv_rtp_header_extensions: RecvRtpHeaderExtensions,\n    pub rtp_listener: RtpListener,\n    pub max_message_size: u32,\n    pub sctp_parameters: Option<SctpParameters>,\n    pub sctp_state: Option<SctpState>,\n    pub sctp_listener: Option<SctpListener>,\n    pub trace_event_types: Vec<TransportTraceEventType>,\n    // PlainTransport specific.\n    pub rtcp_mux: bool,\n    pub comedia: bool,\n    pub tuple: TransportTuple,\n    pub rtcp_tuple: Option<TransportTuple>,\n    pub srtp_parameters: Option<SrtpParameters>,\n}\n\nimpl<'a> TryFromFbs<'a> for PlainTransportDump {\n    type FbsType = plain_transport::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            // Common to all Transports.\n            id: dump.base.id.parse()?,\n            direct: false,\n            producer_ids: dump\n                .base\n                .producer_ids\n                .iter()\n                .map(|producer_id| Ok(producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            consumer_ids: dump\n                .base\n                .consumer_ids\n                .iter()\n                .map(|consumer_id| Ok(consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_ssrc_consumer_id: dump\n                .base\n                .map_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_rtx_ssrc_consumer_id: dump\n                .base\n                .map_rtx_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_producer_ids: dump\n                .base\n                .data_producer_ids\n                .iter()\n                .map(|data_producer_id| Ok(data_producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_consumer_ids: dump\n                .base\n                .data_consumer_ids\n                .iter()\n                .map(|data_consumer_id| Ok(data_consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs(\n                dump.base.recv_rtp_header_extensions.as_ref(),\n            ),\n            rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?,\n            max_message_size: dump.base.max_message_size,\n            sctp_parameters: dump\n                .base\n                .sctp_parameters\n                .as_ref()\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: FromFbs::from_fbs(&dump.base.sctp_state),\n            sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| {\n                SctpListener::try_from_fbs(listener.as_ref().clone())\n                    .expect(\"Error parsing SctpListner\")\n            }),\n            trace_event_types: dump\n                .base\n                .trace_event_types\n                .iter()\n                .map(TransportTraceEventType::from_fbs)\n                .collect(),\n            // PlainTransport specific.\n            rtcp_mux: dump.rtcp_mux,\n            comedia: dump.comedia,\n            tuple: TransportTuple::from_fbs(dump.tuple.as_ref()),\n            rtcp_tuple: dump\n                .rtcp_tuple\n                .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            srtp_parameters: dump\n                .srtp_parameters\n                .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())),\n        })\n    }\n}\n\n/// RTC statistics of the plain transport.\n#[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct PlainTransportStat {\n    // Common to all Transports.\n    // `type` field is present in worker, but ignored here\n    pub transport_id: TransportId,\n    pub timestamp: u64,\n    pub sctp_state: Option<SctpState>,\n    pub bytes_received: u64,\n    pub recv_bitrate: u32,\n    pub bytes_sent: u64,\n    pub send_bitrate: u32,\n    pub rtp_bytes_received: u64,\n    pub rtp_recv_bitrate: u32,\n    pub rtp_bytes_sent: u64,\n    pub rtp_send_bitrate: u32,\n    pub rtx_bytes_received: u64,\n    pub rtx_recv_bitrate: u32,\n    pub rtx_bytes_sent: u64,\n    pub rtx_send_bitrate: u32,\n    pub probation_bytes_sent: u64,\n    pub probation_send_bitrate: u32,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_incoming_bitrate: Option<u32>,\n    pub max_incoming_bitrate: Option<u32>,\n    pub max_outgoing_bitrate: Option<u32>,\n    pub min_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_received: Option<f64>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_sent: Option<f64>,\n    // PlainTransport specific.\n    pub rtcp_mux: bool,\n    pub comedia: bool,\n    pub tuple: TransportTuple,\n    pub rtcp_tuple: Option<TransportTuple>,\n}\n\nimpl<'a> TryFromFbs<'a> for PlainTransportStat {\n    type FbsType = plain_transport::GetStatsResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(stats: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            transport_id: stats.base.transport_id.parse()?,\n            timestamp: stats.base.timestamp,\n            sctp_state: FromFbs::from_fbs(&stats.base.sctp_state),\n            bytes_received: stats.base.bytes_received,\n            recv_bitrate: stats.base.recv_bitrate,\n            bytes_sent: stats.base.bytes_sent,\n            send_bitrate: stats.base.send_bitrate,\n            rtp_bytes_received: stats.base.rtp_bytes_received,\n            rtp_recv_bitrate: stats.base.rtp_recv_bitrate,\n            rtp_bytes_sent: stats.base.rtp_bytes_sent,\n            rtp_send_bitrate: stats.base.rtp_send_bitrate,\n            rtx_bytes_received: stats.base.rtx_bytes_received,\n            rtx_recv_bitrate: stats.base.rtx_recv_bitrate,\n            rtx_bytes_sent: stats.base.rtx_bytes_sent,\n            rtx_send_bitrate: stats.base.rtx_send_bitrate,\n            probation_bytes_sent: stats.base.probation_bytes_sent,\n            probation_send_bitrate: stats.base.probation_send_bitrate,\n            available_outgoing_bitrate: stats.base.available_outgoing_bitrate,\n            available_incoming_bitrate: stats.base.available_incoming_bitrate,\n            max_incoming_bitrate: stats.base.max_incoming_bitrate,\n            max_outgoing_bitrate: stats.base.max_outgoing_bitrate,\n            min_outgoing_bitrate: stats.base.min_outgoing_bitrate,\n            rtp_packet_loss_received: stats.base.rtp_packet_loss_received,\n            rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent,\n            // PlainTransport specific.\n            rtcp_mux: stats.rtcp_mux,\n            comedia: stats.comedia,\n            tuple: TransportTuple::from_fbs(stats.tuple.as_ref()),\n            rtcp_tuple: stats\n                .rtcp_tuple\n                .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n        })\n    }\n}\n\n/// Remote parameters for plain transport.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct PlainTransportRemoteParameters {\n    /// Remote IPv4 or IPv6.\n    /// Required if `comedia` is not set.\n    pub ip: Option<IpAddr>,\n    /// Remote port.\n    /// Required if `comedia` is not set.\n    pub port: Option<u16>,\n    /// Remote RTCP port.\n    /// Required if `comedia` is not set and RTCP-mux is not enabled.\n    pub rtcp_port: Option<u16>,\n    /// SRTP parameters used by the remote endpoint to encrypt its RTP and RTCP.\n    /// The SRTP crypto suite of the local `srtpParameters` gets also updated after connect()\n    /// resolves.\n    /// Required if enable_srtp was set.\n    pub srtp_parameters: Option<SrtpParameters>,\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    new_consumer: Bag<Arc<dyn Fn(&Consumer) + Send + Sync>, Consumer>,\n    new_data_producer: Bag<Arc<dyn Fn(&DataProducer) + Send + Sync>, DataProducer>,\n    new_data_consumer: Bag<Arc<dyn Fn(&DataConsumer) + Send + Sync>, DataConsumer>,\n    tuple: Bag<Arc<dyn Fn(&TransportTuple) + Send + Sync>, TransportTuple>,\n    rtcp_tuple: Bag<Arc<dyn Fn(&TransportTuple) + Send + Sync>, TransportTuple>,\n    sctp_state_change: Bag<Arc<dyn Fn(SctpState) + Send + Sync>>,\n    trace: Bag<Arc<dyn Fn(&TransportTraceEventData) + Send + Sync>, TransportTraceEventData>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    Tuple {\n        tuple: TransportTuple,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    RtcpTuple {\n        rtcp_tuple: TransportTuple,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    SctpStateChange {\n        sctp_state: SctpState,\n    },\n    Trace(TransportTraceEventData),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::PlaintransportTuple => {\n                let Ok(Some(notification::BodyRef::PlainTransportTupleNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let tuple_fbs = transport::Tuple::try_from(body.tuple().unwrap()).unwrap();\n                let tuple = TransportTuple::from_fbs(&tuple_fbs);\n\n                Ok(Notification::Tuple { tuple })\n            }\n            notification::Event::PlaintransportRtcpTuple => {\n                let Ok(Some(notification::BodyRef::PlainTransportRtcpTupleNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let rtcp_tuple_fbs = transport::Tuple::try_from(body.tuple().unwrap()).unwrap();\n                let rtcp_tuple = TransportTuple::from_fbs(&rtcp_tuple_fbs);\n\n                Ok(Notification::RtcpTuple { rtcp_tuple })\n            }\n            notification::Event::TransportSctpStateChange => {\n                let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap());\n\n                Ok(Notification::SctpStateChange { sctp_state })\n            }\n            notification::Event::TransportTrace => {\n                let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap();\n                let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: TransportId,\n    next_mid_for_consumers: AtomicUsize,\n    used_sctp_stream_ids: Mutex<IntMap<u16, bool>>,\n    cname_for_producers: Mutex<Option<String>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    data: Arc<PlainTransportData>,\n    app_data: AppData,\n    // Make sure router is not dropped until this transport is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to transport-specific notifications when transport itself is dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = TransportCloseRequest {\n                    transport_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"transport closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"transport closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A plain transport represents a network path through which RTP, RTCP (optionally secured with\n/// SRTP) and SCTP (DataChannel) is transmitted.\n#[derive(Clone)]\n#[must_use = \"Transport will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct PlainTransport {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for PlainTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"PlainTransport\")\n            .field(\"id\", &self.inner.id)\n            .field(\"next_mid_for_consumers\", &self.inner.next_mid_for_consumers)\n            .field(\"used_sctp_stream_ids\", &self.inner.used_sctp_stream_ids)\n            .field(\"cname_for_producers\", &self.inner.cname_for_producers)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl Transport for PlainTransport {\n    fn id(&self) -> TransportId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn produce(&self, producer_options: ProducerOptions) -> Result<Producer, ProduceError> {\n        debug!(\"produce()\");\n\n        let producer = self\n            .produce_impl(producer_options, TransportType::Plain)\n            .await?;\n\n        self.inner.handlers.new_producer.call_simple(&producer);\n\n        Ok(producer)\n    }\n\n    async fn consume(&self, consumer_options: ConsumerOptions) -> Result<Consumer, ConsumeError> {\n        debug!(\"consume()\");\n\n        let consumer = self\n            .consume_impl(consumer_options, TransportType::Plain, false)\n            .await?;\n\n        self.inner.handlers.new_consumer.call_simple(&consumer);\n\n        Ok(consumer)\n    }\n\n    async fn produce_data(\n        &self,\n        data_producer_options: DataProducerOptions,\n    ) -> Result<DataProducer, ProduceDataError> {\n        debug!(\"produce_data()\");\n\n        let data_producer = self\n            .produce_data_impl(\n                DataProducerType::Sctp,\n                data_producer_options,\n                TransportType::Plain,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_producer\n            .call_simple(&data_producer);\n\n        Ok(data_producer)\n    }\n\n    async fn consume_data(\n        &self,\n        data_consumer_options: DataConsumerOptions,\n    ) -> Result<DataConsumer, ConsumeDataError> {\n        debug!(\"consume_data()\");\n\n        let data_consumer = self\n            .consume_data_impl(\n                DataConsumerType::Sctp,\n                data_consumer_options,\n                TransportType::Plain,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_consumer\n            .call_simple(&data_consumer);\n\n        Ok(data_consumer)\n    }\n\n    async fn enable_trace_event(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.enable_trace_event_impl(types).await\n    }\n\n    fn on_new_producer(\n        &self,\n        callback: Arc<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_producer.add(callback)\n    }\n\n    fn on_new_consumer(\n        &self,\n        callback: Arc<dyn Fn(&Consumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_consumer.add(callback)\n    }\n\n    fn on_new_data_producer(\n        &self,\n        callback: Arc<dyn Fn(&DataProducer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_producer.add(callback)\n    }\n\n    fn on_new_data_consumer(\n        &self,\n        callback: Arc<dyn Fn(&DataConsumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_consumer.add(callback)\n    }\n\n    fn on_trace(\n        &self,\n        callback: Arc<dyn Fn(&TransportTraceEventData) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.trace.add(callback)\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(callback)\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\n#[async_trait]\nimpl TransportGeneric for PlainTransport {\n    type Dump = PlainTransportDump;\n    type Stat = PlainTransportStat;\n\n    #[doc(hidden)]\n    async fn dump(&self) -> Result<Self::Dump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self.dump_impl().await?;\n\n        if let response::Body::PlainTransportDumpResponse(data) = response {\n            Ok(PlainTransportDump::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    async fn get_stats(&self) -> Result<Vec<Self::Stat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self.get_stats_impl().await?;\n\n        if let response::Body::PlainTransportGetStatsResponse(data) = response {\n            Ok(vec![PlainTransportStat::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\")])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n}\n\nimpl TransportImpl for PlainTransport {\n    fn channel(&self) -> &Channel {\n        &self.inner.channel\n    }\n\n    fn executor(&self) -> &Arc<Executor<'static>> {\n        &self.inner.executor\n    }\n\n    fn next_mid_for_consumers(&self) -> &AtomicUsize {\n        &self.inner.next_mid_for_consumers\n    }\n\n    fn used_sctp_stream_ids(&self) -> &Mutex<IntMap<u16, bool>> {\n        &self.inner.used_sctp_stream_ids\n    }\n\n    fn cname_for_producers(&self) -> &Mutex<Option<String>> {\n        &self.inner.cname_for_producers\n    }\n}\n\nimpl PlainTransport {\n    pub(super) fn new(\n        id: TransportId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        data: PlainTransportData,\n        app_data: AppData,\n        router: Router,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let data = Arc::new(data);\n\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let data = Arc::clone(&data);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::Tuple { tuple } => {\n                            *data.tuple.lock() = tuple.clone();\n\n                            handlers.tuple.call_simple(&tuple);\n                        }\n                        Notification::RtcpTuple { rtcp_tuple } => {\n                            data.rtcp_tuple.lock().replace(rtcp_tuple.clone());\n\n                            handlers.rtcp_tuple.call_simple(&rtcp_tuple);\n                        }\n                        Notification::SctpStateChange { sctp_state } => {\n                            data.sctp_state.lock().replace(sctp_state);\n\n                            handlers.sctp_state_change.call(|callback| {\n                                callback(sctp_state);\n                            });\n                        }\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let next_mid_for_consumers = AtomicUsize::default();\n        let used_sctp_stream_ids = Mutex::new({\n            let mut used_used_sctp_stream_ids = IntMap::default();\n            if let Some(sctp_parameters) = &data.sctp_parameters {\n                for i in 0..sctp_parameters.mis {\n                    used_used_sctp_stream_ids.insert(i, false);\n                }\n            }\n            used_used_sctp_stream_ids\n        });\n        let cname_for_producers = Mutex::new(None);\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            next_mid_for_consumers,\n            used_sctp_stream_ids,\n            cname_for_producers,\n            executor,\n            channel,\n            handlers,\n            data,\n            app_data,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Provide the [`PlainTransport`] with remote parameters.\n    ///\n    /// # Notes on usage\n    /// * If `comedia` is enabled in this plain transport and SRTP is not, `connect()` must not be\n    ///   called.\n    /// * If `comedia` is enabled and SRTP is also enabled (`enable_srtp` was set in the\n    ///   [`Router::create_plain_transport`] options) then `connect()` must be called with just the\n    ///   remote `srtp_parameters`.\n    /// * If `comedia` is disabled, `connect()` must be eventually called with remote `ip`, `port`,\n    ///   optional `rtcp_port` (if RTCP-mux is not enabled) and optional `srtp_parameters` (if SRTP\n    ///   is enabled).\n    ///\n    /// # Examples\n    /// ```rust\n    /// use mediasoup::plain_transport::PlainTransportRemoteParameters;\n    ///\n    /// # async fn f(\n    /// #     plain_transport: mediasoup::plain_transport::PlainTransport,\n    /// # ) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Calling connect() on a PlainTransport created with comedia and rtcp_mux set.\n    /// plain_transport\n    ///     .connect(PlainTransportRemoteParameters {\n    ///         ip: Some(\"1.2.3.4\".parse().unwrap()),\n    ///         port: Some(9998),\n    ///         rtcp_port: None,\n    ///         srtp_parameters: None,\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    /// ```rust\n    /// use mediasoup::plain_transport::PlainTransportRemoteParameters;\n    ///\n    /// # async fn f(\n    /// #     plain_transport: mediasoup::plain_transport::PlainTransport,\n    /// # ) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Calling connect() on a PlainTransport created with comedia unset and rtcp_mux\n    /// // also unset.\n    /// plain_transport\n    ///     .connect(PlainTransportRemoteParameters {\n    ///         ip: Some(\"1.2.3.4\".parse().unwrap()),\n    ///         port: Some(9998),\n    ///         rtcp_port: Some(9999),\n    ///         srtp_parameters: None,\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    /// ```rust\n    /// use mediasoup::plain_transport::PlainTransportRemoteParameters;\n    /// use mediasoup_types::srtp_parameters::{SrtpParameters, SrtpCryptoSuite};\n    ///\n    /// # async fn f(\n    /// #     plain_transport: mediasoup::plain_transport::PlainTransport,\n    /// # ) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Calling connect() on a PlainTransport created with comedia set and\n    /// // enable_srtp enabled.\n    /// plain_transport\n    ///     .connect(PlainTransportRemoteParameters {\n    ///         ip: None,\n    ///         port: None,\n    ///         rtcp_port: None,\n    ///         srtp_parameters: Some(SrtpParameters {\n    ///             crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180,\n    ///             key_base64: \"ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv\".to_string(),\n    ///         }),\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    /// ```rust\n    /// use mediasoup::plain_transport::PlainTransportRemoteParameters;\n    /// use mediasoup_types::srtp_parameters::{SrtpParameters, SrtpCryptoSuite};\n    ///\n    /// # async fn f(\n    /// #     plain_transport: mediasoup::plain_transport::PlainTransport,\n    /// # ) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Calling connect() on a PlainTransport created with comedia unset,\n    /// // rtcp_mux set and enableSrtp enabled.\n    /// plain_transport\n    ///     .connect(PlainTransportRemoteParameters {\n    ///         ip: Some(\"1.2.3.4\".parse().unwrap()),\n    ///         port: Some(9998),\n    ///         rtcp_port: None,\n    ///         srtp_parameters: Some(SrtpParameters {\n    ///             crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180,\n    ///             key_base64: \"ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv\".to_string(),\n    ///         }),\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn connect(\n        &self,\n        remote_parameters: PlainTransportRemoteParameters,\n    ) -> Result<(), RequestError> {\n        debug!(\"connect()\");\n\n        let response = self\n            .inner\n            .channel\n            .request(\n                self.inner.id,\n                TransportConnectPlainRequest {\n                    ip: remote_parameters.ip,\n                    port: remote_parameters.port,\n                    rtcp_port: remote_parameters.rtcp_port,\n                    srtp_parameters: remote_parameters.srtp_parameters,\n                },\n            )\n            .await?;\n\n        *self.inner.data.tuple.lock() = response.tuple;\n\n        if let Some(rtcp_tuple) = response.rtcp_tuple {\n            self.inner.data.rtcp_tuple.lock().replace(rtcp_tuple);\n        }\n\n        if let Some(srtp_parameters) = response.srtp_parameters {\n            self.inner\n                .data\n                .srtp_parameters\n                .lock()\n                .replace(srtp_parameters);\n        }\n\n        Ok(())\n    }\n\n    /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this\n    /// transport.\n    pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> {\n        debug!(\"set_max_incoming_bitrate() [bitrate:{}]\", bitrate);\n\n        self.set_max_incoming_bitrate_impl(bitrate).await\n    }\n\n    /// The transport tuple. If RTCP-mux is enabled (`rtcp_mux` is set), this tuple refers to both\n    /// RTP and RTCP.\n    ///\n    /// # Notes on usage\n    /// * Once the plain transport is created, `transport.tuple()` will contain information about\n    ///   its `local_address`, `local_port` and `protocol`.\n    /// * Information about `remote_ip` and `remote_port` will be set:\n    ///   * after calling `connect()` method, or\n    ///   * via dynamic remote address detection when using `comedia` mode.\n    #[must_use]\n    pub fn tuple(&self) -> TransportTuple {\n        self.inner.data.tuple.lock().clone()\n    }\n\n    /// The transport tuple for RTCP. If RTCP-mux is enabled (`rtcp_mux` is set), its value is\n    /// `None`.\n    ///\n    /// # Notes on usage\n    /// * Once the plain transport is created (with RTCP-mux disabled), `transport.rtcp_tuple()`\n    ///   will contain information about its `local_address`, `local_port` and `protocol`.\n    /// * Information about `remote_ip` and `remote_port` will be set:\n    ///   * after calling `connect()` method, or\n    ///   * via dynamic remote address detection when using `comedia` mode.\n    #[must_use]\n    pub fn rtcp_tuple(&self) -> Option<TransportTuple> {\n        self.inner.data.rtcp_tuple.lock().clone()\n    }\n\n    /// Current SCTP state. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_parameters(&self) -> Option<SctpParameters> {\n        self.inner.data.sctp_parameters\n    }\n\n    /// Current SCTP state. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_state(&self) -> Option<SctpState> {\n        *self.inner.data.sctp_state.lock()\n    }\n\n    /// Local SRTP parameters representing the crypto suite and key material used to encrypt sending\n    /// RTP and SRTP. Note that, if `comedia` mode is set, these local SRTP parameters may change\n    /// after calling `connect()` with the remote SRTP parameters (to override the local SRTP crypto\n    /// suite with the one given in `connect()`).\n    #[must_use]\n    pub fn srtp_parameters(&self) -> Option<SrtpParameters> {\n        self.inner.data.srtp_parameters.lock().clone()\n    }\n\n    /// Callback is called after the remote RTP origin has been discovered. Only if `comedia` mode\n    /// was set.\n    pub fn on_tuple<F: Fn(&TransportTuple) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.tuple.add(Arc::new(callback))\n    }\n\n    /// Callback is called after the remote RTCP origin has been discovered. Only if `comedia` mode\n    /// was set and `rtcp_mux` was not.\n    pub fn on_rtcp_tuple<F: Fn(&TransportTuple) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.rtcp_tuple.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport SCTP state changes.\n    pub fn on_sctp_state_change<F: Fn(SctpState) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .sctp_state_change\n            .add(Arc::new(callback))\n    }\n\n    /// Downgrade `PlainTransport` to [`WeakPlainTransport`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakPlainTransport {\n        WeakPlainTransport {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakPlainTransport`] doesn't own pipe transport instance on mediasoup-worker and will not\n/// prevent one from being destroyed once last instance of regular [`PlainTransport`] is dropped.\n///\n/// [`WeakPlainTransport`] vs [`PlainTransport`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakPlainTransport {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakPlainTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakPlainTransport\").finish()\n    }\n}\n\nimpl WeakPlainTransport {\n    /// Attempts to upgrade `WeakPlainTransport` to [`PlainTransport`] if last instance of one\n    /// wasn't dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<PlainTransport> {\n        let inner = self.inner.upgrade()?;\n\n        Some(PlainTransport { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router/producer/tests.rs",
    "content": "use crate::producer::ProducerOptions;\nuse crate::router::{Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse crate::worker::WorkerSettings;\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters,\n    RtpParameters,\n};\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::Opus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(2).unwrap(),\n        parameters: RtpCodecParametersParameters::default(),\n        rtcp_feedback: vec![],\n    }]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 0,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            }],\n            ..RtpParameters::default()\n        },\n    )\n}\n\nasync fn init() -> (Router, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport_1 = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    (router, transport_1)\n}\n\n#[test]\nfn transport_close_event() {\n    future::block_on(async move {\n        let (router, transport_1) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_producer.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_producer.on_transport_close(move || {\n            let _ = transport_close_tx.send(());\n        });\n\n        router.close();\n\n        transport_close_rx\n            .await\n            .expect(\"Failed to receive transport_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(audio_producer.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/producer.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::consumer::{RtpStreamParams, RtxStreamParams};\nuse crate::fbs::{FromFbs, ToFbs, TryFromFbs};\nuse crate::messages::{\n    ProducerCloseRequest, ProducerDumpRequest, ProducerEnableTraceEventRequest,\n    ProducerGetStatsRequest, ProducerPauseRequest, ProducerResumeRequest, ProducerSendNotification,\n};\npub use crate::ortc::RtpMapping;\nuse crate::transport::Transport;\nuse crate::uuid_based_wrapper_type;\nuse crate::worker::{\n    Channel, NotificationError, NotificationParseError, RequestError, SubscriptionHandler,\n};\nuse async_executor::Executor;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{notification, producer, response, rtp_parameters, rtp_stream};\nuse mediasoup_types::data_structures::{\n    AppData, RtpPacketTraceInfo, SrTraceInfo, SsrcTraceInfo, TraceEventDirection,\n};\nuse mediasoup_types::rtp_parameters::{MediaKind, MimeType, RtpParameters};\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse serde_repr::{Deserialize_repr, Serialize_repr};\nuse std::error::Error;\nuse std::fmt;\nuse std::fmt::Debug;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\n\nuuid_based_wrapper_type!(\n    /// [`Producer`] identifier.\n    ProducerId\n);\n\n/// [`Producer`] options.\n///\n/// # Notes on usage\n/// Check the\n/// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n/// section for more details (TypeScript-oriented, but concepts apply here as well).\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct ProducerOptions {\n    /// Producer id (just for\n    /// [`Router::pipe_producer_to_router`](crate::router::Router::pipe_producer_to_router) method).\n    pub(super) id: Option<ProducerId>,\n    /// Media kind.\n    pub kind: MediaKind,\n    /// RTP parameters defining what the endpoint is sending.\n    pub rtp_parameters: RtpParameters,\n    /// Whether the producer must start in paused mode. Default false.\n    pub paused: bool,\n    /// Just for video. Time (in ms) before asking the sender for a new key frame after having asked\n    /// a previous one. If 0 there is no delay.\n    pub key_frame_request_delay: u32,\n    /// Add mediasoup custom 'urn:mediasoup:params:rtp-hdrext:packet-id' header extension\n    /// to RTP packets received from the sender endpoint.\n    pub enable_mediasoup_packet_id_header_extension: bool,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl ProducerOptions {\n    /// Create producer options that will be used with Pipe transport\n    #[must_use]\n    pub fn new_pipe_transport(\n        producer_id: ProducerId,\n        kind: MediaKind,\n        rtp_parameters: RtpParameters,\n    ) -> Self {\n        Self {\n            id: Some(producer_id),\n            kind,\n            rtp_parameters,\n            paused: false,\n            key_frame_request_delay: 0,\n            enable_mediasoup_packet_id_header_extension: false,\n            app_data: AppData::default(),\n        }\n    }\n\n    /// Create producer options that will be used with non-Pipe transport\n    #[must_use]\n    pub fn new(kind: MediaKind, rtp_parameters: RtpParameters) -> Self {\n        Self {\n            id: None,\n            kind,\n            rtp_parameters,\n            paused: false,\n            key_frame_request_delay: 0,\n            enable_mediasoup_packet_id_header_extension: false,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtxStream {\n    pub params: RtxStreamParams,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtpStreamRecv {\n    pub params: RtpStreamParams,\n    pub score: u8,\n    pub rtx_stream: Option<RtxStream>,\n}\n\nimpl<'a> TryFromFbs<'a> for RtpStreamRecv {\n    type FbsType = rtp_stream::DumpRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            params: RtpStreamParams::try_from_fbs(dump.params()?)?,\n            score: dump.score()?,\n            rtx_stream: if let Some(rtx_stream) = dump.rtx_stream()? {\n                Some(RtxStream {\n                    params: RtxStreamParams::try_from_fbs(rtx_stream.params()?)?,\n                })\n            } else {\n                None\n            },\n        })\n    }\n}\n\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct ProducerDump {\n    pub id: ProducerId,\n    pub kind: MediaKind,\n    pub paused: bool,\n    pub rtp_mapping: RtpMapping,\n    pub rtp_parameters: RtpParameters,\n    pub rtp_streams: Vec<RtpStreamRecv>,\n    pub trace_event_types: Vec<ProducerTraceEventType>,\n    pub r#type: ProducerType,\n}\n\nimpl<'a> TryFromFbs<'a> for ProducerDump {\n    type FbsType = producer::DumpResponseRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            id: dump.id()?.parse()?,\n            kind: MediaKind::from_fbs(&dump.kind()?),\n            paused: dump.paused()?,\n            rtp_mapping: RtpMapping::try_from_fbs(dump.rtp_mapping()?)?,\n            rtp_parameters: RtpParameters::try_from_fbs(dump.rtp_parameters()?)?,\n            rtp_streams: dump\n                .rtp_streams()?\n                .iter()\n                .map(|rtp_stream| RtpStreamRecv::try_from_fbs(rtp_stream?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            trace_event_types: dump\n                .trace_event_types()?\n                .iter()\n                .map(|trace_event_type| {\n                    ProducerTraceEventType::from_fbs(&trace_event_type.unwrap())\n                })\n                .collect(),\n\n            r#type: ProducerType::from_fbs(&dump.type_()?),\n        })\n    }\n}\n\n/// Producer type.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum ProducerType {\n    /// A single RTP stream is received with no spatial/temporal layers.\n    Simple,\n    /// Two or more RTP streams are received, each of them with one or more temporal layers.\n    Simulcast,\n    /// A single RTP stream is received with spatial/temporal layers.\n    Svc,\n}\n\nimpl FromFbs for ProducerType {\n    type FbsType = rtp_parameters::Type;\n\n    fn from_fbs(producer_type: &Self::FbsType) -> Self {\n        match producer_type {\n            rtp_parameters::Type::Simple => ProducerType::Simple,\n            rtp_parameters::Type::Simulcast => ProducerType::Simulcast,\n            rtp_parameters::Type::Svc => ProducerType::Svc,\n            // TODO: Create a new FBS type ProducerType with just Simple,\n            // Simulcast and Svc.\n            rtp_parameters::Type::Pipe => unimplemented!(),\n        }\n    }\n}\n\n/// Score of the RTP stream in the producer representing its transmission quality.\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ProducerScore {\n    /// Index of the RTP stream in the [`RtpParameters::encodings`] array of the producer.\n    pub encoding_idx: u32,\n    /// RTP stream SSRC.\n    pub ssrc: u32,\n    /// RTP stream RID value.\n    pub rid: Option<String>,\n    /// RTP stream score (from 0 to 10) representing the transmission quality.\n    pub score: u8,\n}\n\nimpl FromFbs for ProducerScore {\n    type FbsType = producer::Score;\n\n    fn from_fbs(producer_score: &producer::Score) -> Self {\n        Self {\n            encoding_idx: producer_score.encoding_idx,\n            ssrc: producer_score.ssrc,\n            rid: producer_score.rid.clone(),\n            score: producer_score.score,\n        }\n    }\n}\n\n/// Rotation angle\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr)]\n#[repr(u16)]\npub enum Rotation {\n    /// 0\n    None = 0,\n    /// 90 (clockwise)\n    Clockwise = 90,\n    /// 180\n    Rotate180 = 180,\n    /// 270 (90 counter-clockwise)\n    CounterClockwise = 270,\n}\n\n/// As documented in\n/// [WebRTC Video Processing and Codec Requirements](https://tools.ietf.org/html/rfc7742#section-4).\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\npub struct ProducerVideoOrientation {\n    /// Whether the source is a video camera.\n    pub camera: bool,\n    /// Whether the video source is flipped.\n    pub flip: bool,\n    /// Rotation degrees.\n    pub rotation: Rotation,\n}\n\nimpl FromFbs for ProducerVideoOrientation {\n    type FbsType = producer::VideoOrientationChangeNotification;\n\n    fn from_fbs(video_orientation: &Self::FbsType) -> Self {\n        Self {\n            camera: video_orientation.camera,\n            flip: video_orientation.flip,\n            rotation: match video_orientation.rotation {\n                0 => Rotation::None,\n                90 => Rotation::Clockwise,\n                180 => Rotation::Rotate180,\n                270 => Rotation::CounterClockwise,\n                _ => Rotation::None,\n            },\n        }\n    }\n}\n\n/// Bitrate  by layer.\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct BitrateByLayer {\n    layer: String,\n    bitrate: u32,\n}\n\n/// RTC statistics of the producer.\n#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct ProducerStat {\n    // Common to all RtpStreams.\n    // `type` field is present in worker, but ignored here\n    pub timestamp: u64,\n    pub ssrc: u32,\n    pub rtx_ssrc: Option<u32>,\n    pub rid: Option<String>,\n    pub kind: MediaKind,\n    pub mime_type: MimeType,\n    pub packets_lost: i32,\n    pub fraction_lost: u8,\n    pub jitter: u32,\n    pub packets_discarded: u64,\n    pub packets_retransmitted: u64,\n    pub packets_repaired: u64,\n    pub nack_count: u64,\n    pub nack_packet_count: u64,\n    pub pli_count: u64,\n    pub fir_count: u64,\n    pub packet_count: u64,\n    pub byte_count: u64,\n    pub bitrate: u32,\n    pub round_trip_time: Option<f32>,\n    pub rtx_packets_discarded: Option<u64>,\n    pub score: u8,\n    // RtpStreamRecv specific.\n    pub bitrate_by_layer: Vec<BitrateByLayer>,\n}\n\nimpl FromFbs for ProducerStat {\n    type FbsType = rtp_stream::Stats;\n\n    fn from_fbs(stats: &rtp_stream::Stats) -> Self {\n        let rtp_stream::StatsData::RecvStats(ref stats) = stats.data else {\n            panic!(\"Wrong message from worker: {stats:?}\");\n        };\n\n        let rtp_stream::StatsData::BaseStats(ref base) = stats.base.data else {\n            panic!(\"Wrong message from worker: {stats:?}\");\n        };\n\n        Self {\n            timestamp: base.timestamp,\n            ssrc: base.ssrc,\n            rtx_ssrc: base.rtx_ssrc,\n            rid: base.rid.clone(),\n            kind: MediaKind::from_fbs(&base.kind),\n            mime_type: base.mime_type.to_string().parse().unwrap(),\n            packets_lost: base.packets_lost,\n            fraction_lost: base.fraction_lost,\n            jitter: base.jitter,\n            packets_discarded: base.packets_discarded,\n            packets_retransmitted: base.packets_retransmitted,\n            packets_repaired: base.packets_repaired,\n            nack_count: base.nack_count,\n            nack_packet_count: base.nack_packet_count,\n            pli_count: base.pli_count,\n            fir_count: base.fir_count,\n            packet_count: stats.packet_count,\n            byte_count: stats.byte_count,\n            bitrate: stats.bitrate,\n            round_trip_time: Some(base.round_trip_time),\n            rtx_packets_discarded: Some(base.rtx_packets_discarded),\n            score: base.score,\n            bitrate_by_layer: stats\n                .bitrate_by_layer\n                .iter()\n                .map(|bitrate_by_layer| BitrateByLayer {\n                    layer: bitrate_by_layer.layer.to_string(),\n                    bitrate: bitrate_by_layer.bitrate,\n                })\n                .collect(),\n        }\n    }\n}\n\n/// 'trace' event data.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"type\", rename_all = \"lowercase\")]\npub enum ProducerTraceEventData {\n    /// RTP packet.\n    Rtp {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// RTP packet info.\n        info: RtpPacketTraceInfo,\n    },\n    /// RTP video keyframe packet.\n    KeyFrame {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// RTP packet info.\n        info: RtpPacketTraceInfo,\n    },\n    /// RTCP NACK packet.\n    Nack {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n    },\n    /// RTCP PLI packet.\n    Pli {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// SSRC info.\n        info: SsrcTraceInfo,\n    },\n    /// RTCP FIR packet.\n    Fir {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// SSRC info.\n        info: SsrcTraceInfo,\n    },\n    /// RTCP Sender Report.\n    Sr {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// SSRC info.\n        info: SrTraceInfo,\n    },\n}\n\nimpl FromFbs for ProducerTraceEventData {\n    type FbsType = producer::TraceNotification;\n\n    fn from_fbs(data: &Self::FbsType) -> Self {\n        match data.type_ {\n            producer::TraceEventType::Rtp => ProducerTraceEventData::Rtp {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(producer::TraceInfo::RtpTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    RtpPacketTraceInfo {\n                        is_rtx: info.is_rtx,\n                        ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref())\n                    }\n                },\n            },\n            producer::TraceEventType::Keyframe => ProducerTraceEventData::KeyFrame {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(producer::TraceInfo::KeyFrameTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    RtpPacketTraceInfo {\n                        is_rtx: info.is_rtx,\n                        ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref())\n                    }\n                },\n            },\n            producer::TraceEventType::Nack => ProducerTraceEventData::Nack {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n            },\n            producer::TraceEventType::Pli => ProducerTraceEventData::Pli {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(producer::TraceInfo::PliTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    SsrcTraceInfo { ssrc: info.ssrc }\n                },\n            },\n            producer::TraceEventType::Fir => ProducerTraceEventData::Fir {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(producer::TraceInfo::FirTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    SsrcTraceInfo { ssrc: info.ssrc }\n                },\n            },\n            producer::TraceEventType::Sr => ProducerTraceEventData::Sr {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(producer::TraceInfo::SrTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    SrTraceInfo::from_fbs(info.as_ref())\n                },\n            },\n        }\n    }\n}\n\n/// Types of consumer trace events.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum ProducerTraceEventType {\n    /// RTP packet.\n    Rtp,\n    /// RTP video keyframe packet.\n    KeyFrame,\n    /// RTCP NACK packet.\n    Nack,\n    /// RTCP PLI packet.\n    Pli,\n    /// RTCP FIR packet.\n    Fir,\n    /// RTCP Sender Report.\n    SR,\n}\n\nimpl ToFbs for ProducerTraceEventType {\n    type FbsType = producer::TraceEventType;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            ProducerTraceEventType::Rtp => producer::TraceEventType::Rtp,\n            ProducerTraceEventType::KeyFrame => producer::TraceEventType::Keyframe,\n            ProducerTraceEventType::Nack => producer::TraceEventType::Nack,\n            ProducerTraceEventType::Pli => producer::TraceEventType::Pli,\n            ProducerTraceEventType::Fir => producer::TraceEventType::Fir,\n            ProducerTraceEventType::SR => producer::TraceEventType::Sr,\n        }\n    }\n}\n\nimpl FromFbs for ProducerTraceEventType {\n    type FbsType = producer::TraceEventType;\n\n    fn from_fbs(event_type: &Self::FbsType) -> Self {\n        match event_type {\n            producer::TraceEventType::Rtp => ProducerTraceEventType::Rtp,\n            producer::TraceEventType::Keyframe => ProducerTraceEventType::KeyFrame,\n            producer::TraceEventType::Nack => ProducerTraceEventType::Nack,\n            producer::TraceEventType::Pli => ProducerTraceEventType::Pli,\n            producer::TraceEventType::Fir => ProducerTraceEventType::Fir,\n            producer::TraceEventType::Sr => ProducerTraceEventType::SR,\n        }\n    }\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    Score(Vec<ProducerScore>),\n    VideoOrientationChange(ProducerVideoOrientation),\n    Trace(ProducerTraceEventData),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::ProducerScore => {\n                let Ok(Some(notification::BodyRef::ProducerScoreNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let scores_fbs: Vec<_> = body\n                    .scores()\n                    .unwrap()\n                    .iter()\n                    .map(|score| producer::Score::try_from(score.unwrap()).unwrap())\n                    .collect();\n                let scores = FromFbs::from_fbs(&scores_fbs);\n\n                Ok(Notification::Score(scores))\n            }\n            notification::Event::ProducerVideoOrientationChange => {\n                let Ok(Some(notification::BodyRef::ProducerVideoOrientationChangeNotification(\n                    body,\n                ))) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let video_orientation_fbs =\n                    producer::VideoOrientationChangeNotification::try_from(body).unwrap();\n                let video_orientation = ProducerVideoOrientation::from_fbs(&video_orientation_fbs);\n\n                Ok(Notification::VideoOrientationChange(video_orientation))\n            }\n            notification::Event::ProducerTrace => {\n                let Ok(Some(notification::BodyRef::ProducerTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = producer::TraceNotification::try_from(body).unwrap();\n                let trace_notification = ProducerTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    score: Bag<Arc<dyn Fn(&[ProducerScore]) + Send + Sync>>,\n    video_orientation_change: Bag<Arc<dyn Fn(ProducerVideoOrientation) + Send + Sync>>,\n    pause: Bag<Arc<dyn Fn() + Send + Sync>>,\n    resume: Bag<Arc<dyn Fn() + Send + Sync>>,\n    trace: Bag<Arc<dyn Fn(&ProducerTraceEventData) + Send + Sync>, ProducerTraceEventData>,\n    transport_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: ProducerId,\n    kind: MediaKind,\n    r#type: ProducerType,\n    rtp_parameters: RtpParameters,\n    consumable_rtp_parameters: RtpParameters,\n    direct: bool,\n    paused: AtomicBool,\n    score: Arc<Mutex<Vec<ProducerScore>>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    transport: Arc<dyn Transport>,\n    closed: AtomicBool,\n    // Drop subscription to producer-specific notifications when producer itself is dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_transport_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let transport_id = self.transport.id();\n                let request = ProducerCloseRequest {\n                    producer_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(transport_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"producer closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"producer closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// Producer created on transport other than\n/// [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct RegularProducer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for RegularProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"RegularProducer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"kind\", &self.inner.kind)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"rtp_parameters\", &self.inner.rtp_parameters)\n            .field(\n                \"consumable_rtp_parameters\",\n                &self.inner.consumable_rtp_parameters,\n            )\n            .field(\"paused\", &self.inner.paused)\n            .field(\"score\", &self.inner.score)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<RegularProducer> for Producer {\n    fn from(producer: RegularProducer) -> Self {\n        Producer::Regular(producer)\n    }\n}\n\n/// Producer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n#[derive(Clone)]\n#[must_use = \"Producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct DirectProducer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for DirectProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"DirectProducer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"kind\", &self.inner.kind)\n            .field(\"type\", &self.inner.r#type)\n            .field(\"rtp_parameters\", &self.inner.rtp_parameters)\n            .field(\n                \"consumable_rtp_parameters\",\n                &self.inner.consumable_rtp_parameters,\n            )\n            .field(\"paused\", &self.inner.paused)\n            .field(\"score\", &self.inner.score)\n            .field(\"transport\", &self.inner.transport)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl From<DirectProducer> for Producer {\n    fn from(producer: DirectProducer) -> Self {\n        Producer::Direct(producer)\n    }\n}\n\n/// A producer represents an audio or video source being injected into a mediasoup router. It's\n/// created on top of a transport that defines how the media packets are carried.\n#[derive(Clone)]\n#[non_exhaustive]\n#[must_use = \"Producer will be closed on drop, make sure to keep it around for as long as needed\"]\npub enum Producer {\n    /// Producer created on transport other than\n    /// [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Regular(RegularProducer),\n    /// Producer created on [`DirectTransport`](crate::direct_transport::DirectTransport).\n    Direct(DirectProducer),\n}\n\nimpl fmt::Debug for Producer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self {\n            Producer::Regular(producer) => f.debug_tuple(\"Regular\").field(&producer).finish(),\n            Producer::Direct(producer) => f.debug_tuple(\"Direct\").field(&producer).finish(),\n        }\n    }\n}\n\nimpl Producer {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) async fn new(\n        id: ProducerId,\n        kind: MediaKind,\n        r#type: ProducerType,\n        rtp_parameters: RtpParameters,\n        consumable_rtp_parameters: RtpParameters,\n        paused: bool,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        transport: Arc<dyn Transport>,\n        direct: bool,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let score = Arc::<Mutex<Vec<ProducerScore>>>::default();\n\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let score = Arc::clone(&score);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::Score(scores) => {\n                            score.lock().clone_from(&scores);\n                            handlers.score.call(|callback| {\n                                callback(&scores);\n                            });\n                        }\n                        Notification::VideoOrientationChange(video_orientation) => {\n                            handlers.video_orientation_change.call(|callback| {\n                                callback(video_orientation);\n                            });\n                        }\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_transport_close_handler = transport.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            Box::new(move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.transport_close.call_simple();\n                    inner.close(false);\n                }\n            })\n        });\n        let inner = Arc::new(Inner {\n            id,\n            kind,\n            r#type,\n            rtp_parameters,\n            consumable_rtp_parameters,\n            direct,\n            paused: AtomicBool::new(paused),\n            score,\n            executor,\n            channel,\n            handlers,\n            app_data,\n            transport,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_transport_close_handler: Mutex::new(on_transport_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        if direct {\n            Self::Direct(DirectProducer { inner })\n        } else {\n            Self::Regular(RegularProducer { inner })\n        }\n    }\n\n    /// Producer identifier.\n    #[must_use]\n    pub fn id(&self) -> ProducerId {\n        self.inner().id\n    }\n\n    /// Transport to which producer belongs.\n    pub fn transport(&self) -> &Arc<dyn Transport> {\n        &self.inner().transport\n    }\n\n    /// Media kind.\n    #[must_use]\n    pub fn kind(&self) -> MediaKind {\n        self.inner().kind\n    }\n\n    /// Producer RTP parameters.\n    /// # Notes on usage\n    /// Check the\n    /// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    #[must_use]\n    pub fn rtp_parameters(&self) -> &RtpParameters {\n        &self.inner().rtp_parameters\n    }\n\n    /// Producer type.\n    #[must_use]\n    pub fn r#type(&self) -> ProducerType {\n        self.inner().r#type\n    }\n\n    /// Whether the Producer is paused.\n    #[must_use]\n    pub fn paused(&self) -> bool {\n        self.inner().paused.load(Ordering::SeqCst)\n    }\n\n    /// The score of each RTP stream being received, representing their transmission quality.\n    #[must_use]\n    pub fn score(&self) -> Vec<ProducerScore> {\n        self.inner().score.lock().clone()\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner().app_data\n    }\n\n    /// Whether the producer is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner().closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump Producer.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<ProducerDump, RequestError> {\n        debug!(\"dump()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), ProducerDumpRequest {})\n            .await\n    }\n\n    /// Returns current RTC statistics of the producer.\n    ///\n    /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    pub async fn get_stats(&self) -> Result<Vec<ProducerStat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self\n            .inner()\n            .channel\n            .request(self.id(), ProducerGetStatsRequest {})\n            .await?;\n\n        if let response::Body::ProducerGetStatsResponse(data) = response {\n            Ok(FromFbs::from_fbs(&data.stats))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    /// Pauses the producer (no RTP is sent to its associated consumers).  Calls\n    /// [`Consumer::on_producer_pause`](crate::consumer::Consumer::on_producer_pause) callback on\n    /// all its associated consumers.\n    pub async fn pause(&self) -> Result<(), RequestError> {\n        debug!(\"pause()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), ProducerPauseRequest {})\n            .await?;\n\n        let was_paused = self.inner().paused.swap(true, Ordering::SeqCst);\n\n        if !was_paused {\n            self.inner().handlers.pause.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Resumes the producer (RTP is sent to its associated consumers). Calls\n    /// [`Consumer::on_producer_resume`](crate::consumer::Consumer::on_producer_resume) callback on\n    /// all its associated consumers.\n    pub async fn resume(&self) -> Result<(), RequestError> {\n        debug!(\"resume()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), ProducerResumeRequest {})\n            .await?;\n\n        let was_paused = self.inner().paused.swap(false, Ordering::SeqCst);\n\n        if was_paused {\n            self.inner().handlers.resume.call_simple();\n        }\n\n        Ok(())\n    }\n\n    /// Instructs the procuer to emit \"trace\" events. For monitoring purposes. Use with caution.\n    pub async fn enable_trace_event(\n        &self,\n        types: Vec<ProducerTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.inner()\n            .channel\n            .request(self.id(), ProducerEnableTraceEventRequest { types })\n            .await\n    }\n\n    /// Callback is called when the producer score changes.\n    pub fn on_score<F: Fn(&[ProducerScore]) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner().handlers.score.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the video orientation changes. This is just possible if the\n    /// `urn:3gpp:video-orientation` RTP extension has been negotiated in the producer RTP\n    /// parameters.\n    pub fn on_video_orientation_change<F: Fn(ProducerVideoOrientation) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner()\n            .handlers\n            .video_orientation_change\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the producer is paused.\n    pub fn on_pause<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.pause.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the producer is resumed.\n    pub fn on_resume<F: Fn() + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner().handlers.resume.add(Arc::new(callback))\n    }\n\n    /// See [`Producer::enable_trace_event`] method.\n    pub fn on_trace<F: Fn(&ProducerTraceEventData) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner().handlers.trace.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport this producer belongs to is closed for whatever\n    /// reason. The producer itself is also closed. A `on_producer_close` callback is called on all\n    /// its associated consumers.\n    pub fn on_transport_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner()\n            .handlers\n            .transport_close\n            .add(Box::new(callback))\n    }\n\n    /// Callback is called when the producer is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if producer is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner().handlers.close.add(Box::new(callback));\n        if self.inner().closed.load(Ordering::Relaxed) {\n            self.inner().handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    /// Consumable RTP parameters.\n    // This is used in tests, otherwise would have been `pub(super)`\n    #[doc(hidden)]\n    #[must_use]\n    pub fn consumable_rtp_parameters(&self) -> &RtpParameters {\n        &self.inner().consumable_rtp_parameters\n    }\n\n    pub(super) fn close(&self) {\n        self.inner().close(true);\n    }\n\n    /// Downgrade `Producer` to [`WeakProducer`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakProducer {\n        WeakProducer {\n            inner: Arc::downgrade(self.inner()),\n        }\n    }\n\n    fn inner(&self) -> &Arc<Inner> {\n        match self {\n            Producer::Regular(producer) => &producer.inner,\n            Producer::Direct(producer) => &producer.inner,\n        }\n    }\n}\n\nimpl DirectProducer {\n    /// Sends a RTP packet from the Rust process.\n    pub fn send(&self, rtp_packet: Vec<u8>) -> Result<(), NotificationError> {\n        self.inner\n            .channel\n            .notify(self.inner.id, ProducerSendNotification { rtp_packet })\n    }\n}\n\n/// Same as [`Producer`], but will not be closed when dropped.\n///\n/// The idea here is that [`ProducerId`] of both original [`Producer`] and `PipedProducer` is the\n/// same and lifetime of piped producer is also tied to original producer, so you may not need to\n/// store `PipedProducer` at all.\n///\n/// Use [`PipedProducer::into_inner()`] method to get regular [`Producer`] instead and restore\n/// regular behavior of [`Drop`] implementation.\npub struct PipedProducer {\n    producer: Producer,\n    on_drop: Option<Box<dyn FnOnce(Producer) + Send + 'static>>,\n}\n\nimpl fmt::Debug for PipedProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"PipedProducer\")\n            .field(\"producer\", &self.producer)\n            .finish()\n    }\n}\n\nimpl Drop for PipedProducer {\n    fn drop(&mut self) {\n        if let Some(on_drop) = self.on_drop.take() {\n            on_drop(self.producer.clone())\n        }\n    }\n}\n\nimpl PipedProducer {\n    /// * `on_drop` - Callback that takes last `Producer` instance and must do something with it to\n    ///   prevent dropping and thus closing\n    pub(crate) fn new<F: FnOnce(Producer) + Send + 'static>(\n        producer: Producer,\n        on_drop: F,\n    ) -> Self {\n        Self {\n            producer,\n            on_drop: Some(Box::new(on_drop)),\n        }\n    }\n\n    /// Get inner [`Producer`] (which will close on drop in contrast to `PipedProducer`).\n    pub fn into_inner(mut self) -> Producer {\n        self.on_drop.take();\n        self.producer.clone()\n    }\n}\n\n/// [`WeakProducer`] doesn't own producer instance on mediasoup-worker and will not prevent one from\n/// being destroyed once last instance of regular [`Producer`] is dropped.\n///\n/// [`WeakProducer`] vs [`Producer`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakProducer {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakProducer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakProducer\").finish()\n    }\n}\n\nimpl WeakProducer {\n    /// Attempts to upgrade `WeakProducer` to [`Producer`] if last instance of one wasn't dropped\n    /// yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<Producer> {\n        let inner = self.inner.upgrade()?;\n\n        let producer = if inner.direct {\n            Producer::Direct(DirectProducer { inner })\n        } else {\n            Producer::Regular(RegularProducer { inner })\n        };\n\n        Some(producer)\n    }\n}\n"
  },
  {
    "path": "rust/src/router/rtp_observer.rs",
    "content": "use crate::producer::{Producer, ProducerId};\nuse crate::router::Router;\nuse crate::uuid_based_wrapper_type;\nuse crate::worker::RequestError;\nuse async_trait::async_trait;\nuse event_listener_primitives::HandlerId;\nuse mediasoup_types::data_structures::AppData;\n\nuuid_based_wrapper_type!(\n    /// [`RtpObserver`] identifier.\n    RtpObserverId\n);\n\n/// Options for adding producer to `[RtpObserver`]\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct RtpObserverAddProducerOptions {\n    /// The id of the Producer to be added.\n    pub producer_id: ProducerId,\n}\n\nimpl RtpObserverAddProducerOptions {\n    /// * `producer_id` - The id of the [`Producer`] to be added.\n    #[must_use]\n    pub fn new(producer_id: ProducerId) -> Self {\n        Self { producer_id }\n    }\n}\n\n/// An RTP observer inspects the media received by a set of selected producers.\n///\n/// mediasoup implements the following RTP observers:\n/// * [`AudioLevelObserver`](crate::audio_level_observer::AudioLevelObserver)\n/// * [`ActiveSpeakerObserver`](crate::active_speaker_observer::ActiveSpeakerObserver)\n#[async_trait]\npub trait RtpObserver {\n    /// RtpObserver id.\n    #[must_use]\n    fn id(&self) -> RtpObserverId;\n\n    /// Router to which RTP observer belongs.\n    fn router(&self) -> &Router;\n\n    /// Whether the RtpObserver is paused.\n    #[must_use]\n    fn paused(&self) -> bool;\n\n    /// Custom application data.\n    #[must_use]\n    fn app_data(&self) -> &AppData;\n\n    /// Whether the RTP observer is closed.\n    #[must_use]\n    fn closed(&self) -> bool;\n\n    /// Pauses the RTP observer. No RTP is inspected until resume() is called.\n    async fn pause(&self) -> Result<(), RequestError>;\n\n    /// Resumes the RTP observer. RTP is inspected again.\n    async fn resume(&self) -> Result<(), RequestError>;\n\n    /// Provides the RTP observer with a new producer to monitor.\n    async fn add_producer(\n        &self,\n        rtp_observer_add_producer_options: RtpObserverAddProducerOptions,\n    ) -> Result<(), RequestError>;\n\n    /// Removes the given producer from the RTP observer.\n    async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError>;\n\n    /// Callback is called when the RTP observer is paused.\n    fn on_pause(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId;\n\n    /// Callback is called when the RTP observer is resumed.\n    fn on_resume(&self, callback: Box<dyn Fn() + Send + Sync + 'static>) -> HandlerId;\n\n    /// Callback is called when a new producer is added into the RTP observer.\n    fn on_add_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when a producer is removed from the RTP observer.\n    fn on_remove_producer(\n        &self,\n        callback: Box<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when the router this RTP observer belongs to is closed for whatever reason. The RTP\n    /// observer itself is also closed.\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId;\n\n    /// Callback is called when the RTP observer is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if observer is already closed.\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId;\n}\n"
  },
  {
    "path": "rust/src/router/tests.rs",
    "content": "use crate::router::RouterOptions;\nuse crate::worker::{Worker, WorkerSettings};\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse std::env;\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings {\n            enable_liburing: false,\n            ..WorkerSettings::default()\n        })\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn worker_close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::default())\n            .await\n            .expect(\"Failed to create router\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = router.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut worker_close_tx, worker_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = router.on_worker_close(move || {\n            let _ = worker_close_tx.send(());\n        });\n\n        worker.close();\n\n        worker_close_rx\n            .await\n            .expect(\"Failed to receive worker_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(router.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/router/transport.rs",
    "content": "use crate::consumer::{Consumer, ConsumerId, ConsumerOptions, ConsumerType};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType};\nuse crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType};\nuse crate::fbs::{FromFbs, ToFbs, TryFromFbs};\nuse crate::messages::{\n    TransportConsumeDataRequest, TransportConsumeRequest, TransportDumpRequest,\n    TransportEnableTraceEventRequest, TransportGetStatsRequest, TransportProduceDataRequest,\n    TransportProduceRequest, TransportSetMaxIncomingBitrateRequest,\n    TransportSetMaxOutgoingBitrateRequest, TransportSetMinOutgoingBitrateRequest,\n};\npub use crate::ortc::{\n    ConsumerRtpParametersError, RtpCapabilitiesError, RtpParametersError, RtpParametersMappingError,\n};\nuse crate::producer::{Producer, ProducerId, ProducerOptions};\nuse crate::router::Router;\nuse crate::worker::{Channel, RequestError};\nuse crate::{ortc, uuid_based_wrapper_type};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::HandlerId;\nuse log::warn;\nuse mediasoup_sys::fbs::{response, transport};\nuse mediasoup_types::data_structures::{\n    AppData, BweTraceInfo, RtpPacketTraceInfo, TraceEventDirection,\n};\nuse mediasoup_types::rtp_parameters::{MediaKind, RtpEncodingParameters};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::fmt::Debug;\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse thiserror::Error;\nuse uuid::Uuid;\n\nuuid_based_wrapper_type!(\n    /// Transport identifier.\n    TransportId\n);\n\n/// Data contained in transport trace events.\n///\n/// See also \"trace\" event in the [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging#trace-Event)\n/// section (TypeScript-oriented, but concepts apply here as well).\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(tag = \"type\", rename_all = \"lowercase\")]\npub enum TransportTraceEventData {\n    /// RTP probation packet.\n    Probation {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// RTP packet info.\n        info: RtpPacketTraceInfo,\n    },\n    /// Transport bandwidth estimation changed.\n    Bwe {\n        /// Event timestamp.\n        timestamp: u64,\n        /// Event direction.\n        direction: TraceEventDirection,\n        /// BWE info.\n        info: BweTraceInfo,\n    },\n}\n\nimpl FromFbs for TransportTraceEventData {\n    type FbsType = transport::TraceNotification;\n    fn from_fbs(data: &Self::FbsType) -> Self {\n        match data.type_ {\n            transport::TraceEventType::Probation => unimplemented!(),\n            transport::TraceEventType::Bwe => TransportTraceEventData::Bwe {\n                timestamp: data.timestamp,\n                direction: TraceEventDirection::from_fbs(&data.direction),\n                info: {\n                    let Some(transport::TraceInfo::BweTraceInfo(info)) = &data.info else {\n                        panic!(\"Wrong message from worker: {data:?}\");\n                    };\n\n                    BweTraceInfo::from_fbs(info.as_ref())\n                },\n            },\n        }\n    }\n}\n\n/// Valid types for \"trace\" event.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum TransportTraceEventType {\n    /// RTP probation packet.\n    Probation,\n    /// Transport bandwidth estimation changed.\n    Bwe,\n}\n\nimpl ToFbs for TransportTraceEventType {\n    type FbsType = transport::TraceEventType;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            TransportTraceEventType::Probation => transport::TraceEventType::Probation,\n            TransportTraceEventType::Bwe => transport::TraceEventType::Bwe,\n        }\n    }\n}\n\nimpl FromFbs for TransportTraceEventType {\n    type FbsType = transport::TraceEventType;\n\n    fn from_fbs(event_type: &transport::TraceEventType) -> Self {\n        match event_type {\n            transport::TraceEventType::Probation => TransportTraceEventType::Probation,\n            transport::TraceEventType::Bwe => TransportTraceEventType::Bwe,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RtpListener {\n    /// Vector of mid and producer ID\n    pub mid_table: Vec<(String, ProducerId)>,\n    /// Vector of rid and producer ID\n    pub rid_table: Vec<(String, ProducerId)>,\n    /// Vector of Ssrc and producer ID\n    pub ssrc_table: Vec<(u32, ProducerId)>,\n}\n\nimpl<'a> TryFromFbs<'a> for RtpListener {\n    type FbsType = transport::RtpListener;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(rtp_listener: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            mid_table: rtp_listener\n                .mid_table\n                .iter()\n                .map(|key_value| Ok((key_value.key.to_string(), key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            rid_table: rtp_listener\n                .rid_table\n                .iter()\n                .map(|key_value| Ok((key_value.key.to_string(), key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            ssrc_table: rtp_listener\n                .ssrc_table\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n        })\n    }\n}\n\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct RecvRtpHeaderExtensions {\n    mid: Option<u8>,\n    rid: Option<u8>,\n    rrid: Option<u8>,\n    abs_send_time: Option<u8>,\n    transport_wide_cc01: Option<u8>,\n}\n\nimpl FromFbs for RecvRtpHeaderExtensions {\n    type FbsType = transport::RecvRtpHeaderExtensions;\n\n    fn from_fbs(extensions: &Self::FbsType) -> Self {\n        Self {\n            mid: extensions.mid,\n            rid: extensions.rid,\n            rrid: extensions.rrid,\n            abs_send_time: extensions.abs_send_time,\n            transport_wide_cc01: extensions.transport_wide_cc01,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct SctpListener {\n    /// Vector of stream ID (as string) to data producer ID\n    stream_id_table: Vec<(u16, DataProducerId)>,\n}\n\nimpl<'a> TryFromFbs<'a> for SctpListener {\n    type FbsType = transport::SctpListener;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(listener: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            stream_id_table: listener\n                .stream_id_table\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n        })\n    }\n}\n\n#[derive(Debug, Copy, Clone, PartialEq)]\npub(super) enum TransportType {\n    Direct,\n    Pipe,\n    Plain,\n    WebRtc,\n}\n\n/// A transport connects an endpoint with a mediasoup router and enables transmission of media in\n/// both directions by means of [`Producer`], [`Consumer`], [`DataProducer`] and [`DataConsumer`]\n/// instances created on it.\n///\n/// mediasoup implements the following transports:\n/// * [`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport)\n/// * [`PlainTransport`](crate::plain_transport::PlainTransport)\n/// * [`PipeTransport`](crate::pipe_transport::PipeTransport)\n/// * [`DirectTransport`](crate::direct_transport::DirectTransport)\n///\n/// For additional methods see [`TransportGeneric`].\n#[async_trait]\npub trait Transport: Debug + Send + Sync {\n    /// Transport id.\n    #[must_use]\n    fn id(&self) -> TransportId;\n\n    /// Router to which transport belongs.\n    fn router(&self) -> &Router;\n\n    /// Custom application data.\n    #[must_use]\n    fn app_data(&self) -> &AppData;\n\n    /// Whether the transport is closed.\n    #[must_use]\n    fn closed(&self) -> bool;\n\n    /// Instructs the router to receive audio or video RTP (or SRTP depending on the transport).\n    /// This is the way to inject media into mediasoup.\n    ///\n    /// Transport will be kept alive as long as at least one producer instance is alive.\n    ///\n    /// # Notes on usage\n    /// Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    async fn produce(&self, producer_options: ProducerOptions) -> Result<Producer, ProduceError>;\n\n    /// Instructs the router to send audio or video RTP (or SRTP depending on the transport).\n    /// This is the way to extract media from mediasoup.\n    ///\n    /// Transport will be kept alive as long as at least one consumer instance is alive.\n    ///\n    /// # Notes on usage\n    /// Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n    /// section for more details (TypeScript-oriented, but concepts apply here as well).\n    ///\n    /// When creating a consumer it's recommended to set [`ConsumerOptions::paused`] to `true`, then\n    /// transmit the consumer parameters to the consuming endpoint and, once the consuming endpoint\n    /// has created its local side consumer, unpause the server side consumer using the\n    /// [`Consumer::resume()`] method.\n    ///\n    /// Reasons for create the server side consumer in `paused` mode:\n    /// * If the remote endpoint is a WebRTC browser or application and it receives a RTP packet of\n    ///   the new consumer before the remote [`RTCPeerConnection`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection)\n    ///   is ready to process it (this is, before the remote consumer is created in the remote\n    ///   endpoint) it may happen that the `RTCPeerConnection` will wrongly associate the SSRC of\n    ///   the received packet to an already existing SDP `m=` section, so the imminent creation of\n    ///   the new consumer and its associated `m=` section will fail.\n    ///   * Related [issue](https://github.com/versatica/libmediasoupclient/issues/57).\n    /// * Also, when creating a video consumer, this is an optimization to make it possible for the\n    ///   consuming endpoint to render the video as far as possible. If the server side consumer was\n    ///   created with `paused: false`, mediasoup will immediately request a key frame to the\n    ///   producer and that key frame may reach the consuming endpoint even before it's ready to\n    ///   consume it, generating \"black\" video until the device requests a keyframe by itself.\n    async fn consume(&self, consumer_options: ConsumerOptions) -> Result<Consumer, ConsumeError>;\n\n    /// Instructs the router to receive data messages. Those messages can be delivered by an\n    /// endpoint via SCTP protocol (AKA [`DataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel)\n    /// in WebRTC) or can be directly sent from the Rust application if the transport is a\n    /// [`DirectTransport`](crate::direct_transport::DirectTransport).\n    ///\n    /// Transport will be kept alive as long as at least one data producer instance is alive.\n    async fn produce_data(\n        &self,\n        data_producer_options: DataProducerOptions,\n    ) -> Result<DataProducer, ProduceDataError>;\n\n    /// Instructs the router to send data messages to the endpoint via SCTP protocol (AKA\n    /// [`DataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) in WebRTC)\n    /// or directly to the Rust process if the transport is a\n    /// [`DirectTransport`](crate::direct_transport::DirectTransport).\n    ///\n    /// Transport will be kept alive as long as at least one data consumer instance is alive.\n    async fn consume_data(\n        &self,\n        data_consumer_options: DataConsumerOptions,\n    ) -> Result<DataConsumer, ConsumeDataError>;\n\n    /// Instructs the transport to emit \"trace\" events. For monitoring purposes. Use with caution.\n    async fn enable_trace_event(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError>;\n\n    /// Callback is called when a new producer is created.\n    fn on_new_producer(\n        &self,\n        callback: Arc<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when a new consumer is created.\n    fn on_new_consumer(\n        &self,\n        callback: Arc<dyn Fn(&Consumer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when a new data producer is created.\n    fn on_new_data_producer(\n        &self,\n        callback: Arc<dyn Fn(&DataProducer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when a new data consumer is created.\n    fn on_new_data_consumer(\n        &self,\n        callback: Arc<dyn Fn(&DataConsumer) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// See [`Transport::enable_trace_event()`]\n    fn on_trace(\n        &self,\n        callback: Arc<dyn Fn(&TransportTraceEventData) + Send + Sync + 'static>,\n    ) -> HandlerId;\n\n    /// Callback is called when the router this transport belongs to is closed for whatever reason.\n    /// The transport itself is also closed. `on_transport_close` callbacks are also called on all\n    /// its producers and consumers.\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId;\n\n    /// Callback is called when the router is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if transport is already closed.\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId;\n}\n\n/// Generic transport trait with methods available on all transports in addition to [`Transport`].\n#[async_trait]\npub trait TransportGeneric: Transport + Clone + 'static {\n    /// Dump data structure specific to each transport.\n    #[doc(hidden)]\n    type Dump: Debug + 'static;\n    /// Stats data structure specific to each transport.\n    type Stat: Debug + 'static;\n\n    /// Dump Transport.\n    async fn dump(&self) -> Result<Self::Dump, RequestError>;\n\n    /// Returns current RTC statistics of the transport. Each transport class produces a different\n    /// set of statistics.\n    async fn get_stats(&self) -> Result<Vec<Self::Stat>, RequestError>;\n}\n\n/// Error that caused [`Transport::produce`] to fail.\n#[derive(Debug, Error)]\npub enum ProduceError {\n    /// Producer with the same id already exists.\n    #[error(\"Producer with the same id \\\"{0}\\\" already exists\")]\n    AlreadyExists(ProducerId),\n    /// Incorrect RTP parameters.\n    #[error(\"Incorrect RTP parameters: {0}\")]\n    IncorrectRtpParameters(RtpParametersError),\n    /// RTP mapping error.\n    #[error(\"RTP mapping error: {0}\")]\n    FailedRtpParametersMapping(RtpParametersMappingError),\n    /// Request to worker failed.\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n/// Error that caused [`Transport::consume`] to fail.\n#[derive(Debug, Error)]\npub enum ConsumeError {\n    /// Producer with specified id not found.\n    #[error(\"Producer with id \\\"{0}\\\" not found\")]\n    ProducerNotFound(ProducerId),\n    /// RTP capabilities error.\n    #[error(\"RTP capabilities error: {0}\")]\n    FailedRtpCapabilitiesValidation(RtpCapabilitiesError),\n    /// Bad consumer RTP parameters.\n    #[error(\"Bad consumer RTP parameters: {0}\")]\n    BadConsumerRtpParameters(ConsumerRtpParametersError),\n    /// Request to worker failed.\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n/// Error that caused [`Transport::produce_data`] to fail.\n#[derive(Debug, Error)]\npub enum ProduceDataError {\n    /// Data producer with the same id already exists.\n    #[error(\"Data producer with the same id \\\"{0}\\\" already exists\")]\n    AlreadyExists(DataProducerId),\n    /// SCTP stream parameters are required for this transport.\n    #[error(\"SCTP stream parameters are required for this transport\")]\n    SctpStreamParametersRequired,\n    /// Request to worker failed.\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n/// Error that caused [`Transport::consume_data`] to fail.\n#[derive(Debug, Error)]\npub enum ConsumeDataError {\n    /// Data producer with specified id not found\n    #[error(\"Data producer with id \\\"{0}\\\" not found\")]\n    DataProducerNotFound(DataProducerId),\n    /// No free `sctp_stream_id` available in transport.\n    #[error(\"No free sctp_stream_id available in transport\")]\n    NoSctpStreamId,\n    /// Request to worker failed.\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n#[async_trait]\npub(super) trait TransportImpl: TransportGeneric {\n    fn channel(&self) -> &Channel;\n\n    fn executor(&self) -> &Arc<Executor<'static>>;\n\n    fn next_mid_for_consumers(&self) -> &AtomicUsize;\n\n    fn used_sctp_stream_ids(&self) -> &Mutex<IntMap<u16, bool>>;\n\n    fn cname_for_producers(&self) -> &Mutex<Option<String>>;\n\n    fn allocate_sctp_stream_id(&self) -> Option<u16> {\n        let mut used_sctp_stream_ids = self.used_sctp_stream_ids().lock();\n        // This is simple, but not the fastest implementation, maybe worth improving\n        for (index, used) in used_sctp_stream_ids.iter_mut() {\n            if !*used {\n                *used = true;\n                return Some(*index);\n            }\n        }\n\n        None\n    }\n\n    fn deallocate_sctp_stream_id(&self, sctp_stream_id: u16) {\n        let used_sctp_stream_ids = self.used_sctp_stream_ids();\n        if let Some(used) = used_sctp_stream_ids.lock().get_mut(&sctp_stream_id) {\n            *used = false;\n        }\n    }\n\n    async fn dump_impl(&self) -> Result<response::Body, RequestError> {\n        self.channel()\n            .request(self.id(), TransportDumpRequest {})\n            .await\n    }\n\n    async fn get_stats_impl(&self) -> Result<response::Body, RequestError> {\n        self.channel()\n            .request(self.id(), TransportGetStatsRequest {})\n            .await\n    }\n\n    async fn set_max_incoming_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> {\n        self.channel()\n            .request(self.id(), TransportSetMaxIncomingBitrateRequest { bitrate })\n            .await\n    }\n\n    async fn set_max_outgoing_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> {\n        self.channel()\n            .request(self.id(), TransportSetMaxOutgoingBitrateRequest { bitrate })\n            .await\n    }\n\n    async fn enable_trace_event_impl(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError> {\n        self.channel()\n            .request(self.id(), TransportEnableTraceEventRequest { types })\n            .await\n    }\n\n    async fn set_min_outgoing_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> {\n        self.channel()\n            .request(self.id(), TransportSetMinOutgoingBitrateRequest { bitrate })\n            .await\n    }\n\n    async fn produce_impl(\n        &self,\n        producer_options: ProducerOptions,\n        transport_type: TransportType,\n    ) -> Result<Producer, ProduceError> {\n        if let Some(id) = &producer_options.id {\n            if self.router().has_producer(id) {\n                return Err(ProduceError::AlreadyExists(*id));\n            }\n        }\n\n        let ProducerOptions {\n            id,\n            kind,\n            mut rtp_parameters,\n            paused,\n            key_frame_request_delay,\n            enable_mediasoup_packet_id_header_extension,\n            app_data,\n        } = producer_options;\n\n        ortc::validate_rtp_parameters(&rtp_parameters)\n            .map_err(ProduceError::IncorrectRtpParameters)?;\n\n        if rtp_parameters.encodings.is_empty() {\n            rtp_parameters\n                .encodings\n                .push(RtpEncodingParameters::default());\n        }\n\n        // Don't do this in PipeTransports since there we must keep CNAME value in each Producer.\n        if transport_type != TransportType::Pipe {\n            let mut cname_for_producers = self.cname_for_producers().lock();\n            if let Some(cname_for_producers) = cname_for_producers.as_ref() {\n                rtp_parameters.rtcp.cname = Some(cname_for_producers.clone());\n            } else if let Some(cname) = rtp_parameters.rtcp.cname.as_ref() {\n                // If CNAME is given and we don't have yet a CNAME for Producers in this\n                // Transport, take it.\n                cname_for_producers.replace(cname.clone());\n            } else {\n                // Otherwise if we don't have yet a CNAME for Producers and the RTP parameters\n                // do not include CNAME, create a random one.\n                let cname = Uuid::new_v4().to_string();\n                cname_for_producers.replace(cname.clone());\n\n                // Override Producer's CNAME.\n                rtp_parameters.rtcp.cname = Some(cname);\n            }\n        }\n\n        let router_rtp_capabilities = self.router().rtp_capabilities();\n\n        let rtp_mapping =\n            ortc::get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities)\n                .map_err(ProduceError::FailedRtpParametersMapping)?;\n\n        let consumable_rtp_parameters = ortc::get_consumable_rtp_parameters(\n            kind,\n            &rtp_parameters,\n            &router_rtp_capabilities,\n            &rtp_mapping,\n        );\n\n        let producer_id = id.unwrap_or_else(ProducerId::new);\n\n        let _buffer_guard = self.channel().buffer_messages_for(producer_id.into());\n\n        let response = self\n            .channel()\n            .request(\n                self.id(),\n                TransportProduceRequest {\n                    producer_id,\n                    kind,\n                    rtp_parameters: rtp_parameters.clone(),\n                    rtp_mapping,\n                    key_frame_request_delay,\n                    enable_mediasoup_packet_id_header_extension,\n                    paused,\n                },\n            )\n            .await\n            .map_err(ProduceError::Request)?;\n\n        let producer_fut = Producer::new(\n            producer_id,\n            kind,\n            response.r#type,\n            rtp_parameters,\n            consumable_rtp_parameters,\n            paused,\n            Arc::clone(self.executor()),\n            self.channel().clone(),\n            app_data,\n            Arc::new(self.clone()),\n            transport_type == TransportType::Direct,\n        );\n\n        Ok(producer_fut.await)\n    }\n\n    async fn consume_impl(\n        &self,\n        consumer_options: ConsumerOptions,\n        transport_type: TransportType,\n        rtx: bool,\n    ) -> Result<Consumer, ConsumeError> {\n        let ConsumerOptions {\n            producer_id,\n            rtp_capabilities,\n            paused,\n            mid,\n            preferred_layers,\n            enable_rtx,\n            ignore_dtx,\n            pipe,\n            app_data,\n        } = consumer_options;\n        ortc::validate_rtp_capabilities(&rtp_capabilities)\n            .map_err(ConsumeError::FailedRtpCapabilitiesValidation)?;\n\n        let producer = match self.router().get_producer(&producer_id) {\n            Some(producer) => producer,\n            None => {\n                return Err(ConsumeError::ProducerNotFound(producer_id));\n            }\n        };\n\n        let enable_rtx = enable_rtx.unwrap_or(producer.kind() == MediaKind::Video);\n\n        let rtp_parameters = if transport_type == TransportType::Pipe {\n            ortc::get_pipe_consumer_rtp_parameters(producer.consumable_rtp_parameters(), rtx)\n        } else {\n            let mut rtp_parameters = ortc::get_consumer_rtp_parameters(\n                producer.consumable_rtp_parameters(),\n                &rtp_capabilities,\n                pipe,\n                enable_rtx,\n            )\n            .map_err(ConsumeError::BadConsumerRtpParameters)?;\n\n            if !pipe {\n                // Set MID.\n                rtp_parameters.mid = mid.or_else(|| {\n                    // We use up to 8 bytes for MID (string).\n                    let next_mid_for_consumers = self\n                        .next_mid_for_consumers()\n                        .fetch_add(1, Ordering::Relaxed);\n                    let mid = next_mid_for_consumers % 100_000_000;\n                    Some(format!(\"{mid}\"))\n                })\n            }\n\n            rtp_parameters\n        };\n\n        let consumer_id = ConsumerId::new();\n\n        let r#type = if transport_type == TransportType::Pipe || pipe {\n            ConsumerType::Pipe\n        } else {\n            producer.r#type().into()\n        };\n\n        let _buffer_guard = self.channel().buffer_messages_for(consumer_id.into());\n\n        let response = self\n            .channel()\n            .request(\n                self.id(),\n                TransportConsumeRequest {\n                    consumer_id,\n                    producer_id: producer.id(),\n                    kind: producer.kind(),\n                    rtp_parameters: rtp_parameters.clone(),\n                    r#type,\n                    consumable_rtp_encodings: producer\n                        .consumable_rtp_parameters()\n                        .encodings\n                        .clone(),\n                    paused,\n                    preferred_layers,\n                    ignore_dtx,\n                },\n            )\n            .await\n            .map_err(ConsumeError::Request)?;\n\n        Ok(Consumer::new(\n            consumer_id,\n            producer,\n            r#type,\n            rtp_parameters,\n            response.paused,\n            Arc::clone(self.executor()),\n            self.channel().clone(),\n            response.producer_paused,\n            response.score,\n            response.preferred_layers,\n            app_data,\n            Arc::new(self.clone()),\n        ))\n    }\n\n    async fn produce_data_impl(\n        &self,\n        r#type: DataProducerType,\n        data_producer_options: DataProducerOptions,\n        transport_type: TransportType,\n    ) -> Result<DataProducer, ProduceDataError> {\n        if let Some(id) = &data_producer_options.id {\n            if self.router().has_data_producer(id) {\n                return Err(ProduceDataError::AlreadyExists(*id));\n            }\n        }\n\n        match r#type {\n            DataProducerType::Sctp => {\n                if data_producer_options.sctp_stream_parameters.is_none() {\n                    return Err(ProduceDataError::SctpStreamParametersRequired);\n                }\n            }\n            DataProducerType::Direct => {\n                if data_producer_options.sctp_stream_parameters.is_some() {\n                    warn!(\n                        \"sctp_stream_parameters are ignored when producing data on a DirectTransport\",\n                    );\n                }\n            }\n        }\n\n        let DataProducerOptions {\n            id,\n            sctp_stream_parameters,\n            label,\n            protocol,\n            paused,\n            app_data,\n        } = data_producer_options;\n\n        let data_producer_id = id.unwrap_or_else(DataProducerId::new);\n\n        let _buffer_guard = self.channel().buffer_messages_for(data_producer_id.into());\n\n        let response = self\n            .channel()\n            .request(\n                self.id(),\n                TransportProduceDataRequest {\n                    data_producer_id,\n                    r#type,\n                    sctp_stream_parameters,\n                    label,\n                    protocol,\n                    paused,\n                },\n            )\n            .await\n            .map_err(ProduceDataError::Request)?;\n\n        Ok(DataProducer::new(\n            data_producer_id,\n            response.r#type,\n            response.sctp_stream_parameters,\n            response.label,\n            response.protocol,\n            response.paused,\n            Arc::clone(self.executor()),\n            self.channel().clone(),\n            app_data,\n            Arc::new(self.clone()),\n            transport_type == TransportType::Direct,\n        ))\n    }\n\n    async fn consume_data_impl(\n        &self,\n        r#type: DataConsumerType,\n        data_consumer_options: DataConsumerOptions,\n        transport_type: TransportType,\n    ) -> Result<DataConsumer, ConsumeDataError> {\n        let DataConsumerOptions {\n            data_producer_id,\n            ordered,\n            max_packet_life_time,\n            max_retransmits,\n            paused,\n            subchannels,\n            app_data,\n        } = data_consumer_options;\n\n        let data_producer = match self.router().get_data_producer(&data_producer_id) {\n            Some(data_producer) => data_producer,\n            None => {\n                return Err(ConsumeDataError::DataProducerNotFound(data_producer_id));\n            }\n        };\n\n        let sctp_stream_parameters = match r#type {\n            DataConsumerType::Sctp => {\n                let stream_id = self\n                    .allocate_sctp_stream_id()\n                    .ok_or(ConsumeDataError::NoSctpStreamId)?;\n                let mut sctp_stream_parameters = data_producer.sctp_stream_parameters().map_or(\n                    SctpStreamParameters {\n                        stream_id,\n                        ordered: true,\n                        max_packet_life_time: None,\n                        max_retransmits: None,\n                    },\n                    |mut sctp_parameters| {\n                        sctp_parameters.stream_id = stream_id;\n\n                        sctp_parameters\n                    },\n                );\n                if let Some(ordered) = ordered {\n                    sctp_stream_parameters.ordered = ordered;\n\n                    if ordered {\n                        sctp_stream_parameters.max_packet_life_time = None;\n                        sctp_stream_parameters.max_retransmits = None;\n                    }\n                }\n                if ordered != Some(true) {\n                    if let Some(max_packet_life_time) = max_packet_life_time {\n                        sctp_stream_parameters.ordered = false;\n                        sctp_stream_parameters.max_packet_life_time = Some(max_packet_life_time);\n                    }\n                    if let Some(max_retransmits) = max_retransmits {\n                        sctp_stream_parameters.ordered = false;\n                        sctp_stream_parameters.max_retransmits = Some(max_retransmits);\n                    }\n                }\n\n                Some(sctp_stream_parameters)\n            }\n            DataConsumerType::Direct => {\n                if ordered.is_some() || max_packet_life_time.is_some() || max_retransmits.is_some()\n                {\n                    warn!(\"ordered, max_packet_life_time and max_retransmits are ignored when consuming data on a DirectTransport\");\n                }\n                None\n            }\n        };\n\n        let data_consumer_id = DataConsumerId::new();\n\n        let _buffer_guard = self.channel().buffer_messages_for(data_consumer_id.into());\n\n        let response = self\n            .channel()\n            .request(\n                self.id(),\n                TransportConsumeDataRequest {\n                    data_consumer_id,\n                    data_producer_id: data_producer.id(),\n                    r#type,\n                    sctp_stream_parameters,\n                    label: data_producer.label().clone(),\n                    protocol: data_producer.protocol().clone(),\n                    subchannels,\n                    paused,\n                },\n            )\n            .await\n            .map_err(ConsumeDataError::Request)?;\n\n        let data_consumer = DataConsumer::new(\n            data_consumer_id,\n            response.r#type,\n            response.sctp_stream_parameters,\n            response.label,\n            response.protocol,\n            response.paused,\n            data_producer,\n            Arc::clone(self.executor()),\n            self.channel().clone(),\n            response.data_producer_paused,\n            response.subchannels,\n            app_data,\n            Arc::new(self.clone()),\n            transport_type == TransportType::Direct,\n        );\n\n        if let Some(sctp_stream_parameters) = data_consumer.sctp_stream_parameters() {\n            let stream_id = sctp_stream_parameters.stream_id;\n            let transport = self.clone();\n            data_consumer\n                .on_close(move || {\n                    transport.deallocate_sctp_stream_id(stream_id);\n                })\n                .detach();\n        }\n\n        Ok(data_consumer)\n    }\n}\n"
  },
  {
    "path": "rust/src/router/webrtc_transport/tests.rs",
    "content": "use crate::prelude::WebRtcTransport;\nuse crate::router::{NewTransport, Router, RouterOptions};\nuse crate::transport::Transport;\nuse crate::webrtc_server::{WebRtcServerIpPort, WebRtcServerListenInfos, WebRtcServerOptions};\nuse crate::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions};\nuse crate::worker::{Worker, WorkerSettings};\nuse crate::worker_manager::WorkerManager;\nuse async_io::Timer;\nuse futures_lite::future;\nuse hash_hasher::HashedSet;\nuse mediasoup_types::data_structures::{\n    IceCandidateTcpType, IceCandidateType, IceState, ListenInfo, Protocol,\n};\nuse parking_lot::Mutex;\nuse portpicker::pick_unused_port;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nasync fn init() -> (Worker, Router) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    (worker, router)\n}\n\n#[test]\nfn create_with_webrtc_server_succeeds() {\n    future::block_on(async move {\n        let (worker, router) = init().await;\n\n        let port1 = pick_unused_port().unwrap();\n        let port2 = pick_unused_port().unwrap();\n\n        let webrtc_server = worker\n            .create_webrtc_server({\n                let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port1),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let listen_infos = listen_infos.insert(ListenInfo {\n                    protocol: Protocol::Tcp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port2),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                WebRtcServerOptions::new(listen_infos)\n            })\n            .await\n            .expect(\"Failed to create WebRTC server\");\n\n        let (new_server_transport_tx, new_server_transport_rx) =\n            async_oneshot::oneshot::<WebRtcTransport>();\n        let _handler = webrtc_server.on_new_webrtc_transport({\n            let new_server_transport_tx = Arc::new(Mutex::new(Some(new_server_transport_tx)));\n\n            move |transport| {\n                let _ = new_server_transport_tx\n                    .lock()\n                    .take()\n                    .unwrap()\n                    .send(transport.clone());\n            }\n        });\n\n        let (new_router_transport_tx, new_router_transport_rx) =\n            async_oneshot::oneshot::<WebRtcTransport>();\n        let _handler = router.on_new_transport({\n            let new_router_transport_tx = Arc::new(Mutex::new(Some(new_router_transport_tx)));\n\n            move |transport| {\n                if let NewTransport::WebRtc(transport) = transport {\n                    let _ = new_router_transport_tx\n                        .lock()\n                        .take()\n                        .unwrap()\n                        .send(transport.clone());\n                }\n            }\n        });\n\n        let transport = router\n            .create_webrtc_transport({\n                let mut webrtc_transport_options =\n                    WebRtcTransportOptions::new_with_server(webrtc_server.clone());\n                // Let's disable UDP so resulting ICE candidates should only contain TCP.\n                webrtc_transport_options.enable_udp = false;\n\n                webrtc_transport_options\n            })\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let router_dump = router.dump().await.expect(\"Failed to dump router\");\n        assert_eq!(router_dump.transport_ids, {\n            let mut set = HashedSet::default();\n            set.insert(transport.id());\n            set\n        });\n\n        assert_eq!(new_server_transport_rx.await.unwrap().id(), transport.id());\n        assert_eq!(new_router_transport_rx.await.unwrap().id(), transport.id());\n\n        assert!(!transport.closed());\n\n        {\n            let ice_candidates = transport.ice_candidates();\n            assert_eq!(ice_candidates.len(), 1);\n            assert_eq!(ice_candidates[0].address, \"127.0.0.1\");\n            assert_eq!(ice_candidates[0].protocol, Protocol::Tcp);\n            assert_eq!(ice_candidates[0].port, port2);\n            assert_eq!(ice_candidates[0].r#type, IceCandidateType::Host);\n            assert_eq!(\n                ice_candidates[0].tcp_type,\n                Some(IceCandidateTcpType::Passive)\n            );\n        }\n\n        assert_eq!(transport.ice_state(), IceState::New);\n        assert_eq!(transport.ice_selected_tuple(), None);\n\n        {\n            let webrtc_server_dump = webrtc_server\n                .dump()\n                .await\n                .expect(\"Failed to dump WebRTC server\");\n\n            assert_eq!(webrtc_server_dump.id, webrtc_server.id());\n            assert_eq!(\n                webrtc_server_dump.udp_sockets,\n                vec![WebRtcServerIpPort {\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    port: port1\n                }]\n            );\n            assert_eq!(\n                webrtc_server_dump.tcp_servers,\n                vec![WebRtcServerIpPort {\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    port: port2\n                }]\n            );\n            assert_eq!(webrtc_server_dump.webrtc_transport_ids, {\n                let mut set = HashedSet::default();\n                set.insert(transport.id());\n                set\n            });\n            assert_eq!(webrtc_server_dump.local_ice_username_fragments.len(), 1);\n            assert_eq!(\n                webrtc_server_dump.local_ice_username_fragments[0].webrtc_transport_id,\n                transport.id()\n            );\n            assert_eq!(webrtc_server_dump.tuple_hashes, vec![]);\n        }\n\n        {\n            let (mut tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = transport.on_close(Box::new(move || {\n                let _ = tx.send(());\n            }));\n            drop(transport);\n\n            rx.await.expect(\"Failed to receive close event\");\n        }\n\n        // Drop is async, give consumer a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        {\n            let webrtc_server_dump = webrtc_server\n                .dump()\n                .await\n                .expect(\"Failed to dump WebRTC server\");\n\n            assert_eq!(webrtc_server_dump.id, webrtc_server.id());\n            assert_eq!(\n                webrtc_server_dump.udp_sockets,\n                vec![WebRtcServerIpPort {\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    port: port1\n                }]\n            );\n            assert_eq!(\n                webrtc_server_dump.tcp_servers,\n                vec![WebRtcServerIpPort {\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    port: port2\n                }]\n            );\n            assert_eq!(\n                webrtc_server_dump.webrtc_transport_ids,\n                HashedSet::default()\n            );\n            assert_eq!(webrtc_server_dump.local_ice_username_fragments, vec![]);\n            assert_eq!(webrtc_server_dump.tuple_hashes, vec![]);\n        }\n    });\n}\n\n#[test]\nfn router_close_event() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_router_close(Box::new(move || {\n            let _ = router_close_tx.send(());\n        }));\n\n        router.close();\n\n        router_close_rx\n            .await\n            .expect(\"Failed to receive router_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n    });\n}\n\n#[test]\nfn webrtc_server_close_event() {\n    future::block_on(async move {\n        let (worker, router) = init().await;\n\n        let port1 = pick_unused_port().unwrap();\n        let port2 = pick_unused_port().unwrap();\n\n        let webrtc_server = worker\n            .create_webrtc_server({\n                let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port1),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let listen_infos = listen_infos.insert(ListenInfo {\n                    protocol: Protocol::Tcp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port2),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                WebRtcServerOptions::new(listen_infos)\n            })\n            .await\n            .expect(\"Failed to create WebRTC server\");\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new_with_server(\n                webrtc_server.clone(),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        let (mut webrtc_server_close_tx, webrtc_server_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_webrtc_server_close(Box::new(move || {\n            let _ = webrtc_server_close_tx.send(());\n        }));\n\n        webrtc_server.close();\n\n        webrtc_server_close_rx\n            .await\n            .expect(\"Failed to receive webrtc_server_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/src/router/webrtc_transport.rs",
    "content": "#[cfg(test)]\nmod tests;\n\nuse crate::consumer::{Consumer, ConsumerId, ConsumerOptions};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType};\nuse crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType};\nuse crate::fbs::{FromFbs, TryFromFbs};\nuse crate::messages::{\n    TransportCloseRequest, TransportRestartIceRequest, WebRtcTransportConnectRequest,\n    WebRtcTransportData,\n};\nuse crate::producer::{Producer, ProducerId, ProducerOptions};\nuse crate::router::transport::{TransportImpl, TransportType};\nuse crate::router::Router;\nuse crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions,\n    RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData,\n    TransportTraceEventType,\n};\nuse crate::webrtc_server::WebRtcServer;\nuse crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler};\nuse async_executor::Executor;\nuse async_trait::async_trait;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse log::{debug, error};\nuse mediasoup_sys::fbs::{notification, response, transport, web_rtc_transport};\nuse mediasoup_types::data_structures::{\n    AppData, DtlsParameters, DtlsState, IceCandidate, IceParameters, IceRole, IceState, ListenInfo,\n    SctpState, TransportTuple,\n};\nuse mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters};\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::convert::TryFrom;\nuse std::error::Error;\nuse std::fmt;\nuse std::ops::Deref;\nuse std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\nuse std::sync::{Arc, Weak};\nuse thiserror::Error;\n\n/// Struct that protects an invariant of having non-empty list of listen IPs\n#[derive(Debug, Clone, Eq, PartialEq, Serialize)]\npub struct WebRtcTransportListenInfos(Vec<ListenInfo>);\n\nimpl WebRtcTransportListenInfos {\n    /// Create transport listen IPs with given IP populated initially.\n    #[must_use]\n    pub fn new(listen_info: ListenInfo) -> Self {\n        Self(vec![listen_info])\n    }\n\n    /// Insert another listen IP.\n    #[must_use]\n    pub fn insert(mut self, listen_info: ListenInfo) -> Self {\n        self.0.push(listen_info);\n        self\n    }\n}\n\nimpl Deref for WebRtcTransportListenInfos {\n    type Target = Vec<ListenInfo>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n/// Empty list of listen IPs provided, should have at least one element\n#[derive(Error, Debug, Eq, PartialEq)]\n#[error(\"Empty list of listen IPs provided, should have at least one element\")]\npub struct EmptyListError;\n\nimpl TryFrom<Vec<ListenInfo>> for WebRtcTransportListenInfos {\n    type Error = EmptyListError;\n\n    fn try_from(listen_infos: Vec<ListenInfo>) -> Result<Self, Self::Error> {\n        if listen_infos.is_empty() {\n            Err(EmptyListError)\n        } else {\n            Ok(Self(listen_infos))\n        }\n    }\n}\n\n/// How [`WebRtcTransport`] should listen on interfaces.\n///\n/// # Notes on usage\n/// * Do not use \"0.0.0.0\" into `listen_infos`. Values in `listen_infos` must be specific bindable IPs\n///   on the host.\n/// * If you use \"0.0.0.0\" or \"::\" into `listen_infos`, then you need to also provide\n///   `announced_address` in the corresponding entry in `listen_infos`.\n#[derive(Debug, Clone)]\npub enum WebRtcTransportListen {\n    /// Listen on individual protocol/IP/port combinations specific to this transport.\n    Individual {\n        /// Listening infos in order of preference (first one is the preferred one).\n        listen_infos: WebRtcTransportListenInfos,\n    },\n    /// Share [`WebRtcServer`] with other transports withing the same worker.\n    Server {\n        /// [`WebRtcServer`] to use.\n        webrtc_server: WebRtcServer,\n    },\n}\n\n/// [`WebRtcTransport`] options.\n///\n/// # Notes on usage\n/// * `initial_available_outgoing_bitrate` is just applied when the consumer endpoint supports REMB\n///   or Transport-CC.\n#[derive(Debug, Clone)]\n#[non_exhaustive]\npub struct WebRtcTransportOptions {\n    /// How [`WebRtcTransport`] should listen on interfaces.\n    pub listen: WebRtcTransportListen,\n    /// Initial available outgoing bitrate (in bps).\n    /// Default 600000.\n    pub initial_available_outgoing_bitrate: u32,\n    /// Enable UDP.\n    /// Default true.\n    pub enable_udp: bool,\n    /// Enable TCP.\n    /// Default true if webrtc_server is given, false otherwise.\n    pub enable_tcp: bool,\n    /// Prefer UDP.\n    /// Default false.\n    pub prefer_udp: bool,\n    /// Prefer TCP.\n    /// Default false.\n    pub prefer_tcp: bool,\n    /// ICE consent timeout (in seconds). If 0 it is disabled.\n    /// Default 30.\n    pub ice_consent_timeout: u8,\n    /// Create a SCTP association.\n    /// Default false.\n    pub enable_sctp: bool,\n    /// SCTP streams number.\n    pub num_sctp_streams: NumSctpStreams,\n    /// Maximum allowed size for SCTP messages sent by DataProducers.\n    // \tDefault 262144.\n    pub max_sctp_message_size: u32,\n    /// Maximum SCTP send buffer used by DataConsumers.\n    /// Default 262144.\n    pub sctp_send_buffer_size: u32,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl WebRtcTransportOptions {\n    /// Create [`WebRtcTransport`] options with given listen infos.\n    #[must_use]\n    pub fn new(listen_infos: WebRtcTransportListenInfos) -> Self {\n        Self {\n            listen: WebRtcTransportListen::Individual { listen_infos },\n            initial_available_outgoing_bitrate: 600_000,\n            enable_udp: true,\n            enable_tcp: false,\n            prefer_udp: false,\n            prefer_tcp: false,\n            ice_consent_timeout: 30,\n            enable_sctp: false,\n            num_sctp_streams: NumSctpStreams::default(),\n            max_sctp_message_size: 262_144,\n            sctp_send_buffer_size: 262_144,\n            app_data: AppData::default(),\n        }\n    }\n    /// Create [`WebRtcTransport`] options with given [`WebRtcServer`].\n    #[must_use]\n    pub fn new_with_server(webrtc_server: WebRtcServer) -> Self {\n        Self {\n            listen: WebRtcTransportListen::Server { webrtc_server },\n            initial_available_outgoing_bitrate: 600_000,\n            enable_udp: true,\n            enable_tcp: true,\n            prefer_udp: false,\n            prefer_tcp: false,\n            ice_consent_timeout: 30,\n            enable_sctp: false,\n            num_sctp_streams: NumSctpStreams::default(),\n            max_sctp_message_size: 262_144,\n            sctp_send_buffer_size: 262_144,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct WebRtcTransportDump {\n    // Common to all Transports.\n    pub id: TransportId,\n    pub direct: bool,\n    pub producer_ids: Vec<ProducerId>,\n    pub consumer_ids: Vec<ConsumerId>,\n    pub map_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub map_rtx_ssrc_consumer_id: IntMap<u32, ConsumerId>,\n    pub data_producer_ids: Vec<DataProducerId>,\n    pub data_consumer_ids: Vec<DataConsumerId>,\n    pub recv_rtp_header_extensions: RecvRtpHeaderExtensions,\n    pub rtp_listener: RtpListener,\n    pub max_message_size: u32,\n    pub sctp_parameters: Option<SctpParameters>,\n    pub sctp_state: Option<SctpState>,\n    pub sctp_listener: Option<SctpListener>,\n    pub trace_event_types: Vec<TransportTraceEventType>,\n    // WebRtcTransport specific.\n    pub dtls_parameters: DtlsParameters,\n    pub dtls_state: DtlsState,\n    pub ice_candidates: Vec<IceCandidate>,\n    pub ice_parameters: IceParameters,\n    pub ice_role: IceRole,\n    pub ice_state: IceState,\n    pub ice_selected_tuple: Option<TransportTuple>,\n}\n\nimpl<'a> TryFromFbs<'a> for WebRtcTransportDump {\n    type FbsType = web_rtc_transport::DumpResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(dump: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            // Common to all Transports.\n            id: dump.base.id.parse()?,\n            direct: false,\n            producer_ids: dump\n                .base\n                .producer_ids\n                .iter()\n                .map(|producer_id| Ok(producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            consumer_ids: dump\n                .base\n                .consumer_ids\n                .iter()\n                .map(|consumer_id| Ok(consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_ssrc_consumer_id: dump\n                .base\n                .map_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            map_rtx_ssrc_consumer_id: dump\n                .base\n                .map_rtx_ssrc_consumer_id\n                .iter()\n                .map(|key_value| Ok((key_value.key, key_value.value.parse()?)))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_producer_ids: dump\n                .base\n                .data_producer_ids\n                .iter()\n                .map(|data_producer_id| Ok(data_producer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            data_consumer_ids: dump\n                .base\n                .data_consumer_ids\n                .iter()\n                .map(|data_consumer_id| Ok(data_consumer_id.parse()?))\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs(\n                dump.base.recv_rtp_header_extensions.as_ref(),\n            ),\n            rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?,\n            max_message_size: dump.base.max_message_size,\n            sctp_parameters: dump\n                .base\n                .sctp_parameters\n                .as_ref()\n                .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())),\n            sctp_state: FromFbs::from_fbs(&dump.base.sctp_state),\n            sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| {\n                SctpListener::try_from_fbs(listener.as_ref().clone())\n                    .expect(\"Error parsing SctpListner\")\n            }),\n            trace_event_types: dump\n                .base\n                .trace_event_types\n                .iter()\n                .map(TransportTraceEventType::from_fbs)\n                .collect(),\n            // WebRtcTransport specific.\n            dtls_parameters: DtlsParameters::from_fbs(dump.dtls_parameters.as_ref()),\n            dtls_state: DtlsState::from_fbs(&dump.dtls_state),\n            ice_candidates: dump\n                .ice_candidates\n                .iter()\n                .map(IceCandidate::from_fbs)\n                .collect(),\n            ice_parameters: IceParameters::from_fbs(dump.ice_parameters.as_ref()),\n            ice_role: IceRole::from_fbs(&dump.ice_role),\n            ice_state: IceState::from_fbs(&dump.ice_state),\n            ice_selected_tuple: dump\n                .ice_selected_tuple\n                .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n        })\n    }\n}\n\n/// RTC statistics of the [`WebRtcTransport`].\n#[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\n#[allow(missing_docs)]\npub struct WebRtcTransportStat {\n    // Common to all Transports.\n    // `type` field is present in worker, but ignored here\n    pub transport_id: TransportId,\n    pub timestamp: u64,\n    pub sctp_state: Option<SctpState>,\n    pub bytes_received: u64,\n    pub recv_bitrate: u32,\n    pub bytes_sent: u64,\n    pub send_bitrate: u32,\n    pub rtp_bytes_received: u64,\n    pub rtp_recv_bitrate: u32,\n    pub rtp_bytes_sent: u64,\n    pub rtp_send_bitrate: u32,\n    pub rtx_bytes_received: u64,\n    pub rtx_recv_bitrate: u32,\n    pub rtx_bytes_sent: u64,\n    pub rtx_send_bitrate: u32,\n    pub probation_bytes_sent: u64,\n    pub probation_send_bitrate: u32,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub available_incoming_bitrate: Option<u32>,\n    pub max_incoming_bitrate: Option<u32>,\n    pub max_outgoing_bitrate: Option<u32>,\n    pub min_outgoing_bitrate: Option<u32>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_received: Option<f64>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtp_packet_loss_sent: Option<f64>,\n    // WebRtcTransport specific.\n    pub ice_role: IceRole,\n    pub ice_state: IceState,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ice_selected_tuple: Option<TransportTuple>,\n    pub dtls_state: DtlsState,\n}\n\nimpl<'a> TryFromFbs<'a> for WebRtcTransportStat {\n    type FbsType = web_rtc_transport::GetStatsResponse;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(stats: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            transport_id: stats.base.transport_id.parse()?,\n            timestamp: stats.base.timestamp,\n            sctp_state: FromFbs::from_fbs(&stats.base.sctp_state),\n            bytes_received: stats.base.bytes_received,\n            recv_bitrate: stats.base.recv_bitrate,\n            bytes_sent: stats.base.bytes_sent,\n            send_bitrate: stats.base.send_bitrate,\n            rtp_bytes_received: stats.base.rtp_bytes_received,\n            rtp_recv_bitrate: stats.base.rtp_recv_bitrate,\n            rtp_bytes_sent: stats.base.rtp_bytes_sent,\n            rtp_send_bitrate: stats.base.rtp_send_bitrate,\n            rtx_bytes_received: stats.base.rtx_bytes_received,\n            rtx_recv_bitrate: stats.base.rtx_recv_bitrate,\n            rtx_bytes_sent: stats.base.rtx_bytes_sent,\n            rtx_send_bitrate: stats.base.rtx_send_bitrate,\n            probation_bytes_sent: stats.base.probation_bytes_sent,\n            probation_send_bitrate: stats.base.probation_send_bitrate,\n            available_outgoing_bitrate: stats.base.available_outgoing_bitrate,\n            available_incoming_bitrate: stats.base.available_incoming_bitrate,\n            max_incoming_bitrate: stats.base.max_incoming_bitrate,\n            max_outgoing_bitrate: stats.base.max_outgoing_bitrate,\n            min_outgoing_bitrate: stats.base.min_outgoing_bitrate,\n            rtp_packet_loss_received: stats.base.rtp_packet_loss_received,\n            rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent,\n            // WebRtcTransport specific.\n            ice_role: IceRole::from_fbs(&stats.ice_role),\n            ice_state: IceState::from_fbs(&stats.ice_state),\n            ice_selected_tuple: stats\n                .ice_selected_tuple\n                .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())),\n            dtls_state: DtlsState::from_fbs(&stats.dtls_state),\n        })\n    }\n}\n/// Remote parameters for [`WebRtcTransport`].\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct WebRtcTransportRemoteParameters {\n    /// Remote DTLS parameters.\n    pub dtls_parameters: DtlsParameters,\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_producer: Bag<Arc<dyn Fn(&Producer) + Send + Sync>, Producer>,\n    new_consumer: Bag<Arc<dyn Fn(&Consumer) + Send + Sync>, Consumer>,\n    new_data_producer: Bag<Arc<dyn Fn(&DataProducer) + Send + Sync>, DataProducer>,\n    new_data_consumer: Bag<Arc<dyn Fn(&DataConsumer) + Send + Sync>, DataConsumer>,\n    ice_state_change: Bag<Arc<dyn Fn(IceState) + Send + Sync>>,\n    ice_selected_tuple_change: Bag<Arc<dyn Fn(&TransportTuple) + Send + Sync>, TransportTuple>,\n    dtls_state_change: Bag<Arc<dyn Fn(DtlsState) + Send + Sync>>,\n    sctp_state_change: Bag<Arc<dyn Fn(SctpState) + Send + Sync>>,\n    trace: Bag<Arc<dyn Fn(&TransportTraceEventData) + Send + Sync>, TransportTraceEventData>,\n    router_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    webrtc_server_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\n#[derive(Debug, Deserialize)]\n#[serde(tag = \"event\", rename_all = \"lowercase\", content = \"data\")]\nenum Notification {\n    #[serde(rename_all = \"camelCase\")]\n    IceStateChange {\n        ice_state: IceState,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    IceSelectedTupleChange {\n        ice_selected_tuple: TransportTuple,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    DtlsStateChange {\n        dtls_state: DtlsState,\n        dtls_remote_cert: Option<String>,\n    },\n    #[serde(rename_all = \"camelCase\")]\n    SctpStateChange {\n        sctp_state: SctpState,\n    },\n    Trace(TransportTraceEventData),\n}\n\nimpl<'a> TryFromFbs<'a> for Notification {\n    type FbsType = notification::NotificationRef<'a>;\n    type Error = NotificationParseError;\n\n    fn try_from_fbs(notification: Self::FbsType) -> Result<Self, Self::Error> {\n        match notification.event().unwrap() {\n            notification::Event::WebrtctransportIceStateChange => {\n                let Ok(Some(notification::BodyRef::WebRtcTransportIceStateChangeNotification(\n                    body,\n                ))) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let ice_state = IceState::from_fbs(&body.ice_state().unwrap());\n\n                Ok(Notification::IceStateChange { ice_state })\n            }\n            notification::Event::WebrtctransportIceSelectedTupleChange => {\n                let Ok(Some(\n                    notification::BodyRef::WebRtcTransportIceSelectedTupleChangeNotification(body),\n                )) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let ice_selected_tuple_fbs =\n                    transport::Tuple::try_from(body.tuple().unwrap()).unwrap();\n                let ice_selected_tuple = TransportTuple::from_fbs(&ice_selected_tuple_fbs);\n\n                Ok(Notification::IceSelectedTupleChange { ice_selected_tuple })\n            }\n            notification::Event::WebrtctransportDtlsStateChange => {\n                let Ok(Some(notification::BodyRef::WebRtcTransportDtlsStateChangeNotification(\n                    body,\n                ))) = notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let dtls_state = DtlsState::from_fbs(&body.dtls_state().unwrap());\n\n                Ok(Notification::DtlsStateChange {\n                    dtls_state,\n                    dtls_remote_cert: None,\n                })\n            }\n            notification::Event::TransportSctpStateChange => {\n                let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap());\n\n                Ok(Notification::SctpStateChange { sctp_state })\n            }\n            notification::Event::TransportTrace => {\n                let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) =\n                    notification.body()\n                else {\n                    panic!(\"Wrong message from worker: {notification:?}\");\n                };\n\n                let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap();\n                let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs);\n\n                Ok(Notification::Trace(trace_notification))\n            }\n            _ => Err(NotificationParseError::InvalidEvent),\n        }\n    }\n}\n\nstruct Inner {\n    id: TransportId,\n    next_mid_for_consumers: AtomicUsize,\n    used_sctp_stream_ids: Mutex<IntMap<u16, bool>>,\n    cname_for_producers: Mutex<Option<String>>,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    data: Arc<WebRtcTransportData>,\n    app_data: AppData,\n    // Make sure WebRTC server is not dropped until this transport is not dropped\n    webrtc_server: Option<WebRtcServer>,\n    // Make sure router is not dropped until this transport is not dropped\n    router: Router,\n    closed: AtomicBool,\n    // Drop subscription to transport-specific notifications when transport itself is dropped\n    _subscription_handler: Mutex<Option<SubscriptionHandler>>,\n    _on_webrtc_server_close_handler: Mutex<Option<HandlerId>>,\n    _on_router_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close(true);\n    }\n}\n\nimpl Inner {\n    fn close(&self, close_request: bool) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            debug!(\"close()\");\n\n            self.handlers.close.call_simple();\n\n            if close_request {\n                let channel = self.channel.clone();\n                let router_id = self.router.id();\n                let request = TransportCloseRequest {\n                    transport_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(router_id, request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"transport closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"transport closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A [`WebRtcTransport`] represents a network path negotiated by both, a WebRTC endpoint and\n/// mediasoup, via ICE and DTLS procedures. A [`WebRtcTransport`] may be used to receive media, to\n/// send media or to both receive and send. There is no limitation in mediasoup. However, due to\n/// their design, mediasoup-client and libmediasoupclient require separate [`WebRtcTransport`]s for\n/// sending and receiving.\n///\n/// # Notes on usage\n/// The [`WebRtcTransport`] implementation of mediasoup is\n/// [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate\n/// ICE connections but expects ICE Binding Requests from endpoints.\n#[derive(Clone)]\n#[must_use = \"Transport will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct WebRtcTransport {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for WebRtcTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WebRtcTransport\")\n            .field(\"id\", &self.inner.id)\n            .field(\"next_mid_for_consumers\", &self.inner.next_mid_for_consumers)\n            .field(\"used_sctp_stream_ids\", &self.inner.used_sctp_stream_ids)\n            .field(\"cname_for_producers\", &self.inner.cname_for_producers)\n            .field(\"router\", &self.inner.router)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\n#[async_trait]\nimpl Transport for WebRtcTransport {\n    fn id(&self) -> TransportId {\n        self.inner.id\n    }\n\n    fn router(&self) -> &Router {\n        &self.inner.router\n    }\n\n    fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    async fn produce(&self, producer_options: ProducerOptions) -> Result<Producer, ProduceError> {\n        debug!(\"produce()\");\n\n        let producer = self\n            .produce_impl(producer_options, TransportType::WebRtc)\n            .await?;\n\n        self.inner.handlers.new_producer.call_simple(&producer);\n\n        Ok(producer)\n    }\n\n    async fn consume(&self, consumer_options: ConsumerOptions) -> Result<Consumer, ConsumeError> {\n        debug!(\"consume()\");\n\n        let consumer = self\n            .consume_impl(consumer_options, TransportType::WebRtc, false)\n            .await?;\n\n        self.inner.handlers.new_consumer.call_simple(&consumer);\n\n        Ok(consumer)\n    }\n\n    async fn produce_data(\n        &self,\n        data_producer_options: DataProducerOptions,\n    ) -> Result<DataProducer, ProduceDataError> {\n        debug!(\"produce_data()\");\n\n        let data_producer = self\n            .produce_data_impl(\n                DataProducerType::Sctp,\n                data_producer_options,\n                TransportType::WebRtc,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_producer\n            .call_simple(&data_producer);\n\n        Ok(data_producer)\n    }\n\n    async fn consume_data(\n        &self,\n        data_consumer_options: DataConsumerOptions,\n    ) -> Result<DataConsumer, ConsumeDataError> {\n        debug!(\"consume_data()\");\n\n        let data_consumer = self\n            .consume_data_impl(\n                DataConsumerType::Sctp,\n                data_consumer_options,\n                TransportType::WebRtc,\n            )\n            .await?;\n\n        self.inner\n            .handlers\n            .new_data_consumer\n            .call_simple(&data_consumer);\n\n        Ok(data_consumer)\n    }\n\n    async fn enable_trace_event(\n        &self,\n        types: Vec<TransportTraceEventType>,\n    ) -> Result<(), RequestError> {\n        debug!(\"enable_trace_event()\");\n\n        self.enable_trace_event_impl(types).await\n    }\n\n    fn on_new_producer(\n        &self,\n        callback: Arc<dyn Fn(&Producer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_producer.add(callback)\n    }\n\n    fn on_new_consumer(\n        &self,\n        callback: Arc<dyn Fn(&Consumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_consumer.add(callback)\n    }\n\n    fn on_new_data_producer(\n        &self,\n        callback: Arc<dyn Fn(&DataProducer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_producer.add(callback)\n    }\n\n    fn on_new_data_consumer(\n        &self,\n        callback: Arc<dyn Fn(&DataConsumer) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.new_data_consumer.add(callback)\n    }\n\n    fn on_trace(\n        &self,\n        callback: Arc<dyn Fn(&TransportTraceEventData) + Send + Sync + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.trace.add(callback)\n    }\n\n    fn on_router_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        self.inner.handlers.router_close.add(callback)\n    }\n\n    fn on_close(&self, callback: Box<dyn FnOnce() + Send + 'static>) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n}\n\n#[async_trait]\nimpl TransportGeneric for WebRtcTransport {\n    type Dump = WebRtcTransportDump;\n    type Stat = WebRtcTransportStat;\n\n    #[doc(hidden)]\n    async fn dump(&self) -> Result<Self::Dump, RequestError> {\n        debug!(\"dump()\");\n\n        let response = self.dump_impl().await?;\n\n        if let response::Body::WebRtcTransportDumpResponse(data) = response {\n            Ok(WebRtcTransportDump::try_from_fbs(*data)\n                .expect(\"Error parsing dump response: {response:?}\"))\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n\n    async fn get_stats(&self) -> Result<Vec<Self::Stat>, RequestError> {\n        debug!(\"get_stats()\");\n\n        let response = self.get_stats_impl().await?;\n\n        if let response::Body::WebRtcTransportGetStatsResponse(data) = response {\n            Ok(vec![WebRtcTransportStat::try_from_fbs(\n                data.as_ref().clone(),\n            )\n            .expect(\"Error parsing dump response: {response:?}\")])\n        } else {\n            panic!(\"Wrong message from worker: {response:?}\");\n        }\n    }\n}\n\nimpl TransportImpl for WebRtcTransport {\n    fn channel(&self) -> &Channel {\n        &self.inner.channel\n    }\n\n    fn executor(&self) -> &Arc<Executor<'static>> {\n        &self.inner.executor\n    }\n\n    fn next_mid_for_consumers(&self) -> &AtomicUsize {\n        &self.inner.next_mid_for_consumers\n    }\n\n    fn used_sctp_stream_ids(&self) -> &Mutex<IntMap<u16, bool>> {\n        &self.inner.used_sctp_stream_ids\n    }\n\n    fn cname_for_producers(&self) -> &Mutex<Option<String>> {\n        &self.inner.cname_for_producers\n    }\n}\n\nimpl WebRtcTransport {\n    #[allow(clippy::too_many_arguments)]\n    pub(super) fn new(\n        id: TransportId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        data: WebRtcTransportData,\n        app_data: AppData,\n        router: Router,\n        webrtc_server: Option<WebRtcServer>,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let handlers = Arc::<Handlers>::default();\n        let data = Arc::new(data);\n\n        let subscription_handler = {\n            let handlers = Arc::clone(&handlers);\n            let data = Arc::clone(&data);\n\n            channel.subscribe_to_notifications(id.into(), move |notification| {\n                match Notification::try_from_fbs(notification) {\n                    Ok(notification) => match notification {\n                        Notification::IceStateChange { ice_state } => {\n                            *data.ice_state.lock() = ice_state;\n                            handlers.ice_state_change.call(|callback| {\n                                callback(ice_state);\n                            });\n                        }\n                        Notification::IceSelectedTupleChange { ice_selected_tuple } => {\n                            data.ice_selected_tuple\n                                .lock()\n                                .replace(ice_selected_tuple.clone());\n                            handlers\n                                .ice_selected_tuple_change\n                                .call_simple(&ice_selected_tuple);\n                        }\n                        Notification::DtlsStateChange {\n                            dtls_state,\n                            dtls_remote_cert,\n                        } => {\n                            *data.dtls_state.lock() = dtls_state;\n\n                            if let Some(dtls_remote_cert) = dtls_remote_cert {\n                                data.dtls_remote_cert.lock().replace(dtls_remote_cert);\n                            }\n\n                            handlers.dtls_state_change.call(|callback| {\n                                callback(dtls_state);\n                            });\n                        }\n                        Notification::SctpStateChange { sctp_state } => {\n                            data.sctp_state.lock().replace(sctp_state);\n\n                            handlers.sctp_state_change.call(|callback| {\n                                callback(sctp_state);\n                            });\n                        }\n                        Notification::Trace(trace_event_data) => {\n                            handlers.trace.call_simple(&trace_event_data);\n                        }\n                    },\n                    Err(error) => {\n                        error!(\"Failed to parse notification: {}\", error);\n                    }\n                }\n            })\n        };\n\n        let next_mid_for_consumers = AtomicUsize::default();\n        let used_sctp_stream_ids = Mutex::new({\n            let mut used_used_sctp_stream_ids = IntMap::default();\n            if let Some(sctp_parameters) = &data.sctp_parameters {\n                for i in 0..sctp_parameters.mis {\n                    used_used_sctp_stream_ids.insert(i, false);\n                }\n            }\n            used_used_sctp_stream_ids\n        });\n        let cname_for_producers = Mutex::new(None);\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_webrtc_server_close_handler = webrtc_server.as_ref().map(|webrtc_server| {\n            webrtc_server.on_close({\n                let inner_weak = Arc::clone(&inner_weak);\n\n                move || {\n                    let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                    if let Some(inner) = maybe_inner {\n                        inner.handlers.webrtc_server_close.call_simple();\n                        inner.close(true);\n                    }\n                }\n            })\n        });\n        let on_router_close_handler = router.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.router_close.call_simple();\n                    inner.close(false);\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            next_mid_for_consumers,\n            used_sctp_stream_ids,\n            cname_for_producers,\n            executor,\n            channel,\n            handlers,\n            data,\n            app_data,\n            webrtc_server,\n            router,\n            closed: AtomicBool::new(false),\n            _subscription_handler: Mutex::new(subscription_handler),\n            _on_webrtc_server_close_handler: Mutex::new(on_webrtc_server_close_handler),\n            _on_router_close_handler: Mutex::new(on_router_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        let webrtc_transport = Self { inner };\n\n        // Notify WebRTC server that new transport was created.\n        if let Some(webrtc_server) = &webrtc_transport.inner.webrtc_server {\n            webrtc_server.notify_new_webrtc_transport(&webrtc_transport);\n        }\n\n        webrtc_transport\n    }\n\n    /// Provide the [`WebRtcTransport`] with remote parameters.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup_types::data_structures::{DtlsParameters, DtlsRole, DtlsFingerprint};\n    /// use mediasoup::webrtc_transport::WebRtcTransportRemoteParameters;\n    ///\n    /// # async fn f(\n    /// #     webrtc_transport: mediasoup::webrtc_transport::WebRtcTransport,\n    /// # ) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Calling connect() on a PlainTransport created with comedia and rtcp_mux set.\n    /// webrtc_transport\n    ///     .connect(WebRtcTransportRemoteParameters {\n    ///         dtls_parameters: DtlsParameters {\n    ///             role: DtlsRole::Server,\n    ///             fingerprints: vec![\n    ///                 DtlsFingerprint::Sha256 {\n    ///                     value: [\n    ///                         0xE5, 0xF5, 0xCA, 0xA7, 0x2D, 0x93, 0xE6, 0x16, 0xAC, 0x21, 0x09,\n    ///                         0x9F, 0x23, 0x51, 0x62, 0x8C, 0xD0, 0x66, 0xE9, 0x0C, 0x22, 0x54,\n    ///                         0x2B, 0x82, 0x0C, 0xDF, 0xE0, 0xC5, 0x2C, 0x7E, 0xCD, 0x53,\n    ///                     ],\n    ///                 },\n    ///             ],\n    ///         },\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn connect(\n        &self,\n        remote_parameters: WebRtcTransportRemoteParameters,\n    ) -> Result<(), RequestError> {\n        debug!(\"connect()\");\n\n        let response = self\n            .inner\n            .channel\n            .request(\n                self.id(),\n                WebRtcTransportConnectRequest {\n                    dtls_parameters: remote_parameters.dtls_parameters,\n                },\n            )\n            .await?;\n\n        self.inner.data.dtls_parameters.lock().role = response.dtls_local_role;\n\n        Ok(())\n    }\n\n    /// WebRTC server used during creation of this transport.\n    pub fn webrtc_server(&self) -> &Option<WebRtcServer> {\n        &self.inner.webrtc_server\n    }\n\n    /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this\n    /// transport.\n    pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> {\n        debug!(\"set_max_incoming_bitrate() [bitrate:{}]\", bitrate);\n\n        self.set_max_incoming_bitrate_impl(bitrate).await\n    }\n\n    /// Set maximum outgoing bitrate for media streams sent by the remote endpoint over this\n    /// transport.\n    pub async fn set_max_outgoing_bitrate(&self, bitrate: u32) -> Result<(), RequestError> {\n        debug!(\"set_max_outgoing_bitrate() [bitrate:{}]\", bitrate);\n\n        self.set_max_outgoing_bitrate_impl(bitrate).await\n    }\n\n    /// Set minimum outgoing bitrate for media streams sent by the remote endpoint over this\n    /// transport.\n    pub async fn set_min_outgoing_bitrate(&self, bitrate: u32) -> Result<(), RequestError> {\n        debug!(\"set_min_outgoing_bitrate() [bitrate:{}]\", bitrate);\n\n        self.set_min_outgoing_bitrate_impl(bitrate).await\n    }\n\n    /// Local ICE role. Due to the mediasoup ICE Lite design, this is always `Controlled`.\n    #[must_use]\n    pub fn ice_role(&self) -> IceRole {\n        self.inner.data.ice_role\n    }\n\n    /// Local ICE parameters.\n    #[must_use]\n    pub fn ice_parameters(&self) -> &IceParameters {\n        &self.inner.data.ice_parameters\n    }\n\n    /// Local ICE candidates.\n    #[must_use]\n    pub fn ice_candidates(&self) -> &Vec<IceCandidate> {\n        &self.inner.data.ice_candidates\n    }\n\n    /// Current ICE state.\n    #[must_use]\n    pub fn ice_state(&self) -> IceState {\n        *self.inner.data.ice_state.lock()\n    }\n\n    /// The selected transport tuple if ICE is in `Connected` or `Completed` state. It is `None` if\n    /// ICE is not established (no working candidate pair was found).\n    #[must_use]\n    pub fn ice_selected_tuple(&self) -> Option<TransportTuple> {\n        self.inner.data.ice_selected_tuple.lock().clone()\n    }\n\n    /// Local DTLS parameters.\n    #[must_use]\n    pub fn dtls_parameters(&self) -> DtlsParameters {\n        self.inner.data.dtls_parameters.lock().clone()\n    }\n\n    /// Current DTLS state.\n    #[must_use]\n    pub fn dtls_state(&self) -> DtlsState {\n        *self.inner.data.dtls_state.lock()\n    }\n\n    /// The remote certificate in PEM format. It is `Some` once the DTLS state becomes `Connected`.\n    ///\n    /// # Notes on usage\n    /// The application may want to inspect the remote certificate for authorization purposes by\n    /// using some certificates utility.\n    #[must_use]\n    pub fn dtls_remote_cert(&self) -> Option<String> {\n        self.inner.data.dtls_remote_cert.lock().clone()\n    }\n\n    /// Local SCTP parameters. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_parameters(&self) -> Option<SctpParameters> {\n        self.inner.data.sctp_parameters\n    }\n\n    /// Current SCTP state. Or `None` if SCTP is not enabled.\n    #[must_use]\n    pub fn sctp_state(&self) -> Option<SctpState> {\n        *self.inner.data.sctp_state.lock()\n    }\n\n    /// Restarts the ICE layer by generating new local ICE parameters that must be signaled to the\n    /// remote endpoint.\n    pub async fn restart_ice(&self) -> Result<IceParameters, RequestError> {\n        debug!(\"restart_ice()\");\n\n        self.inner\n            .channel\n            .request(self.id(), TransportRestartIceRequest {})\n            .await\n    }\n\n    /// Callback is called when the WebRTC server used during creation of this transport is closed\n    /// for whatever reason.\n    /// The transport itself is also closed. `on_transport_close` callbacks are also called on all\n    /// its producers and consumers.\n    pub fn on_webrtc_server_close(\n        &self,\n        callback: Box<dyn FnOnce() + Send + 'static>,\n    ) -> HandlerId {\n        self.inner.handlers.webrtc_server_close.add(callback)\n    }\n\n    /// Callback is called when the transport ICE state changes.\n    pub fn on_ice_state_change<F: Fn(IceState) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.ice_state_change.add(Arc::new(callback))\n    }\n\n    /// Callback is called after ICE state becomes `Completed` and when the ICE selected tuple\n    /// changes.\n    pub fn on_ice_selected_tuple_change<F: Fn(&TransportTuple) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .ice_selected_tuple_change\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport DTLS state changes.\n    pub fn on_dtls_state_change<F: Fn(DtlsState) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .dtls_state_change\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when the transport SCTP state changes.\n    pub fn on_sctp_state_change<F: Fn(SctpState) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .sctp_state_change\n            .add(Arc::new(callback))\n    }\n\n    /// Downgrade `WebRtcTransport` to [`WeakWebRtcTransport`] instance.\n    #[must_use]\n    pub fn downgrade(&self) -> WeakWebRtcTransport {\n        WeakWebRtcTransport {\n            inner: Arc::downgrade(&self.inner),\n        }\n    }\n}\n\n/// [`WeakWebRtcTransport`] doesn't own [`WebRtcTransport`] instance on mediasoup-worker and will\n/// not prevent one from being destroyed once last instance of regular [`WebRtcTransport`] is\n/// dropped.\n///\n/// [`WeakWebRtcTransport`] vs [`WebRtcTransport`] is similar to [`Weak`] vs [`Arc`].\n#[derive(Clone)]\npub struct WeakWebRtcTransport {\n    inner: Weak<Inner>,\n}\n\nimpl fmt::Debug for WeakWebRtcTransport {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WeakWebRtcTransport\").finish()\n    }\n}\n\nimpl WeakWebRtcTransport {\n    /// Attempts to upgrade `WeakWebRtcTransport` to [`WebRtcTransport`] if last instance of one\n    /// wasn't dropped yet.\n    #[must_use]\n    pub fn upgrade(&self) -> Option<WebRtcTransport> {\n        let inner = self.inner.upgrade()?;\n\n        Some(WebRtcTransport { inner })\n    }\n}\n"
  },
  {
    "path": "rust/src/router.rs",
    "content": "//! A router enables injection, selection and forwarding of media streams through [`Transport`]\n//! instances created on it.\n//!\n//! Developers may think of a mediasoup router as if it were a \"multi-party conference room\",\n//! although mediasoup is much more low level than that and doesn't constrain itself to specific\n//! high level use cases (for instance, a \"multi-party conference room\" could involve various\n//! mediasoup routers, even in different physicals hosts).\n\npub(super) mod active_speaker_observer;\npub(super) mod audio_level_observer;\npub(super) mod consumer;\npub(super) mod data_consumer;\npub(super) mod data_producer;\npub(super) mod direct_transport;\npub(super) mod pipe_transport;\npub(super) mod plain_transport;\npub(super) mod producer;\npub(super) mod rtp_observer;\n#[cfg(test)]\nmod tests;\npub(super) mod transport;\npub(super) mod webrtc_transport;\n\nuse crate::active_speaker_observer::{ActiveSpeakerObserver, ActiveSpeakerObserverOptions};\nuse crate::audio_level_observer::{AudioLevelObserver, AudioLevelObserverOptions};\nuse crate::consumer::{Consumer, ConsumerId, ConsumerOptions};\nuse crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions};\nuse crate::data_producer::{\n    DataProducer, DataProducerId, DataProducerOptions, NonClosingDataProducer, WeakDataProducer,\n};\nuse crate::direct_transport::{DirectTransport, DirectTransportOptions};\nuse crate::messages::{\n    RouterCloseRequest, RouterCreateActiveSpeakerObserverData,\n    RouterCreateActiveSpeakerObserverRequest, RouterCreateAudioLevelObserverData,\n    RouterCreateAudioLevelObserverRequest, RouterCreateDirectTransportData,\n    RouterCreateDirectTransportRequest, RouterCreatePipeTransportData,\n    RouterCreatePipeTransportRequest, RouterCreatePlainTransportData,\n    RouterCreatePlainTransportRequest, RouterCreateWebRtcTransportRequest,\n    RouterCreateWebRtcTransportWithServerRequest, RouterCreateWebrtcTransportData,\n    RouterDumpRequest,\n};\nuse crate::pipe_transport::{\n    PipeTransport, PipeTransportOptions, PipeTransportRemoteParameters, WeakPipeTransport,\n};\nuse crate::plain_transport::{PlainTransport, PlainTransportOptions};\nuse crate::producer::{PipedProducer, Producer, ProducerId, ProducerOptions, WeakProducer};\nuse crate::rtp_observer::{RtpObserver, RtpObserverId};\nuse crate::transport::{\n    ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, Transport, TransportGeneric,\n    TransportId,\n};\nuse crate::webrtc_transport::{WebRtcTransport, WebRtcTransportListen, WebRtcTransportOptions};\nuse crate::worker::{Channel, RequestError, Worker};\nuse crate::{ortc, uuid_based_wrapper_type};\nuse async_executor::Executor;\nuse async_lock::Mutex as AsyncMutex;\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse log::{debug, error};\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    RtpCapabilities, RtpCapabilitiesFinalized, RtpCodecCapability,\n};\nuse mediasoup_types::sctp_parameters::NumSctpStreams;\nuse parking_lot::{Mutex, RwLock};\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::ops::Deref;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::Mutex as SyncMutex;\nuse std::sync::{Arc, Weak};\nuse thiserror::Error;\n\nuuid_based_wrapper_type!(\n    /// [`Router`] identifier.\n    RouterId\n);\n\n/// [`Router`] options.\n///\n/// # Notes on usage\n///\n/// * Feature codecs such as `RTX` MUST NOT be placed into the mediaCodecs list.\n/// * If `preferred_payload_type` is given in a [`RtpCodecCapability`] (although it's unnecessary)\n///   it's extremely recommended to use a value in the 96-127 range.\n#[derive(Debug)]\n#[non_exhaustive]\npub struct RouterOptions {\n    /// Router media codecs.\n    pub media_codecs: Vec<RtpCodecCapability>,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl RouterOptions {\n    /// Create router options with given list of declared media codecs.\n    #[must_use]\n    pub fn new(media_codecs: Vec<RtpCodecCapability>) -> Self {\n        Self {\n            media_codecs,\n            app_data: AppData::default(),\n        }\n    }\n}\n\nimpl Default for RouterOptions {\n    fn default() -> Self {\n        Self::new(vec![])\n    }\n}\n\n/// Options used for piping media or data producer to into another router on the same host.\n///\n/// # Notes on usage\n/// * SCTP arguments will only apply the first time the underlying transports are created.\n#[derive(Debug)]\npub struct PipeToRouterOptions {\n    /// Target Router instance.\n    pub router: Router,\n    /// Whether the `id` of the returned Producer or DataProducer should be the\n    /// same than the `id` of the original Producer or DataProducer. Default true.\n    ///\n    /// # Note\n    /// If set to true, then the origin router and target router cannot be in the\n    /// same worker.\n    pub keep_id: bool,\n    /// IP used in the PipeTransport pair.\n    ///\n    /// Default `{ protocol: 'udp', ip: '127.0.0.1' }`.\n    listen_info: ListenInfo,\n    /// Create a SCTP association.\n    ///\n    /// Default `true`.\n    pub enable_sctp: bool,\n    /// SCTP streams number.\n    pub num_sctp_streams: NumSctpStreams,\n    /// Enable RTX and NACK for RTP retransmission.\n    ///\n    /// Default `false`.\n    pub enable_rtx: bool,\n    /// Enable SRTP.\n    ///\n    /// Default `false`.\n    pub enable_srtp: bool,\n}\n\nimpl PipeToRouterOptions {\n    /// Crate pipe options for piping into given local router.\n    #[must_use]\n    pub fn new(router: Router) -> Self {\n        Self {\n            router,\n            keep_id: true,\n            listen_info: ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            },\n            enable_sctp: true,\n            num_sctp_streams: NumSctpStreams::default(),\n            enable_rtx: false,\n            enable_srtp: false,\n        }\n    }\n}\n\n/// Container for pipe consumer and pipe producer pair.\n///\n/// # Notes on usage\n/// Pipe consumer and Pipe producer will not be closed on drop, to control this manually get pipe\n/// producer out of non-closing variant with [`PipedProducer::into_inner()`] call,\n/// otherwise pipe consumer and pipe producer lifetime will be tied to source producer lifetime.\n///\n/// Pipe consumer is always tied to the lifetime of pipe producer.\n#[derive(Debug)]\npub struct PipeProducerToRouterPair {\n    /// The Consumer created in the current Router.\n    pub pipe_consumer: Consumer,\n    /// The Producer created in the target Router, get regular instance with\n    /// [`PipedProducer::into_inner()`] call.\n    pub pipe_producer: PipedProducer,\n}\n\n/// Error that caused [`Router::pipe_producer_to_router()`] to fail.\n#[derive(Debug, Error)]\npub enum PipeProducerToRouterError {\n    /// Destination router must be different\n    #[error(\"Destination router must be different\")]\n    SameRouter,\n    /// Producer with specified id not found\n    #[error(\"Producer with id \\\"{0}\\\" not found\")]\n    ProducerNotFound(ProducerId),\n    /// Failed to create or connect Pipe transport\n    #[error(\"Failed to create or connect Pipe transport: \\\"{0}\\\"\")]\n    TransportFailed(RequestError),\n    /// Failed to consume\n    #[error(\"Failed to consume: \\\"{0}\\\"\")]\n    ConsumeFailed(ConsumeError),\n    /// Failed to produce\n    #[error(\"Failed to produce: \\\"{0}\\\"\")]\n    ProduceFailed(ProduceError),\n}\n\nimpl From<RequestError> for PipeProducerToRouterError {\n    fn from(error: RequestError) -> Self {\n        PipeProducerToRouterError::TransportFailed(error)\n    }\n}\n\nimpl From<ConsumeError> for PipeProducerToRouterError {\n    fn from(error: ConsumeError) -> Self {\n        PipeProducerToRouterError::ConsumeFailed(error)\n    }\n}\n\nimpl From<ProduceError> for PipeProducerToRouterError {\n    fn from(error: ProduceError) -> Self {\n        PipeProducerToRouterError::ProduceFailed(error)\n    }\n}\n\n/// Container for pipe data consumer and pipe data producer pair.\n///\n/// # Notes on usage\n/// Pipe data consumer and pipe data producer will not be closed on drop, to control this manually\n/// get pipe data producer out of non-closing variant with [`NonClosingDataProducer::into_inner()`]\n/// call, otherwise pipe data consumer and pipe data producer lifetime will be tied to source data\n/// producer lifetime.\n///\n/// Pipe data consumer is always tied to the lifetime of pipe data producer.\n#[derive(Debug)]\npub struct PipeDataProducerToRouterPair {\n    /// The DataConsumer created in the current Router.\n    pub pipe_data_consumer: DataConsumer,\n    /// The DataProducer created in the target Router, get regular instance with\n    /// [`NonClosingDataProducer::into_inner()`] call.\n    pub pipe_data_producer: NonClosingDataProducer,\n}\n\n/// Error that caused [`Router::pipe_data_producer_to_router()`] to fail.\n#[derive(Debug, Error)]\npub enum PipeDataProducerToRouterError {\n    /// Destination router must be different\n    #[error(\"Destination router must be different\")]\n    SameRouter,\n    /// Data producer with specified id not found\n    #[error(\"Data producer with id \\\"{0}\\\" not found\")]\n    DataProducerNotFound(DataProducerId),\n    /// Failed to create or connect Pipe transport\n    #[error(\"Failed to create or connect Pipe transport: \\\"{0}\\\"\")]\n    TransportFailed(RequestError),\n    /// Failed to consume\n    #[error(\"Failed to consume: \\\"{0}\\\"\")]\n    ConsumeFailed(ConsumeDataError),\n    /// Failed to produce\n    #[error(\"Failed to produce: \\\"{0}\\\"\")]\n    ProduceFailed(ProduceDataError),\n}\n\nimpl From<RequestError> for PipeDataProducerToRouterError {\n    fn from(error: RequestError) -> Self {\n        PipeDataProducerToRouterError::TransportFailed(error)\n    }\n}\n\nimpl From<ConsumeDataError> for PipeDataProducerToRouterError {\n    fn from(error: ConsumeDataError) -> Self {\n        PipeDataProducerToRouterError::ConsumeFailed(error)\n    }\n}\n\nimpl From<ProduceDataError> for PipeDataProducerToRouterError {\n    fn from(error: ProduceDataError) -> Self {\n        PipeDataProducerToRouterError::ProduceFailed(error)\n    }\n}\n\n/// Error that caused [`Router::update_media_codecs`] to fail.\n#[derive(Debug, Error)]\npub enum UpdateMediaCodecsError {\n    /// RTP capabilities generation error\n    #[error(\"RTP capabilities generation error: {0}\")]\n    FailedRtpCapabilitiesGeneration(ortc::RtpCapabilitiesError),\n}\n\n#[derive(Debug, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct RouterDump {\n    pub id: RouterId,\n    pub map_consumer_id_producer_id: HashedMap<ConsumerId, ProducerId>,\n    pub map_data_consumer_id_data_producer_id: HashedMap<DataConsumerId, DataProducerId>,\n    pub map_data_producer_id_data_consumer_ids:\n        HashedMap<DataProducerId, HashedSet<DataConsumerId>>,\n    pub map_producer_id_consumer_ids: HashedMap<ProducerId, HashedSet<ConsumerId>>,\n    pub map_producer_id_observer_ids: HashedMap<ProducerId, HashedSet<RtpObserverId>>,\n    pub rtp_observer_ids: HashedSet<RtpObserverId>,\n    pub transport_ids: HashedSet<TransportId>,\n}\n\n/// New transport that was just created.\n#[derive(Debug)]\npub enum NewTransport<'a> {\n    /// Direct transport\n    Direct(&'a DirectTransport),\n    /// Pipe transport\n    Pipe(&'a PipeTransport),\n    /// Plain transport\n    Plain(&'a PlainTransport),\n    /// WebRtc transport\n    WebRtc(&'a WebRtcTransport),\n}\n\nimpl<'a> Deref for NewTransport<'a> {\n    type Target = dyn Transport;\n\n    fn deref(&self) -> &Self::Target {\n        match self {\n            Self::Direct(transport) => *transport as &Self::Target,\n            Self::Pipe(transport) => *transport as &Self::Target,\n            Self::Plain(transport) => *transport as &Self::Target,\n            Self::WebRtc(transport) => *transport as &Self::Target,\n        }\n    }\n}\n\n/// New RTP observer that was just created.\n#[derive(Debug)]\npub enum NewRtpObserver<'a> {\n    /// Audio level observer\n    AudioLevel(&'a AudioLevelObserver),\n    /// Active speaker observer\n    ActiveSpeaker(&'a ActiveSpeakerObserver),\n}\n\nimpl<'a> Deref for NewRtpObserver<'a> {\n    type Target = dyn RtpObserver;\n\n    fn deref(&self) -> &Self::Target {\n        match self {\n            Self::AudioLevel(observer) => *observer as &Self::Target,\n            Self::ActiveSpeaker(observer) => *observer as &Self::Target,\n        }\n    }\n}\n\nstruct PipeTransportPair {\n    local: PipeTransport,\n    remote: PipeTransport,\n}\n\nimpl PipeTransportPair {\n    fn downgrade(&self) -> WeakPipeTransportPair {\n        WeakPipeTransportPair {\n            local: self.local.downgrade(),\n            remote: self.remote.downgrade(),\n        }\n    }\n}\n\n#[derive(Debug)]\nstruct WeakPipeTransportPair {\n    local: WeakPipeTransport,\n    remote: WeakPipeTransport,\n}\n\nimpl WeakPipeTransportPair {\n    fn upgrade(&self) -> Option<PipeTransportPair> {\n        let local = self.local.upgrade()?;\n        let remote = self.remote.upgrade()?;\n        Some(PipeTransportPair { local, remote })\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_transport: Bag<Arc<dyn Fn(NewTransport<'_>) + Send + Sync>>,\n    new_rtp_observer: Bag<Arc<dyn Fn(NewRtpObserver<'_>) + Send + Sync>>,\n    worker_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: RouterId,\n    executor: Arc<Executor<'static>>,\n    rtp_capabilities: SyncMutex<RtpCapabilitiesFinalized>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    producers: Arc<RwLock<HashedMap<ProducerId, WeakProducer>>>,\n    data_producers: Arc<RwLock<HashedMap<DataProducerId, WeakDataProducer>>>,\n    #[allow(clippy::type_complexity)]\n    mapped_pipe_transports:\n        Arc<Mutex<HashedMap<RouterId, Arc<AsyncMutex<Option<WeakPipeTransportPair>>>>>>,\n    // Make sure worker is not dropped until this router is not dropped\n    worker: Worker,\n    closed: AtomicBool,\n    _on_worker_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close();\n    }\n}\n\nimpl Inner {\n    fn close(&self) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            self.handlers.close.call_simple();\n\n            {\n                let channel = self.channel.clone();\n                let request = RouterCloseRequest { router_id: self.id };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(\"\", request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\"router closing failed on drop: Channel already closed\");\n                            }\n                            Err(error) => {\n                                error!(\"router closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A router enables injection, selection and forwarding of media streams through [`Transport`]\n/// instances created on it.\n///\n/// Developers may think of a mediasoup router as if it were a \"multi-party conference room\",\n/// although mediasoup is much more low level than that and doesn't constrain itself to specific\n/// high level use cases (for instance, a \"multi-party conference room\" could involve various\n/// mediasoup routers, even in different physicals hosts).\n#[derive(Clone)]\n#[must_use = \"Router will be closed on drop, make sure to keep it around for as long as needed\"]\npub struct Router {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for Router {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Router\")\n            .field(\"id\", &self.inner.id)\n            .field(\"rtp_capabilities\", &self.inner.rtp_capabilities)\n            .field(\"producers\", &self.inner.producers)\n            .field(\"data_producers\", &self.inner.data_producers)\n            .field(\"mapped_pipe_transports\", &self.inner.mapped_pipe_transports)\n            .field(\"worker\", &self.inner.worker)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl Router {\n    pub(super) fn new(\n        id: RouterId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        rtp_capabilities: RtpCapabilitiesFinalized,\n        app_data: AppData,\n        worker: Worker,\n    ) -> Self {\n        debug!(\"new()\");\n\n        let producers = Arc::<RwLock<HashedMap<ProducerId, WeakProducer>>>::default();\n        let data_producers = Arc::<RwLock<HashedMap<DataProducerId, WeakDataProducer>>>::default();\n        let mapped_pipe_transports = Arc::<\n            Mutex<HashedMap<RouterId, Arc<AsyncMutex<Option<WeakPipeTransportPair>>>>>,\n        >::default();\n        let handlers = Arc::<Handlers>::default();\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_worker_close_handler = worker.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.worker_close.call_simple();\n                    if !inner.closed.swap(true, Ordering::SeqCst) {\n                        inner.handlers.close.call_simple();\n                    }\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            executor,\n            rtp_capabilities: SyncMutex::new(rtp_capabilities),\n            channel,\n            handlers,\n            producers,\n            data_producers,\n            mapped_pipe_transports,\n            app_data,\n            worker,\n            closed: AtomicBool::new(false),\n            _on_worker_close_handler: Mutex::new(on_worker_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Router id.\n    #[must_use]\n    pub fn id(&self) -> RouterId {\n        self.inner.id\n    }\n\n    /// Worker to which router belongs.\n    pub fn worker(&self) -> &Worker {\n        &self.inner.worker\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    /// Whether router is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    /// RTP capabilities of the router. These capabilities are typically needed by mediasoup clients\n    /// to compute their sending RTP parameters.\n    ///\n    /// # Notes on usage\n    /// * Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/)\n    ///   section for more details.\n    /// * See also how to [filter these RTP capabilities](https://mediasoup.org/documentation/v3/tricks/#rtp-capabilities-filtering)\n    ///   before using them into a client.\n    #[must_use]\n    pub fn rtp_capabilities(&self) -> RtpCapabilitiesFinalized {\n        self.inner.rtp_capabilities.lock().unwrap().clone()\n    }\n\n    /// Dump Router.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<RouterDump, RequestError> {\n        debug!(\"dump()\");\n\n        self.inner\n            .channel\n            .request(self.inner.id, RouterDumpRequest {})\n            .await\n    }\n\n    /// Create a [`DirectTransport`].\n    ///\n    /// Router will be kept alive as long as at least one transport instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    ///\n    /// # async fn f(router: Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let transport = router.create_direct_transport(DirectTransportOptions::default()).await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_direct_transport(\n        &self,\n        direct_transport_options: DirectTransportOptions,\n    ) -> Result<DirectTransport, RequestError> {\n        debug!(\"create_direct_transport()\");\n\n        let transport_id = TransportId::new();\n\n        let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into());\n\n        self.inner\n            .channel\n            .request(\n                self.inner.id,\n                RouterCreateDirectTransportRequest {\n                    data: RouterCreateDirectTransportData::from_options(\n                        transport_id,\n                        &direct_transport_options,\n                    ),\n                },\n            )\n            .await?;\n\n        let transport = DirectTransport::new(\n            transport_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            direct_transport_options.app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_transport.call(|callback| {\n            callback(NewTransport::Direct(&transport));\n        });\n\n        self.after_transport_creation(&transport);\n\n        Ok(transport)\n    }\n\n    /// Create a [`WebRtcTransport`].\n    ///\n    /// Router will be kept alive as long as at least one transport instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use std::net::{IpAddr, Ipv4Addr};\n    ///\n    /// # async fn f(router: Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let transport = router\n    ///     .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(\n    ///         ListenInfo {\n    ///             protocol: Protocol::Udp,\n    ///             ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///             announced_address: Some(\"9.9.9.1\".to_string()),\n    ///             expose_internal_ip: false,\n    ///             port: None,\n    ///             port_range: None,\n    ///             flags: None,\n    ///             send_buffer_size: None,\n    ///             recv_buffer_size: None,\n    ///         },\n    ///     )))\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_webrtc_transport(\n        &self,\n        webrtc_transport_options: WebRtcTransportOptions,\n    ) -> Result<WebRtcTransport, RequestError> {\n        debug!(\"create_webrtc_transport()\");\n\n        let transport_id = TransportId::new();\n\n        let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into());\n\n        let data = match webrtc_transport_options.listen {\n            WebRtcTransportListen::Individual { listen_infos: _ } => {\n                self.inner\n                    .channel\n                    .request(\n                        self.inner.id,\n                        RouterCreateWebRtcTransportRequest {\n                            data: RouterCreateWebrtcTransportData::from_options(\n                                transport_id,\n                                &webrtc_transport_options,\n                            ),\n                        },\n                    )\n                    .await?\n            }\n            WebRtcTransportListen::Server { webrtc_server: _ } => {\n                self.inner\n                    .channel\n                    .request(\n                        self.inner.id,\n                        RouterCreateWebRtcTransportWithServerRequest {\n                            data: RouterCreateWebrtcTransportData::from_options(\n                                transport_id,\n                                &webrtc_transport_options,\n                            ),\n                        },\n                    )\n                    .await?\n            }\n        };\n\n        let transport = WebRtcTransport::new(\n            transport_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            data,\n            webrtc_transport_options.app_data,\n            self.clone(),\n            match webrtc_transport_options.listen {\n                WebRtcTransportListen::Individual { .. } => None,\n                WebRtcTransportListen::Server { webrtc_server } => Some(webrtc_server),\n            },\n        );\n\n        self.inner.handlers.new_transport.call(|callback| {\n            callback(NewTransport::WebRtc(&transport));\n        });\n\n        self.after_transport_creation(&transport);\n\n        Ok(transport)\n    }\n\n    /// Create a [`PipeTransport`].\n    ///\n    /// Router will be kept alive as long as at least one transport instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use std::net::{IpAddr, Ipv4Addr};\n    ///\n    /// # async fn f(router: Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let transport = router\n    ///     .create_pipe_transport(PipeTransportOptions::new(ListenInfo {\n    ///         protocol: Protocol::Udp,\n    ///         ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///         announced_address: Some(\"9.9.9.1\".to_string()),\n    ///         expose_internal_ip: false,\n    ///         port: None,\n    ///         port_range: None,\n    ///         flags: None,\n    ///         send_buffer_size: None,\n    ///         recv_buffer_size: None,\n    ///     }))\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_pipe_transport(\n        &self,\n        pipe_transport_options: PipeTransportOptions,\n    ) -> Result<PipeTransport, RequestError> {\n        debug!(\"create_pipe_transport()\");\n\n        let transport_id = TransportId::new();\n\n        let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into());\n\n        let data = self\n            .inner\n            .channel\n            .request(\n                self.inner.id,\n                RouterCreatePipeTransportRequest {\n                    data: RouterCreatePipeTransportData::from_options(\n                        transport_id,\n                        &pipe_transport_options,\n                    ),\n                },\n            )\n            .await?;\n\n        let transport = PipeTransport::new(\n            transport_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            data,\n            pipe_transport_options.app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_transport.call(|callback| {\n            callback(NewTransport::Pipe(&transport));\n        });\n\n        self.after_transport_creation(&transport);\n\n        Ok(transport)\n    }\n\n    /// Create a [`PlainTransport`].\n    ///\n    /// Router will be kept alive as long as at least one transport instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use std::net::{IpAddr, Ipv4Addr};\n    ///\n    /// # async fn f(router: Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let transport = router\n    ///     .create_plain_transport(PlainTransportOptions::new(ListenInfo {\n    ///         protocol: Protocol::Udp,\n    ///         ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///         announced_address: Some(\"9.9.9.1\".to_string()),\n    ///         expose_internal_ip: false,\n    ///         port: None,\n    ///         port_range: None,\n    ///         flags: None,\n    ///         send_buffer_size: None,\n    ///         recv_buffer_size: None,\n    ///     }))\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_plain_transport(\n        &self,\n        plain_transport_options: PlainTransportOptions,\n    ) -> Result<PlainTransport, RequestError> {\n        debug!(\"create_plain_transport()\");\n\n        let transport_id = TransportId::new();\n\n        let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into());\n\n        let data = self\n            .inner\n            .channel\n            .request(\n                self.inner.id,\n                RouterCreatePlainTransportRequest {\n                    data: RouterCreatePlainTransportData::from_options(\n                        transport_id,\n                        &plain_transport_options,\n                    ),\n                },\n            )\n            .await?;\n\n        let transport = PlainTransport::new(\n            transport_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            data,\n            plain_transport_options.app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_transport.call(|callback| {\n            callback(NewTransport::Plain(&transport));\n        });\n\n        self.after_transport_creation(&transport);\n\n        Ok(transport)\n    }\n\n    /// Create an [`AudioLevelObserver`].\n    ///\n    /// Router will be kept alive as long as at least one observer instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use std::num::NonZeroU16;\n    ///\n    /// # async fn f(router: Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let observer = router\n    ///     .create_audio_level_observer({\n    ///         let mut options = AudioLevelObserverOptions::default();\n    ///         options.max_entries = NonZeroU16::new(1).unwrap();\n    ///         options.threshold = -70;\n    ///         options.interval = 2000;\n    ///         options\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_audio_level_observer(\n        &self,\n        audio_level_observer_options: AudioLevelObserverOptions,\n    ) -> Result<AudioLevelObserver, RequestError> {\n        debug!(\"create_audio_level_observer()\");\n\n        let rtp_observer_id = RtpObserverId::new();\n\n        let _buffer_guard = self\n            .inner\n            .channel\n            .buffer_messages_for(rtp_observer_id.into());\n\n        self.inner\n            .channel\n            .request(\n                self.inner.id,\n                RouterCreateAudioLevelObserverRequest {\n                    data: RouterCreateAudioLevelObserverData::from_options(\n                        rtp_observer_id,\n                        &audio_level_observer_options,\n                    ),\n                },\n            )\n            .await?;\n\n        let audio_level_observer = AudioLevelObserver::new(\n            rtp_observer_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            audio_level_observer_options.app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_rtp_observer.call(|callback| {\n            callback(NewRtpObserver::AudioLevel(&audio_level_observer));\n        });\n\n        Ok(audio_level_observer)\n    }\n\n    /// Create an [`ActiveSpeakerObserver`].\n    ///\n    /// Router will be kept alive as long as at least one observer instance is alive.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions;\n    ///\n    /// # async fn f(router: mediasoup::router::Router) -> Result<(), Box<dyn std::error::Error>> {\n    /// let observer = router\n    ///     .create_active_speaker_observer({\n    ///         let mut options = ActiveSpeakerObserverOptions::default();\n    ///         options.interval = 300;\n    ///         options\n    ///     })\n    ///     .await?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn create_active_speaker_observer(\n        &self,\n        active_speaker_observer_options: ActiveSpeakerObserverOptions,\n    ) -> Result<ActiveSpeakerObserver, RequestError> {\n        debug!(\"create_active_speaker_observer()\");\n\n        let rtp_observer_id = RtpObserverId::new();\n\n        let _buffer_guard = self\n            .inner\n            .channel\n            .buffer_messages_for(rtp_observer_id.into());\n\n        self.inner\n            .channel\n            .request(\n                self.inner.id,\n                RouterCreateActiveSpeakerObserverRequest {\n                    data: RouterCreateActiveSpeakerObserverData::from_options(\n                        rtp_observer_id,\n                        &active_speaker_observer_options,\n                    ),\n                },\n            )\n            .await?;\n\n        let active_speaker_observer = ActiveSpeakerObserver::new(\n            rtp_observer_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            active_speaker_observer_options.app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_rtp_observer.call(|callback| {\n            callback(NewRtpObserver::ActiveSpeaker(&active_speaker_observer));\n        });\n\n        Ok(active_speaker_observer)\n    }\n\n    /// Pipes [`Producer`] with the given `producer_id` into another [`Router`] on same host.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use mediasoup_types::rtp_parameters::RtpCodecParameters;\n    /// use std::net::{IpAddr, Ipv4Addr};\n    /// use std::num::{NonZeroU32, NonZeroU8};\n    ///\n    /// # async fn f(worker_manager: WorkerManager) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Have two workers.\n    /// let worker1 = worker_manager.create_worker(WorkerSettings::default()).await?;\n    /// let worker2 = worker_manager.create_worker(WorkerSettings::default()).await?;\n    ///\n    /// // Create a router in each worker.\n    /// let media_codecs = vec![\n    ///     RtpCodecCapability::Audio {\n    ///         mime_type: MimeTypeAudio::Opus,\n    ///         preferred_payload_type: None,\n    ///         clock_rate: NonZeroU32::new(48000).unwrap(),\n    ///         channels: NonZeroU8::new(2).unwrap(),\n    ///         parameters: RtpCodecParametersParameters::from([\n    ///             (\"useinbandfec\", 1_u32.into()),\n    ///         ]),\n    ///         rtcp_feedback: vec![],\n    ///     },\n    /// ];\n    /// let router1 = worker1.create_router(RouterOptions::new(media_codecs.clone())).await?;\n    /// let router2 = worker2.create_router(RouterOptions::new(media_codecs)).await?;\n    ///\n    /// // Produce in router1.\n    /// let transport1 = router1\n    ///     .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(\n    ///         ListenInfo {\n    ///             protocol: Protocol::Udp,\n    ///             ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///             announced_address: Some(\"9.9.9.1\".to_string()),\n    ///             expose_internal_ip: false,\n    ///             port: None,\n    ///             port_range: None,\n    ///             flags: None,\n    ///             send_buffer_size: None,\n    ///             recv_buffer_size: None,\n    ///         },\n    ///     )))\n    ///     .await?;\n    /// let producer1 = transport1\n    ///     .produce(ProducerOptions::new(\n    ///         MediaKind::Audio,\n    ///         RtpParameters {\n    ///             mid: Some(\"AUDIO\".to_string()),\n    ///             codecs: vec![RtpCodecParameters::Audio {\n    ///                 mime_type: MimeTypeAudio::Opus,\n    ///                 payload_type: 0,\n    ///                 clock_rate: NonZeroU32::new(48000).unwrap(),\n    ///                 channels: NonZeroU8::new(2).unwrap(),\n    ///                 parameters: RtpCodecParametersParameters::from([\n    ///                     (\"useinbandfec\", 1_u32.into()),\n    ///                     (\"usedtx\", 1_u32.into()),\n    ///                 ]),\n    ///                 rtcp_feedback: vec![],\n    ///             }],\n    ///             ..RtpParameters::default()\n    ///         },\n    ///     ))\n    ///     .await?;\n    ///\n    /// // Pipe producer1 into router2.\n    /// router1\n    ///     .pipe_producer_to_router(\n    ///         producer1.id(),\n    ///         PipeToRouterOptions::new(router2.clone())\n    ///     )\n    ///     .await?;\n    ///\n    /// // Consume producer1 from router2.\n    /// let transport2 = router2\n    ///     .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(\n    ///         ListenInfo {\n    ///             protocol: Protocol::Udp,\n    ///             ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///             announced_address: Some(\"9.9.9.1\".to_string()),\n    ///             expose_internal_ip: false,\n    ///             port: None,\n    ///             port_range: None,\n    ///             flags: None,\n    ///             send_buffer_size: None,\n    ///             recv_buffer_size: None,\n    ///         },\n    ///     )))\n    ///     .await?;\n    /// let consumer2 = transport2\n    ///     .consume(ConsumerOptions::new(\n    ///         producer1.id(),\n    ///         RtpCapabilities {\n    ///            codecs: vec![\n    ///                RtpCodecCapability::Audio {\n    ///                    mime_type: MimeTypeAudio::Opus,\n    ///                    preferred_payload_type: Some(100),\n    ///                    clock_rate: NonZeroU32::new(48000).unwrap(),\n    ///                    channels: NonZeroU8::new(2).unwrap(),\n    ///                    parameters: RtpCodecParametersParameters::default(),\n    ///                    rtcp_feedback: vec![],\n    ///                },\n    ///            ],\n    ///            header_extensions: vec![],\n    ///        }\n    ///     ))\n    ///     .await?;\n    ///\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn pipe_producer_to_router(\n        &self,\n        producer_id: ProducerId,\n        pipe_to_router_options: PipeToRouterOptions,\n    ) -> Result<PipeProducerToRouterPair, PipeProducerToRouterError> {\n        debug!(\"pipe_producer_to_router()\");\n\n        let PipeToRouterOptions {\n            ref router,\n            keep_id,\n            ..\n        } = pipe_to_router_options;\n\n        if keep_id && router.id() == self.id() {\n            return Err(PipeProducerToRouterError::SameRouter);\n        }\n\n        let producer = match self\n            .inner\n            .producers\n            .read()\n            .get(&producer_id)\n            .and_then(WeakProducer::upgrade)\n        {\n            Some(producer) => producer,\n            None => {\n                return Err(PipeProducerToRouterError::ProducerNotFound(producer_id));\n            }\n        };\n\n        let pipe_transport_pair = self\n            .get_or_create_pipe_transport_pair(pipe_to_router_options)\n            .await?;\n\n        let pipe_consumer = pipe_transport_pair\n            .local\n            .consume(ConsumerOptions::new(\n                producer_id,\n                RtpCapabilities::default(),\n            ))\n            .await?;\n\n        let pipe_producer = pipe_transport_pair\n            .remote\n            .produce({\n                let mut producer_options = ProducerOptions::new_pipe_transport(\n                    // Generate a new id for the pipeProducer if requested.\n                    if keep_id {\n                        producer_id\n                    } else {\n                        ProducerId::new()\n                    },\n                    pipe_consumer.kind(),\n                    pipe_consumer.rtp_parameters().clone(),\n                );\n                producer_options.paused = pipe_consumer.producer_paused();\n                producer_options.app_data = producer.app_data().clone();\n\n                producer_options\n            })\n            .await?;\n\n        // Pipe events from the pipe Consumer to the pipe Producer.\n        pipe_consumer\n            .on_close({\n                let pipe_producer_weak = pipe_producer.downgrade();\n\n                move || {\n                    if let Some(pipe_producer) = pipe_producer_weak.upgrade() {\n                        pipe_producer.close();\n                    }\n                }\n            })\n            .detach();\n        pipe_consumer\n            .on_pause({\n                let executor = Arc::clone(&self.inner.executor);\n                let pipe_producer_weak = pipe_producer.downgrade();\n\n                move || {\n                    if let Some(pipe_producer) = pipe_producer_weak.upgrade() {\n                        executor\n                            .spawn(async move {\n                                let _ = pipe_producer.pause().await;\n                            })\n                            .detach();\n                    }\n                }\n            })\n            .detach();\n        pipe_consumer\n            .on_resume({\n                let executor = Arc::clone(&self.inner.executor);\n                let pipe_producer_weak = pipe_producer.downgrade();\n\n                move || {\n                    if let Some(pipe_producer) = pipe_producer_weak.upgrade() {\n                        executor\n                            .spawn(async move {\n                                let _ = pipe_producer.resume().await;\n                            })\n                            .detach();\n                    }\n                }\n            })\n            .detach();\n\n        // Pipe events from the pipe Producer to the pipe Consumer.\n        pipe_producer\n            .on_close({\n                let pipe_consumer = pipe_consumer.clone();\n\n                move || {\n                    // Just to make sure consumer on local router outlives producer on the other\n                    // router\n                    drop(pipe_consumer);\n                }\n            })\n            .detach();\n\n        let pipe_producer = PipedProducer::new(pipe_producer, {\n            let weak_producer = producer.downgrade();\n\n            move |pipe_producer| {\n                if let Some(producer) = weak_producer.upgrade() {\n                    // In case `PipedProducer` was dropped without transforming into regular\n                    // `Producer` first, we need to tie underlying pipe producer lifetime to the\n                    // lifetime of original source producer in another router\n                    producer\n                        .on_close(move || {\n                            pipe_producer.close();\n                        })\n                        .detach();\n                }\n            }\n        });\n\n        Ok(PipeProducerToRouterPair {\n            pipe_consumer,\n            pipe_producer,\n        })\n    }\n\n    /// Pipes [`DataProducer`] with the given `data_producer_id` into another [`Router`] on same\n    /// host.\n    ///\n    /// # Example\n    /// ```rust\n    /// use mediasoup::prelude::*;\n    /// use std::net::{IpAddr, Ipv4Addr};\n    ///\n    /// # async fn f(worker_manager: WorkerManager) -> Result<(), Box<dyn std::error::Error>> {\n    /// // Have two workers.\n    /// let worker1 = worker_manager.create_worker(WorkerSettings::default()).await?;\n    /// let worker2 = worker_manager.create_worker(WorkerSettings::default()).await?;\n    ///\n    /// // Create a router in each worker.\n    /// let router1 = worker1.create_router(RouterOptions::default()).await?;\n    /// let router2 = worker2.create_router(RouterOptions::default()).await?;\n    ///\n    /// // Produce in router1.\n    /// let transport1 = router1\n    ///     .create_webrtc_transport({\n    ///         let mut options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(\n    ///             ListenInfo {\n    ///                 protocol: Protocol::Udp,\n    ///                 ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///                 announced_address: Some(\"9.9.9.1\".to_string()),\n    ///                 expose_internal_ip: false,\n    ///                 port: None,\n    ///                 port_range: None,\n    ///                 flags: None,\n    ///                 send_buffer_size: None,\n    ///                 recv_buffer_size: None,\n    ///             },\n    ///         ));\n    ///         options.enable_sctp = true;\n    ///         options\n    ///     })\n    ///     .await?;\n    /// let data_producer1 = transport1\n    ///     .produce_data(DataProducerOptions::new_sctp(\n    ///         SctpStreamParameters::new_unordered_with_life_time(666, 5000),\n    ///     ))\n    ///     .await?;\n    ///\n    /// // Pipe data_producer1 into router2.\n    /// router1\n    ///     .pipe_data_producer_to_router(\n    ///         data_producer1.id(),\n    ///         PipeToRouterOptions::new(router2.clone())\n    ///     )\n    ///     .await?;\n    ///\n    /// // Consume data_producer1 from router2.\n    /// let transport2 = router2\n    ///     .create_webrtc_transport({\n    ///         let mut options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(\n    ///             ListenInfo {\n    ///                 protocol: Protocol::Udp,\n    ///                 ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n    ///                 announced_address: Some(\"9.9.9.1\".to_string()),\n    ///                 expose_internal_ip: false,\n    ///                 port: None,\n    ///                 port_range: None,\n    ///                 flags: None,\n    ///                 send_buffer_size: None,\n    ///                 recv_buffer_size: None,\n    ///             },\n    ///         ));\n    ///         options.enable_sctp = true;\n    ///         options\n    ///     })\n    ///     .await?;\n    /// let data_consumer2 = transport2\n    ///     .consume_data(DataConsumerOptions::new_sctp(data_producer1.id()))\n    ///     .await?;\n    ///\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub async fn pipe_data_producer_to_router(\n        &self,\n        data_producer_id: DataProducerId,\n        pipe_to_router_options: PipeToRouterOptions,\n    ) -> Result<PipeDataProducerToRouterPair, PipeDataProducerToRouterError> {\n        debug!(\"pipe_data_producer_to_router()\");\n\n        let PipeToRouterOptions {\n            ref router,\n            keep_id,\n            ..\n        } = pipe_to_router_options;\n\n        if keep_id && router.id() == self.id() {\n            return Err(PipeDataProducerToRouterError::SameRouter);\n        }\n\n        let data_producer = match self\n            .inner\n            .data_producers\n            .read()\n            .get(&data_producer_id)\n            .and_then(WeakDataProducer::upgrade)\n        {\n            Some(data_producer) => data_producer,\n            None => {\n                return Err(PipeDataProducerToRouterError::DataProducerNotFound(\n                    data_producer_id,\n                ));\n            }\n        };\n\n        let pipe_transport_pair = self\n            .get_or_create_pipe_transport_pair(pipe_to_router_options)\n            .await?;\n\n        let pipe_data_consumer = pipe_transport_pair\n            .local\n            .consume_data(DataConsumerOptions::new_sctp(data_producer_id))\n            .await?;\n\n        let pipe_data_producer = pipe_transport_pair\n            .remote\n            .produce_data({\n                let mut producer_options = DataProducerOptions::new_pipe_transport(\n                    // Generate a new id for the pipeDataProducer if requested.\n                    if keep_id {\n                        data_producer_id\n                    } else {\n                        DataProducerId::new()\n                    },\n                    // We've created `DataConsumer` with SCTP above, so this should never panic\n                    pipe_data_consumer.sctp_stream_parameters().unwrap(),\n                );\n                producer_options\n                    .label\n                    .clone_from(pipe_data_consumer.label());\n                producer_options\n                    .protocol\n                    .clone_from(pipe_data_consumer.protocol());\n                producer_options.app_data = data_producer.app_data().clone();\n\n                producer_options\n            })\n            .await?;\n\n        // Pipe events from the pipe DataConsumer to the pipe DataProducer.\n        pipe_data_consumer\n            .on_close({\n                let pipe_data_producer_weak = pipe_data_producer.downgrade();\n\n                move || {\n                    if let Some(pipe_data_producer) = pipe_data_producer_weak.upgrade() {\n                        pipe_data_producer.close();\n                    }\n                }\n            })\n            .detach();\n        // Pipe events from the pipe DataProducer to the pipe Consumer.\n        pipe_data_producer\n            .on_close({\n                let pipe_data_consumer = pipe_data_consumer.clone();\n\n                move || {\n                    // Just to make sure consumer on local router outlives producer on the other\n                    // router\n                    drop(pipe_data_consumer);\n                }\n            })\n            .detach();\n\n        let pipe_data_producer = NonClosingDataProducer::new(pipe_data_producer, {\n            let weak_data_producer = data_producer.downgrade();\n\n            move |pipe_data_producer| {\n                if let Some(data_producer) = weak_data_producer.upgrade() {\n                    // In case `NonClosingDataProducer` was dropped without transforming into\n                    // regular `DataProducer` first, we need to tie underlying pipe data producer\n                    // lifetime to the lifetime of original source data producer in another router\n                    data_producer\n                        .on_close(move || {\n                            pipe_data_producer.close();\n                        })\n                        .detach();\n                }\n            }\n        });\n\n        Ok(PipeDataProducerToRouterPair {\n            pipe_data_consumer,\n            pipe_data_producer,\n        })\n    }\n\n    /// Check whether the given RTP capabilities are valid to consume the given producer.\n    #[must_use]\n    pub fn can_consume(\n        &self,\n        producer_id: &ProducerId,\n        rtp_capabilities: &RtpCapabilities,\n    ) -> bool {\n        if let Some(producer) = self.get_producer(producer_id) {\n            match ortc::can_consume(producer.consumable_rtp_parameters(), rtp_capabilities) {\n                Ok(result) => result,\n                Err(error) => {\n                    error!(\"can_consume() | unexpected error: {}\", error);\n                    false\n                }\n            }\n        } else {\n            error!(\n                \"can_consume() | Producer with id \\\"{}\\\" not found\",\n                producer_id\n            );\n            false\n        }\n    }\n\n    /// Update the Router media codecs. Once called, the return value of\n    /// router.rtp_capabilities() changes.\n    pub fn update_media_codecs(\n        &mut self,\n        media_codecs: Vec<RtpCodecCapability>,\n    ) -> Result<(), UpdateMediaCodecsError> {\n        debug!(\"update_media_codecs()\");\n\n        let rtp_capabilities = ortc::generate_router_rtp_capabilities(media_codecs)\n            .map_err(UpdateMediaCodecsError::FailedRtpCapabilitiesGeneration)?;\n\n        let mut locked = self.inner.rtp_capabilities.lock().unwrap();\n        *locked = rtp_capabilities;\n\n        Ok(())\n    }\n\n    /// Callback is called when a new transport is created.\n    pub fn on_new_transport<F: Fn(NewTransport<'_>) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.new_transport.add(Arc::new(callback))\n    }\n\n    /// Callback is called when a new RTP observer is created.\n    pub fn on_new_rtp_observer<F: Fn(NewRtpObserver<'_>) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.new_rtp_observer.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the worker this router belongs to is closed for whatever reason.\n    /// The router itself is also closed. A `on_router_close` callbacks are triggered in all its\n    /// transports all RTP observers.\n    pub fn on_worker_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.worker_close.add(Box::new(callback))\n    }\n\n    /// Callback is called when the router is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if router is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    async fn get_or_create_pipe_transport_pair(\n        &self,\n        pipe_to_router_options: PipeToRouterOptions,\n    ) -> Result<PipeTransportPair, RequestError> {\n        // Here we may have to create a new PipeTransport pair to connect source and\n        // destination Routers. We just want to keep a PipeTransport pair for each\n        // pair of Routers. Since this operation is async, it may happen that two\n        // simultaneous calls piping to the same router would end up generating two pairs of\n        // `PipeTransport`. To prevent that, we have `HashedMap` with mapping behind `Mutex` and\n        // another `Mutex` on the pair of `PipeTransport`.\n\n        let pipe_transport_pair_mutex = self\n            .inner\n            .mapped_pipe_transports\n            .lock()\n            .entry(pipe_to_router_options.router.id())\n            .or_default()\n            .clone();\n\n        let mut pipe_transport_pair_guard = pipe_transport_pair_mutex.lock().await;\n\n        let pipe_transport_pair = match pipe_transport_pair_guard\n            .as_ref()\n            .and_then(WeakPipeTransportPair::upgrade)\n        {\n            Some(pipe_transport_pair) => pipe_transport_pair,\n            None => {\n                let pipe_transport_pair = self\n                    .create_pipe_transport_pair(pipe_to_router_options)\n                    .await?;\n                pipe_transport_pair_guard.replace(pipe_transport_pair.downgrade());\n\n                pipe_transport_pair\n            }\n        };\n\n        Ok(pipe_transport_pair)\n    }\n\n    async fn create_pipe_transport_pair(\n        &self,\n        pipe_to_router_options: PipeToRouterOptions,\n    ) -> Result<PipeTransportPair, RequestError> {\n        let PipeToRouterOptions {\n            router,\n            keep_id: _,\n            listen_info,\n            enable_sctp,\n            num_sctp_streams,\n            enable_rtx,\n            enable_srtp,\n        } = pipe_to_router_options;\n\n        let remote_router_id = router.id();\n\n        let transport_options = PipeTransportOptions {\n            enable_sctp,\n            num_sctp_streams,\n            enable_rtx,\n            enable_srtp,\n            app_data: AppData::default(),\n            ..PipeTransportOptions::new(listen_info)\n        };\n        let local_pipe_transport_fut = self.create_pipe_transport(transport_options.clone());\n\n        let remote_pipe_transport_fut = router.create_pipe_transport(transport_options);\n\n        let (local_pipe_transport, remote_pipe_transport) =\n            future::try_zip(local_pipe_transport_fut, remote_pipe_transport_fut).await?;\n\n        let local_connect_fut = local_pipe_transport.connect({\n            let tuple = remote_pipe_transport.tuple();\n\n            PipeTransportRemoteParameters {\n                ip: tuple.local_address().parse::<IpAddr>().unwrap(),\n                port: tuple.local_port(),\n                srtp_parameters: remote_pipe_transport.srtp_parameters(),\n            }\n        });\n\n        let remote_connect_fut = remote_pipe_transport.connect({\n            let tuple = local_pipe_transport.tuple();\n\n            PipeTransportRemoteParameters {\n                ip: tuple.local_address().parse::<IpAddr>().unwrap(),\n                port: tuple.local_port(),\n                srtp_parameters: local_pipe_transport.srtp_parameters(),\n            }\n        });\n\n        future::try_zip(local_connect_fut, remote_connect_fut).await?;\n\n        local_pipe_transport\n            .on_close({\n                let mapped_pipe_transports = Arc::clone(&self.inner.mapped_pipe_transports);\n\n                Box::new(move || {\n                    mapped_pipe_transports.lock().remove(&remote_router_id);\n                })\n            })\n            .detach();\n\n        remote_pipe_transport\n            .on_close({\n                let mapped_pipe_transports = Arc::clone(&self.inner.mapped_pipe_transports);\n\n                Box::new(move || {\n                    mapped_pipe_transports.lock().remove(&remote_router_id);\n                })\n            })\n            .detach();\n\n        Ok(PipeTransportPair {\n            local: local_pipe_transport,\n            remote: remote_pipe_transport,\n        })\n    }\n\n    fn after_transport_creation(&self, transport: &impl TransportGeneric) {\n        {\n            let producers_weak = Arc::downgrade(&self.inner.producers);\n            transport\n                .on_new_producer(Arc::new(move |producer| {\n                    let producer_id = producer.id();\n                    if let Some(producers) = producers_weak.upgrade() {\n                        producers.write().insert(producer_id, producer.downgrade());\n                    }\n                    {\n                        let producers_weak = producers_weak.clone();\n                        producer\n                            .on_close(move || {\n                                if let Some(producers) = producers_weak.upgrade() {\n                                    producers.write().remove(&producer_id);\n                                }\n                            })\n                            .detach();\n                    }\n                }))\n                .detach();\n        }\n        {\n            let data_producers_weak = Arc::downgrade(&self.inner.data_producers);\n            transport\n                .on_new_data_producer(Arc::new(move |data_producer| {\n                    let data_producer_id = data_producer.id();\n                    if let Some(data_producers) = data_producers_weak.upgrade() {\n                        data_producers\n                            .write()\n                            .insert(data_producer_id, data_producer.downgrade());\n                    }\n                    {\n                        let data_producers_weak = data_producers_weak.clone();\n                        data_producer\n                            .on_close(move || {\n                                if let Some(data_producers) = data_producers_weak.upgrade() {\n                                    data_producers.write().remove(&data_producer_id);\n                                }\n                            })\n                            .detach();\n                    }\n                }))\n                .detach();\n        }\n    }\n\n    fn has_producer(&self, producer_id: &ProducerId) -> bool {\n        self.inner.producers.read().contains_key(producer_id)\n    }\n\n    fn get_producer(&self, producer_id: &ProducerId) -> Option<Producer> {\n        self.inner.producers.read().get(producer_id)?.upgrade()\n    }\n\n    fn has_data_producer(&self, data_producer_id: &DataProducerId) -> bool {\n        self.inner\n            .data_producers\n            .read()\n            .contains_key(data_producer_id)\n    }\n\n    fn get_data_producer(&self, data_producer_id: &DataProducerId) -> Option<DataProducer> {\n        self.inner\n            .data_producers\n            .read()\n            .get(data_producer_id)?\n            .upgrade()\n    }\n\n    #[cfg(test)]\n    fn close(&self) {\n        self.inner.close();\n    }\n}\n"
  },
  {
    "path": "rust/src/rtp_parameters.rs",
    "content": "//! Collection of RTP-related data structures that are used to specify codec parameters and\n//! capabilities of various endpoints.\n\nuse crate::fbs::{FromFbs, ToFbs, TryFromFbs};\nuse mediasoup_sys::fbs::rtp_parameters;\nuse mediasoup_types::rtp_parameters::*;\nuse std::borrow::Cow;\nuse std::error::Error;\nuse std::str::FromStr;\n\nimpl<'a> TryFromFbs<'a> for RtpParameters {\n    type FbsType = rtp_parameters::RtpParametersRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(rtp_parameters: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            mid: rtp_parameters.mid()?.map(|mid| mid.to_string()),\n            codecs: rtp_parameters\n                .codecs()?\n                .into_iter()\n                .map(|codec| {\n                    let parameters = codec?\n                        .parameters()?\n                        .unwrap_or(planus::Vector::new_empty())\n                        .into_iter()\n                        .map(|parameters| {\n                            Ok((\n                                Cow::Owned(parameters?.name()?.to_string()),\n                                match parameters?.value()? {\n                                    rtp_parameters::ValueRef::Boolean(_)\n                                    | rtp_parameters::ValueRef::Double(_)\n                                    | rtp_parameters::ValueRef::Integer32Array(_) => {\n                                        // TODO: Above value variant should not exist in the\n                                        //  first place\n                                        panic!(\"Invalid parameter\")\n                                    }\n                                    rtp_parameters::ValueRef::Integer32(n) => {\n                                        RtpCodecParametersParametersValue::Number(\n                                            n.value()?.try_into()?,\n                                        )\n                                    }\n                                    rtp_parameters::ValueRef::String(s) => {\n                                        RtpCodecParametersParametersValue::String(\n                                            s.value()?.to_string().into(),\n                                        )\n                                    }\n                                },\n                            ))\n                        })\n                        .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?;\n                    let rtcp_feedback = codec?\n                        .rtcp_feedback()?\n                        .unwrap_or(planus::Vector::new_empty())\n                        .into_iter()\n                        .map(|rtcp_feedback| {\n                            Ok(RtcpFeedback::from_type_parameter(\n                                rtcp_feedback?.type_()?,\n                                rtcp_feedback?.parameter()?.unwrap_or_default(),\n                            )?)\n                        })\n                        .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?;\n\n                    Ok(match MimeType::from_str(codec?.mime_type()?)? {\n                        MimeType::Audio(mime_type) => RtpCodecParameters::Audio {\n                            mime_type,\n                            payload_type: codec?.payload_type()?,\n                            clock_rate: codec?.clock_rate()?.try_into()?,\n                            channels: codec?\n                                .channels()?\n                                .ok_or(\"Audio must have channels specified\")?\n                                .try_into()?,\n                            parameters,\n                            rtcp_feedback: vec![],\n                        },\n                        MimeType::Video(mime_type) => RtpCodecParameters::Video {\n                            mime_type,\n                            payload_type: codec?.payload_type()?,\n                            clock_rate: codec?.clock_rate()?.try_into()?,\n                            parameters,\n                            rtcp_feedback,\n                        },\n                    })\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            header_extensions: rtp_parameters\n                .header_extensions()?\n                .into_iter()\n                .map(|header_extension_parameters| {\n                    Ok(RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::from_fbs(&header_extension_parameters?.uri()?),\n                        id: u16::from(header_extension_parameters?.id()?),\n                        encrypt: header_extension_parameters?.encrypt()?,\n                    })\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            encodings: rtp_parameters\n                .encodings()?\n                .into_iter()\n                .map(|encoding| {\n                    Ok(RtpEncodingParameters {\n                        ssrc: encoding?.ssrc()?,\n                        rid: encoding?.rid()?.map(|rid| rid.to_string()),\n                        codec_payload_type: encoding?.codec_payload_type()?,\n                        rtx: encoding?.rtx()?.map(|rtx| RtpEncodingParametersRtx {\n                            ssrc: rtx.ssrc().unwrap(),\n                        }),\n                        dtx: {\n                            match encoding?.dtx()? {\n                                true => Some(true),\n                                false => None,\n                            }\n                        },\n                        scalability_mode: encoding?\n                            .scalability_mode()?\n                            .unwrap_or(String::from(\"S1T1\").as_str())\n                            .parse()?,\n                        max_bitrate: encoding?.max_bitrate()?,\n                    })\n                })\n                .collect::<Result<_, Box<dyn Error + Send + Sync>>>()?,\n            rtcp: RtcpParameters {\n                cname: rtp_parameters\n                    .rtcp()?\n                    .cname()?\n                    .map(|cname| cname.to_string()),\n                reduced_size: rtp_parameters.rtcp()?.reduced_size()?,\n            },\n            msid: rtp_parameters.msid()?.map(|msid| msid.to_string()),\n        })\n    }\n}\n\nimpl<'a> TryFromFbs<'a> for RtpEncodingParameters {\n    type FbsType = rtp_parameters::RtpEncodingParametersRef<'a>;\n    type Error = Box<dyn Error + Send + Sync>;\n\n    fn try_from_fbs(encoding_parameters: Self::FbsType) -> Result<Self, Self::Error> {\n        Ok(Self {\n            ssrc: encoding_parameters.ssrc()?,\n            rid: encoding_parameters.rid()?.map(|rid| rid.to_string()),\n            codec_payload_type: encoding_parameters.codec_payload_type()?,\n            rtx: if let Some(rtx) = encoding_parameters.rtx()? {\n                Some(RtpEncodingParametersRtx { ssrc: rtx.ssrc()? })\n            } else {\n                None\n            },\n            dtx: {\n                match encoding_parameters.dtx()? {\n                    true => Some(true),\n                    false => None,\n                }\n            },\n            scalability_mode: encoding_parameters\n                .scalability_mode()?\n                .map(|maybe_scalability_mode| maybe_scalability_mode.parse())\n                .transpose()?\n                .unwrap_or_default(),\n            max_bitrate: encoding_parameters.max_bitrate()?,\n        })\n    }\n}\n\nimpl FromFbs for MediaKind {\n    type FbsType = rtp_parameters::MediaKind;\n\n    fn from_fbs(kind: &Self::FbsType) -> Self {\n        match kind {\n            rtp_parameters::MediaKind::Audio => MediaKind::Audio,\n            rtp_parameters::MediaKind::Video => MediaKind::Video,\n        }\n    }\n}\n\nimpl ToFbs for MediaKind {\n    type FbsType = rtp_parameters::MediaKind;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            MediaKind::Audio => rtp_parameters::MediaKind::Audio,\n            MediaKind::Video => rtp_parameters::MediaKind::Video,\n        }\n    }\n}\n\nimpl FromFbs for RtpHeaderExtensionUri {\n    type FbsType = rtp_parameters::RtpHeaderExtensionUri;\n\n    fn from_fbs(uri: &Self::FbsType) -> Self {\n        match uri {\n            rtp_parameters::RtpHeaderExtensionUri::Mid => RtpHeaderExtensionUri::Mid,\n            rtp_parameters::RtpHeaderExtensionUri::RtpStreamId => {\n                RtpHeaderExtensionUri::RtpStreamId\n            }\n            rtp_parameters::RtpHeaderExtensionUri::RepairRtpStreamId => {\n                RtpHeaderExtensionUri::RepairRtpStreamId\n            }\n            rtp_parameters::RtpHeaderExtensionUri::AbsSendTime => {\n                RtpHeaderExtensionUri::AbsSendTime\n            }\n            rtp_parameters::RtpHeaderExtensionUri::TransportWideCcDraft01 => {\n                RtpHeaderExtensionUri::TransportWideCcDraft01\n            }\n            rtp_parameters::RtpHeaderExtensionUri::SsrcAudioLevel => {\n                RtpHeaderExtensionUri::SsrcAudioLevel\n            }\n            rtp_parameters::RtpHeaderExtensionUri::DependencyDescriptor => {\n                RtpHeaderExtensionUri::DependencyDescriptor\n            }\n            rtp_parameters::RtpHeaderExtensionUri::VideoOrientation => {\n                RtpHeaderExtensionUri::VideoOrientation\n            }\n            rtp_parameters::RtpHeaderExtensionUri::TimeOffset => RtpHeaderExtensionUri::TimeOffset,\n            rtp_parameters::RtpHeaderExtensionUri::AbsCaptureTime => {\n                RtpHeaderExtensionUri::AbsCaptureTime\n            }\n            rtp_parameters::RtpHeaderExtensionUri::PlayoutDelay => {\n                RtpHeaderExtensionUri::PlayoutDelay\n            }\n            rtp_parameters::RtpHeaderExtensionUri::MediasoupPacketId => {\n                RtpHeaderExtensionUri::MediasoupPacketId\n            }\n        }\n    }\n}\n\nimpl ToFbs for RtpHeaderExtensionUri {\n    type FbsType = rtp_parameters::RtpHeaderExtensionUri;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            RtpHeaderExtensionUri::Mid => rtp_parameters::RtpHeaderExtensionUri::Mid,\n            RtpHeaderExtensionUri::RtpStreamId => {\n                rtp_parameters::RtpHeaderExtensionUri::RtpStreamId\n            }\n            RtpHeaderExtensionUri::RepairRtpStreamId => {\n                rtp_parameters::RtpHeaderExtensionUri::RepairRtpStreamId\n            }\n            RtpHeaderExtensionUri::AbsSendTime => {\n                rtp_parameters::RtpHeaderExtensionUri::AbsSendTime\n            }\n            RtpHeaderExtensionUri::TransportWideCcDraft01 => {\n                rtp_parameters::RtpHeaderExtensionUri::TransportWideCcDraft01\n            }\n            RtpHeaderExtensionUri::SsrcAudioLevel => {\n                rtp_parameters::RtpHeaderExtensionUri::SsrcAudioLevel\n            }\n            RtpHeaderExtensionUri::DependencyDescriptor => {\n                rtp_parameters::RtpHeaderExtensionUri::DependencyDescriptor\n            }\n            RtpHeaderExtensionUri::VideoOrientation => {\n                rtp_parameters::RtpHeaderExtensionUri::VideoOrientation\n            }\n            RtpHeaderExtensionUri::TimeOffset => rtp_parameters::RtpHeaderExtensionUri::TimeOffset,\n            RtpHeaderExtensionUri::AbsCaptureTime => {\n                rtp_parameters::RtpHeaderExtensionUri::AbsCaptureTime\n            }\n            RtpHeaderExtensionUri::PlayoutDelay => {\n                rtp_parameters::RtpHeaderExtensionUri::PlayoutDelay\n            }\n            RtpHeaderExtensionUri::MediasoupPacketId => {\n                rtp_parameters::RtpHeaderExtensionUri::MediasoupPacketId\n            }\n            RtpHeaderExtensionUri::Unsupported => panic!(\"Invalid RTP extension header URI\"),\n        }\n    }\n}\n\nimpl ToFbs for RtpParameters {\n    type FbsType = rtp_parameters::RtpParameters;\n\n    fn to_fbs(&self) -> rtp_parameters::RtpParameters {\n        rtp_parameters::RtpParameters {\n            mid: self.mid.clone(),\n            codecs: self\n                .codecs\n                .iter()\n                .map(|codec| rtp_parameters::RtpCodecParameters {\n                    mime_type: codec.mime_type().as_str().to_string(),\n                    payload_type: codec.payload_type(),\n                    clock_rate: codec.clock_rate().get(),\n                    channels: match &codec {\n                        RtpCodecParameters::Audio { channels, .. } => Some(channels.get()),\n                        RtpCodecParameters::Video { .. } => None,\n                    },\n                    parameters: Some(\n                        codec\n                            .parameters()\n                            .iter()\n                            .map(|(name, value)| rtp_parameters::Parameter {\n                                name: name.to_string(),\n                                value: match value {\n                                    RtpCodecParametersParametersValue::String(s) => {\n                                        rtp_parameters::Value::String(Box::new(\n                                            rtp_parameters::String {\n                                                value: s.to_string(),\n                                            },\n                                        ))\n                                    }\n                                    RtpCodecParametersParametersValue::Number(n) => {\n                                        rtp_parameters::Value::Integer32(Box::new(\n                                            rtp_parameters::Integer32 { value: *n as i32 },\n                                        ))\n                                    }\n                                },\n                            })\n                            .collect(),\n                    ),\n                    rtcp_feedback: Some(\n                        codec\n                            .rtcp_feedback()\n                            .iter()\n                            .map(|rtcp_feedback| {\n                                let (r#type, parameter) = rtcp_feedback.as_type_parameter();\n                                rtp_parameters::RtcpFeedback {\n                                    type_: r#type.to_string(),\n                                    parameter: Some(parameter.to_string()),\n                                }\n                            })\n                            .collect(),\n                    ),\n                })\n                .collect(),\n            header_extensions: self\n                .header_extensions\n                .iter()\n                .map(\n                    |header_extension_parameters| rtp_parameters::RtpHeaderExtensionParameters {\n                        uri: header_extension_parameters.uri.to_fbs(),\n                        id: header_extension_parameters.id as u8,\n                        encrypt: header_extension_parameters.encrypt,\n                        parameters: None,\n                    },\n                )\n                .collect(),\n            encodings: self\n                .encodings\n                .iter()\n                .map(|encoding| rtp_parameters::RtpEncodingParameters {\n                    ssrc: encoding.ssrc,\n                    rid: encoding.rid.clone(),\n                    codec_payload_type: encoding.codec_payload_type,\n                    rtx: encoding\n                        .rtx\n                        .map(|rtx| Box::new(rtp_parameters::Rtx { ssrc: rtx.ssrc })),\n                    dtx: encoding.dtx.unwrap_or_default(),\n                    scalability_mode: if encoding.scalability_mode.is_none() {\n                        None\n                    } else {\n                        Some(encoding.scalability_mode.as_str().to_string())\n                    },\n                    max_bitrate: encoding.max_bitrate,\n                })\n                .collect(),\n            rtcp: Box::new(rtp_parameters::RtcpParameters {\n                cname: self.rtcp.cname.clone(),\n                reduced_size: self.rtcp.reduced_size,\n            }),\n            msid: self.msid.clone(),\n        }\n    }\n}\n\nimpl ToFbs for RtpEncodingParameters {\n    type FbsType = rtp_parameters::RtpEncodingParameters;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        rtp_parameters::RtpEncodingParameters {\n            ssrc: self.ssrc,\n            rid: self.rid.clone(),\n            codec_payload_type: self.codec_payload_type,\n            rtx: self\n                .rtx\n                .map(|rtx| Box::new(rtp_parameters::Rtx { ssrc: rtx.ssrc })),\n            dtx: self.dtx.unwrap_or_default(),\n            scalability_mode: if self.scalability_mode.is_none() {\n                None\n            } else {\n                Some(self.scalability_mode.as_str().to_string())\n            },\n            max_bitrate: self.max_bitrate,\n        }\n    }\n}\n"
  },
  {
    "path": "rust/src/scalability_modes.rs",
    "content": "//! Scalability mode.\n\nuse once_cell::sync::OnceCell;\nuse regex::Regex;\nuse serde::{de, Deserialize, Deserializer, Serialize, Serializer};\nuse std::fmt;\nuse std::num::NonZeroU8;\nuse std::str::FromStr;\nuse thiserror::Error;\n\n/// Scalability mode.\n///\n/// Most modes match [webrtc-svc](https://w3c.github.io/webrtc-svc/), but custom ones are also\n/// supported by mediasoup.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]\npub enum ScalabilityMode {\n    /// No scalability used, there is just one spatial and one temporal layer.\n    None,\n    /// L1T2.\n    L1T2,\n    /// L1T2h.\n    L1T2h,\n    /// L1T3.\n    L1T3,\n    /// L1T3h.\n    L1T3h,\n    /// L2T1.\n    L2T1,\n    /// L2T1h.\n    L2T1h,\n    /// L2T1_KEY.\n    L2T1Key,\n    /// L2T2.\n    L2T2,\n    /// L2T2h.\n    L2T2h,\n    /// L2T2_KEY.\n    L2T2Key,\n    /// L2T2_KEY_SHIFT.\n    L2T2KeyShift,\n    /// L2T3.\n    L2T3,\n    /// L2T3h.\n    L2T3h,\n    /// L2T3_KEY.\n    L2T3Key,\n    /// L2T3_KEY_SHIFT.\n    L2T3KeyShift,\n    /// L3T1.\n    L3T1,\n    /// L3T1h.\n    L3T1h,\n    /// L3T1_KEY.\n    L3T1Key,\n    /// L3T2.\n    L3T2,\n    /// L3T2h.\n    L3T2h,\n    /// L3T2_KEY.\n    L3T2Key,\n    /// L3T2_KEY_SHIFT.\n    L3T2KeyShift,\n    /// L3T3.\n    L3T3,\n    /// L3T3h.\n    L3T3h,\n    /// L3T3_KEY.\n    L3T3Key,\n    /// L3T3_KEY_SHIFT.\n    L3T3KeyShift,\n    /// S2T1.\n    S2T1,\n    /// S2T1h.\n    S2T1h,\n    /// S2T2.\n    S2T2,\n    /// S2T2h.\n    S2T2h,\n    /// S2T3.\n    S2T3,\n    /// S2T3h.\n    S2T3h,\n    /// S3T1.\n    S3T1,\n    /// S3T1h.\n    S3T1h,\n    /// S3T2.\n    S3T2,\n    /// S3T2h.\n    S3T2h,\n    /// S3T3.\n    S3T3,\n    /// S3T3h.\n    S3T3h,\n    /// Custom scalability mode not defined in [webrtc-svc](https://w3c.github.io/webrtc-svc/).\n    Custom {\n        /// Scalability mode as string\n        scalability_mode: String,\n        /// Number of spatial layers.\n        spatial_layers: NonZeroU8,\n        /// Number of temporal layers.\n        temporal_layers: NonZeroU8,\n        /// K-SVC mode.\n        ksvc: bool,\n    },\n}\n\nimpl Default for ScalabilityMode {\n    fn default() -> Self {\n        Self::None\n    }\n}\n\n/// Error that caused [`ScalabilityMode`] parsing error.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum ParseScalabilityModeError {\n    /// Invalid input string\n    #[error(\"Invalid Scalability Mode input string\")]\n    InvalidInput,\n}\n\nimpl FromStr for ScalabilityMode {\n    type Err = ParseScalabilityModeError;\n\n    fn from_str(scalability_mode: &str) -> Result<Self, Self::Err> {\n        Ok(match scalability_mode {\n            \"S1T1\" => Self::None,\n            \"L1T2\" => Self::L1T2,\n            \"L1T2h\" => Self::L1T2h,\n            \"L1T3\" => Self::L1T3,\n            \"L1T3h\" => Self::L1T3h,\n            \"L2T1\" => Self::L2T1,\n            \"L2T1h\" => Self::L2T1h,\n            \"L2T1_KEY\" => Self::L2T1Key,\n            \"L2T2\" => Self::L2T2,\n            \"L2T2h\" => Self::L2T2h,\n            \"L2T2_KEY\" => Self::L2T2Key,\n            \"L2T2_KEY_SHIFT\" => Self::L2T2KeyShift,\n            \"L2T3\" => Self::L2T3,\n            \"L2T3h\" => Self::L2T3h,\n            \"L2T3_KEY\" => Self::L2T3Key,\n            \"L2T3_KEY_SHIFT\" => Self::L2T3KeyShift,\n            \"L3T1\" => Self::L3T1,\n            \"L3T1h\" => Self::L3T1h,\n            \"L3T1_KEY\" => Self::L3T1Key,\n            \"L3T2\" => Self::L3T2,\n            \"L3T2h\" => Self::L3T2h,\n            \"L3T2_KEY\" => Self::L3T2Key,\n            \"L3T2_KEY_SHIFT\" => Self::L3T2KeyShift,\n            \"L3T3\" => Self::L3T3,\n            \"L3T3h\" => Self::L3T3h,\n            \"L3T3_KEY\" => Self::L3T3Key,\n            \"L3T3_KEY_SHIFT\" => Self::L3T3KeyShift,\n            \"S2T1\" => Self::S2T1,\n            \"S2T1h\" => Self::S2T1h,\n            \"S2T2\" => Self::S2T2,\n            \"S2T2h\" => Self::S2T2h,\n            \"S2T3\" => Self::S2T3,\n            \"S2T3h\" => Self::S2T3h,\n            \"S3T1\" => Self::S3T1,\n            \"S3T1h\" => Self::S3T1h,\n            \"S3T2\" => Self::S3T2,\n            \"S3T2h\" => Self::S3T2h,\n            \"S3T3\" => Self::S3T3,\n            \"S3T3h\" => Self::S3T3h,\n            scalability_mode => {\n                static SCALABILITY_MODE_REGEX: OnceCell<Regex> = OnceCell::new();\n\n                SCALABILITY_MODE_REGEX\n                    .get_or_init(|| Regex::new(r\"^[LS]([1-9][0-9]?)T([1-9][0-9]?)(_KEY)?\").unwrap())\n                    .captures(scalability_mode)\n                    .map(|captures| Self::Custom {\n                        scalability_mode: scalability_mode.to_string(),\n                        spatial_layers: captures.get(1).unwrap().as_str().parse().unwrap(),\n                        temporal_layers: captures.get(2).unwrap().as_str().parse().unwrap(),\n                        ksvc: captures.get(3).is_some(),\n                    })\n                    .ok_or(ParseScalabilityModeError::InvalidInput)?\n            }\n        })\n    }\n}\n\nimpl std::fmt::Display for ScalabilityMode {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.as_str())\n    }\n}\n\nimpl ScalabilityMode {\n    /// Returns true if there is no scalability.\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    /// Number of spatial layers.\n    pub fn spatial_layers(&self) -> NonZeroU8 {\n        match self {\n            Self::None | Self::L1T2 | Self::L1T2h | Self::L1T3 | Self::L1T3h => {\n                NonZeroU8::new(1).unwrap()\n            }\n            Self::L2T1\n            | Self::L2T1h\n            | Self::L2T1Key\n            | Self::L2T2\n            | Self::L2T2h\n            | Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L2T3\n            | Self::L2T3h\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::S2T1\n            | Self::S2T1h\n            | Self::S2T2\n            | Self::S2T2h\n            | Self::S2T3\n            | Self::S2T3h => NonZeroU8::new(2).unwrap(),\n            Self::L3T1\n            | Self::L3T1h\n            | Self::L3T1Key\n            | Self::L3T2\n            | Self::L3T2h\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::L3T3\n            | Self::L3T3h\n            | Self::L3T3Key\n            | Self::L3T3KeyShift\n            | Self::S3T1\n            | Self::S3T1h\n            | Self::S3T2\n            | Self::S3T2h\n            | Self::S3T3\n            | Self::S3T3h => NonZeroU8::new(3).unwrap(),\n            Self::Custom { spatial_layers, .. } => *spatial_layers,\n        }\n    }\n\n    /// Number of temporal layers.\n    pub fn temporal_layers(&self) -> NonZeroU8 {\n        match self {\n            Self::None\n            | Self::L2T1\n            | Self::L2T1h\n            | Self::L2T1Key\n            | Self::L3T1\n            | Self::L3T1h\n            | Self::L3T1Key\n            | Self::S2T1\n            | Self::S2T1h\n            | Self::S3T1\n            | Self::S3T1h => NonZeroU8::new(1).unwrap(),\n            Self::L1T2\n            | Self::L1T2h\n            | Self::L2T2\n            | Self::L2T2h\n            | Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L3T2\n            | Self::L3T2h\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::S2T2\n            | Self::S2T2h\n            | Self::S3T2\n            | Self::S3T2h => NonZeroU8::new(2).unwrap(),\n            Self::L1T3\n            | Self::L1T3h\n            | Self::L2T3\n            | Self::L2T3h\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::L3T3\n            | Self::L3T3h\n            | Self::L3T3Key\n            | Self::L3T3KeyShift\n            | Self::S2T3\n            | Self::S2T3h\n            | Self::S3T3\n            | Self::S3T3h => NonZeroU8::new(3).unwrap(),\n            Self::Custom {\n                temporal_layers, ..\n            } => *temporal_layers,\n        }\n    }\n\n    /// K-SVC mode.\n    pub fn ksvc(&self) -> bool {\n        match self {\n            Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::L3T1Key\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::L3T3Key\n            | Self::L3T3KeyShift => true,\n            Self::Custom { ksvc, .. } => *ksvc,\n            _ => false,\n        }\n    }\n\n    /// String representation of scalability mode.\n    pub fn as_str(&self) -> &str {\n        match self {\n            Self::None => \"S1T1\",\n            Self::L1T2 => \"L1T2\",\n            Self::L1T2h => \"L1T2h\",\n            Self::L1T3 => \"L1T3\",\n            Self::L1T3h => \"L1T3h\",\n            Self::L2T1 => \"L2T1\",\n            Self::L2T1h => \"L2T1h\",\n            Self::L2T1Key => \"L2T1_KEY\",\n            Self::L2T2 => \"L2T2\",\n            Self::L2T2h => \"L2T2h\",\n            Self::L2T2Key => \"L2T2_KEY\",\n            Self::L2T2KeyShift => \"L2T2_KEY_SHIFT\",\n            Self::L2T3 => \"L2T3\",\n            Self::L2T3h => \"L2T3h\",\n            Self::L2T3Key => \"L2T3_KEY\",\n            Self::L2T3KeyShift => \"L2T3_KEY_SHIFT\",\n            Self::L3T1 => \"L3T1\",\n            Self::L3T1h => \"L3T1h\",\n            Self::L3T1Key => \"L3T1_KEY\",\n            Self::L3T2 => \"L3T2\",\n            Self::L3T2h => \"L3T2h\",\n            Self::L3T2Key => \"L3T2_KEY\",\n            Self::L3T2KeyShift => \"L3T2_KEY_SHIFT\",\n            Self::L3T3 => \"L3T3\",\n            Self::L3T3h => \"L3T3h\",\n            Self::L3T3Key => \"L3T3_KEY\",\n            Self::L3T3KeyShift => \"L3T3_KEY_SHIFT\",\n            Self::S2T1 => \"S2T1\",\n            Self::S2T1h => \"S2T1h\",\n            Self::S2T2 => \"S2T2\",\n            Self::S2T2h => \"S2T2h\",\n            Self::S2T3 => \"S2T3\",\n            Self::S2T3h => \"S2T3h\",\n            Self::S3T1 => \"S3T1\",\n            Self::S3T1h => \"S3T1h\",\n            Self::S3T2 => \"S3T2\",\n            Self::S3T2h => \"S3T2h\",\n            Self::S3T3 => \"S3T3\",\n            Self::S3T3h => \"S3T3h\",\n            Self::Custom {\n                scalability_mode, ..\n            } => scalability_mode,\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for ScalabilityMode {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct ScalabilityModeVisitor;\n\n        impl<'de> de::Visitor<'de> for ScalabilityModeVisitor {\n            type Value = ScalabilityMode;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n                formatter.write_str(r#\"Scalability mode string like \"S1T3\"\"#)\n            }\n\n            #[inline]\n            fn visit_none<E>(self) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                Ok(ScalabilityMode::None)\n            }\n\n            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                v.parse().map_err(de::Error::custom)\n            }\n        }\n\n        deserializer.deserialize_str(ScalabilityModeVisitor)\n    }\n}\n\nimpl Serialize for ScalabilityMode {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.serialize_str(self.as_str())\n    }\n}\n"
  },
  {
    "path": "rust/src/sctp_parameters.rs",
    "content": "//! SCTP parameters.\n\nuse crate::fbs::{FromFbs, ToFbs};\nuse mediasoup_sys::fbs::sctp_parameters;\nuse mediasoup_types::sctp_parameters::*;\n\nimpl ToFbs for NumSctpStreams {\n    type FbsType = sctp_parameters::NumSctpStreams;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        sctp_parameters::NumSctpStreams {\n            os: self.os,\n            mis: self.mis,\n        }\n    }\n}\n\nimpl FromFbs for SctpParameters {\n    type FbsType = sctp_parameters::SctpParameters;\n\n    fn from_fbs(parameters: &Self::FbsType) -> Self {\n        Self {\n            port: parameters.port,\n            os: parameters.os,\n            mis: parameters.mis,\n            max_message_size: parameters.max_message_size,\n        }\n    }\n}\n\nimpl FromFbs for SctpStreamParameters {\n    type FbsType = sctp_parameters::SctpStreamParameters;\n\n    fn from_fbs(stream_parameters: &Self::FbsType) -> Self {\n        Self {\n            stream_id: stream_parameters.stream_id,\n            ordered: stream_parameters.ordered.unwrap_or(false),\n            max_packet_life_time: stream_parameters.max_packet_life_time,\n            max_retransmits: stream_parameters.max_retransmits,\n        }\n    }\n}\n\nimpl ToFbs for SctpStreamParameters {\n    type FbsType = sctp_parameters::SctpStreamParameters;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        sctp_parameters::SctpStreamParameters {\n            stream_id: self.stream_id,\n            ordered: Some(self.ordered),\n            max_packet_life_time: self.max_packet_life_time,\n            max_retransmits: self.max_retransmits,\n        }\n    }\n}\n"
  },
  {
    "path": "rust/src/srtp_parameters.rs",
    "content": "//! SRTP parameters.\n\nuse crate::fbs::{FromFbs, ToFbs};\nuse mediasoup_sys::fbs::srtp_parameters;\nuse mediasoup_types::srtp_parameters::*;\n\nimpl FromFbs for SrtpParameters {\n    type FbsType = srtp_parameters::SrtpParameters;\n\n    fn from_fbs(tuple: &Self::FbsType) -> Self {\n        Self {\n            crypto_suite: SrtpCryptoSuite::from_fbs(&tuple.crypto_suite),\n            key_base64: String::from(tuple.key_base64.as_str()),\n        }\n    }\n}\n\nimpl ToFbs for SrtpParameters {\n    type FbsType = srtp_parameters::SrtpParameters;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        srtp_parameters::SrtpParameters {\n            crypto_suite: SrtpCryptoSuite::to_fbs(&self.crypto_suite),\n            key_base64: String::from(self.key_base64.as_str()),\n        }\n    }\n}\n\nimpl FromFbs for SrtpCryptoSuite {\n    type FbsType = srtp_parameters::SrtpCryptoSuite;\n\n    fn from_fbs(crypto_suite: &Self::FbsType) -> Self {\n        match crypto_suite {\n            srtp_parameters::SrtpCryptoSuite::AeadAes256Gcm => Self::AeadAes256Gcm,\n            srtp_parameters::SrtpCryptoSuite::AeadAes128Gcm => Self::AeadAes128Gcm,\n            srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha180 => Self::AesCm128HmacSha180,\n            srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha132 => Self::AesCm128HmacSha132,\n        }\n    }\n}\n\nimpl ToFbs for SrtpCryptoSuite {\n    type FbsType = srtp_parameters::SrtpCryptoSuite;\n\n    fn to_fbs(&self) -> Self::FbsType {\n        match self {\n            Self::AeadAes256Gcm => srtp_parameters::SrtpCryptoSuite::AeadAes256Gcm,\n            Self::AeadAes128Gcm => srtp_parameters::SrtpCryptoSuite::AeadAes128Gcm,\n            Self::AesCm128HmacSha180 => srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha180,\n            Self::AesCm128HmacSha132 => srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha132,\n        }\n    }\n}\n"
  },
  {
    "path": "rust/src/supported_rtp_capabilities.rs",
    "content": "//! RTP capabilities supported by mediasoup.\n\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtpCapabilities, RtpCodecCapability,\n    RtpCodecParametersParameters, RtpHeaderExtension, RtpHeaderExtensionDirection,\n    RtpHeaderExtensionUri,\n};\nuse std::num::{NonZeroU32, NonZeroU8};\n\n/// Get a mediasoup supported RTP capabilities.\n///\n/// # Notes on usage\n/// Those are NOT the RTP capabilities needed by mediasoup-client's\n/// [device.load()](https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-load) and\n/// libmediasoupclient's\n/// [device.Load()](https://mediasoup.org/documentation/v3/libmediasoupclient/api/#device-Load)\n/// methods. There you must use\n/// [`Router::rtp_capabilities`](crate::router::Router::rtp_capabilities) getter instead.\n#[must_use]\npub fn get_supported_rtp_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(4).unwrap(),\n                // Quad channel\n                parameters: RtpCodecParametersParameters::from([\n                    (\"channel_mapping\", \"0,1,2,3\".into()),\n                    (\"num_streams\", 2_u32.into()),\n                    (\"coupled_streams\", 2_u32.into()),\n                ]),\n                rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(6).unwrap(),\n                // 5.1\n                parameters: RtpCodecParametersParameters::from([\n                    (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                    (\"num_streams\", 4_u32.into()),\n                    (\"coupled_streams\", 2_u32.into()),\n                ]),\n                rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(8).unwrap(),\n                // 7.1\n                parameters: RtpCodecParametersParameters::from([\n                    (\"channel_mapping\", \"0,6,1,2,3,4,5,7\".into()),\n                    (\"num_streams\", 5_u32.into()),\n                    (\"coupled_streams\", 3_u32.into()),\n                ]),\n                rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Pcmu,\n                preferred_payload_type: Some(0),\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Pcma,\n                preferred_payload_type: Some(8),\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Isac,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(32000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Isac,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(16000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::G722,\n                preferred_payload_type: Some(9),\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Ilbc,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Silk,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(24000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Silk,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(16000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Silk,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(12000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Silk,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::TransportCc],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Cn,\n                preferred_payload_type: Some(13),\n                clock_rate: NonZeroU32::new(32000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Cn,\n                preferred_payload_type: Some(13),\n                clock_rate: NonZeroU32::new(16000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Cn,\n                preferred_payload_type: Some(13),\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::TelephoneEvent,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::TelephoneEvent,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(32000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::TelephoneEvent,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(16000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::TelephoneEvent,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(8000).unwrap(),\n                channels: NonZeroU8::new(1).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Vp9,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::H264,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\n                    \"level-asymmetry-allowed\",\n                    1_u32.into(),\n                )]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::AV1,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n        ],\n        header_extensions: vec![\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::RtpStreamId,\n                preferred_id: 2,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::RecvOnly,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::RepairRtpStreamId,\n                preferred_id: 3,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::RecvOnly,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            // NOTE: For audio we just enable transport-wide-cc-01 when receiving media.\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::TransportWideCcDraft01,\n                preferred_id: 5,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::RecvOnly,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::TransportWideCcDraft01,\n                preferred_id: 5,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                preferred_id: 6,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                preferred_id: 7,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::RecvOnly,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::VideoOrientation,\n                preferred_id: 8,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::TimeOffset,\n                preferred_id: 9,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                preferred_id: 10,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                preferred_id: 10,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::PlayoutDelay,\n                preferred_id: 11,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::PlayoutDelay,\n                preferred_id: 11,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                preferred_id: 12,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                preferred_id: 12,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n        ],\n    }\n}\n"
  },
  {
    "path": "rust/src/webrtc_server/tests.rs",
    "content": "use crate::webrtc_server::{WebRtcServerListenInfos, WebRtcServerOptions};\nuse crate::worker::{Worker, WorkerSettings};\nuse crate::worker_manager::WorkerManager;\nuse futures_lite::future;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse portpicker::pick_unused_port;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn worker_close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let port = pick_unused_port().unwrap();\n\n        let webrtc_server = worker\n            .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new(\n                ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                },\n            )))\n            .await\n            .expect(\"Failed to create WebRTC server\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = webrtc_server.on_close(move || {\n            let _ = close_tx.send(());\n        });\n\n        let (mut worker_close_tx, worker_close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = webrtc_server.on_worker_close(move || {\n            let _ = worker_close_tx.send(());\n        });\n\n        worker.close();\n\n        worker_close_rx\n            .await\n            .expect(\"Failed to receive worker_close event\");\n        close_rx.await.expect(\"Failed to receive close event\");\n\n        assert!(webrtc_server.closed());\n    });\n}\n"
  },
  {
    "path": "rust/src/webrtc_server.rs",
    "content": "//! A WebRTC server brings the ability to listen on a single UDP/TCP port for multiple\n//! `WebRtcTransport`s.\n//!\n//! A WebRTC server exists within the context of a [`Worker`], meaning that if your app launches N\n//! workers it also needs to create N WebRTC servers listening on different ports (to not collide).\n//! The WebRTC transport implementation of mediasoup is\n//! [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate\n//! ICE connections but expects ICE Binding Requests from endpoints.\n\n#[cfg(test)]\nmod tests;\n\nuse crate::messages::{WebRtcServerCloseRequest, WebRtcServerDumpRequest};\nuse crate::transport::TransportId;\nuse crate::uuid_based_wrapper_type;\nuse crate::webrtc_transport::WebRtcTransport;\nuse crate::worker::{Channel, RequestError, Worker};\nuse async_executor::Executor;\nuse event_listener_primitives::{BagOnce, HandlerId};\nuse hash_hasher::HashedSet;\nuse log::{debug, error};\nuse mediasoup_types::data_structures::{AppData, ListenInfo};\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::fmt;\nuse std::net::IpAddr;\nuse std::ops::Deref;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\nuse thiserror::Error;\n\nuuid_based_wrapper_type!(\n    /// [`WebRtcServer`] identifier.\n    WebRtcServerId\n);\n\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[doc(hidden)]\npub struct WebRtcServerIpPort {\n    pub ip: IpAddr,\n    pub port: u16,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct WebRtcServerIceUsernameFragment {\n    pub local_ice_username_fragment: String,\n    #[serde(rename = \"webRtcTransportId\")]\n    pub webrtc_transport_id: TransportId,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct WebRtcServerTupleHash {\n    pub tuple_hash: u64,\n    #[serde(rename = \"webRtcTransportId\")]\n    pub webrtc_transport_id: TransportId,\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct WebRtcServerDump {\n    pub id: WebRtcServerId,\n    pub udp_sockets: Vec<WebRtcServerIpPort>,\n    pub tcp_servers: Vec<WebRtcServerIpPort>,\n    #[serde(rename = \"webRtcTransportIds\")]\n    pub webrtc_transport_ids: HashedSet<TransportId>,\n    pub local_ice_username_fragments: Vec<WebRtcServerIceUsernameFragment>,\n    pub tuple_hashes: Vec<WebRtcServerTupleHash>,\n}\n\n/// Struct that protects an invariant of having non-empty list of listen infos.\n#[derive(Debug, Clone, Eq, PartialEq, Serialize)]\npub struct WebRtcServerListenInfos(Vec<ListenInfo>);\n\nimpl WebRtcServerListenInfos {\n    /// Create WebRTC server listen infos with given info populated initially.\n    #[must_use]\n    pub fn new(listen_info: ListenInfo) -> Self {\n        Self(vec![listen_info])\n    }\n\n    /// Insert another listen info.\n    #[must_use]\n    pub fn insert(mut self, listen_info: ListenInfo) -> Self {\n        self.0.push(listen_info);\n        self\n    }\n}\n\nimpl Deref for WebRtcServerListenInfos {\n    type Target = Vec<ListenInfo>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\n/// Empty list of listen infos provided, should have at least one element.\n#[derive(Error, Debug, Eq, PartialEq)]\n#[error(\"Empty list of listen infos provided, should have at least one element\")]\npub struct EmptyListError;\n\nimpl TryFrom<Vec<ListenInfo>> for WebRtcServerListenInfos {\n    type Error = EmptyListError;\n\n    fn try_from(listen_infos: Vec<ListenInfo>) -> Result<Self, Self::Error> {\n        if listen_infos.is_empty() {\n            Err(EmptyListError)\n        } else {\n            Ok(Self(listen_infos))\n        }\n    }\n}\n\n/// [`WebRtcServer`] options.\n#[derive(Debug)]\n#[non_exhaustive]\npub struct WebRtcServerOptions {\n    /// Listening infos in order of preference (first one is the preferred one).\n    pub listen_infos: WebRtcServerListenInfos,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl WebRtcServerOptions {\n    /// Create [`WebRtcServer`] options with given listen infos.\n    pub fn new(listen_infos: WebRtcServerListenInfos) -> Self {\n        Self {\n            listen_infos,\n            app_data: AppData::default(),\n        }\n    }\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_webrtc_transport: BagOnce<Box<dyn Fn(&WebRtcTransport) + Send>>,\n    worker_close: BagOnce<Box<dyn FnOnce() + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: WebRtcServerId,\n    executor: Arc<Executor<'static>>,\n    channel: Channel,\n    handlers: Arc<Handlers>,\n    app_data: AppData,\n    worker: Worker,\n    closed: AtomicBool,\n    _on_worker_close_handler: Mutex<HandlerId>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close();\n    }\n}\n\nimpl Inner {\n    fn close(&self) {\n        if !self.closed.swap(true, Ordering::SeqCst) {\n            self.handlers.close.call_simple();\n\n            {\n                let channel = self.channel.clone();\n                let request = WebRtcServerCloseRequest {\n                    webrtc_server_id: self.id,\n                };\n\n                self.executor\n                    .spawn(async move {\n                        match channel.request(\"\", request).await {\n                            Err(RequestError::ChannelClosed) => {\n                                debug!(\n                                    \"WebRTC server closing failed on drop: Channel already closed\"\n                                );\n                            }\n                            Err(error) => {\n                                error!(\"WebRTC server closing failed on drop: {}\", error);\n                            }\n                            Ok(_) => {}\n                        }\n                    })\n                    .detach();\n            }\n        }\n    }\n}\n\n/// A WebRTC server brings the ability to listen on a single UDP/TCP port for multiple\n/// `WebRtcTransport`s.\n///\n/// A WebRTC server exists within the context of a [`Worker`], meaning that if your app launches N\n/// workers it also needs to create N WebRTC servers listening on different ports (to not collide).\n/// The WebRTC transport implementation of mediasoup is\n/// [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate\n/// ICE connections but expects ICE Binding Requests from endpoints.\n#[derive(Clone)]\npub struct WebRtcServer {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for WebRtcServer {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WebRtcServer\")\n            .field(\"id\", &self.inner.id)\n            .field(\"worker\", &self.inner.worker)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl WebRtcServer {\n    pub(crate) fn new(\n        id: WebRtcServerId,\n        executor: Arc<Executor<'static>>,\n        channel: Channel,\n        app_data: AppData,\n        worker: Worker,\n    ) -> Self {\n        let handlers = Arc::<Handlers>::default();\n        let inner_weak = Arc::<Mutex<Option<Weak<Inner>>>>::default();\n        let on_worker_close_handler = worker.on_close({\n            let inner_weak = Arc::clone(&inner_weak);\n\n            move || {\n                let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade);\n                if let Some(inner) = maybe_inner {\n                    inner.handlers.worker_close.call_simple();\n                    if !inner.closed.swap(true, Ordering::SeqCst) {\n                        inner.handlers.close.call_simple();\n                    }\n                }\n            }\n        });\n        let inner = Arc::new(Inner {\n            id,\n            executor,\n            channel,\n            handlers,\n            app_data,\n            worker,\n            closed: AtomicBool::new(false),\n            _on_worker_close_handler: Mutex::new(on_worker_close_handler),\n        });\n\n        inner_weak.lock().replace(Arc::downgrade(&inner));\n\n        Self { inner }\n    }\n\n    /// Router id.\n    #[must_use]\n    pub fn id(&self) -> WebRtcServerId {\n        self.inner.id\n    }\n\n    /// Worker to which WebRTC server belongs.\n    pub fn worker(&self) -> &Worker {\n        &self.inner.worker\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    /// Whether WebRTC server is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump WebRTC server.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<WebRtcServerDump, RequestError> {\n        debug!(\"dump()\");\n\n        self.inner\n            .channel\n            .request(self.id(), WebRtcServerDumpRequest {})\n            .await\n    }\n\n    /// Callback is called when the worker this WebRTC server belongs to is closed for whatever\n    /// reason.\n    /// The WebRtc server itself is also closed. A `on_webrtc_server_close` callbacks are\n    /// triggered in all relevant WebRTC transports.\n    pub fn on_worker_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.worker_close.add(Box::new(callback))\n    }\n\n    /// Callback is called when new [`WebRtcTransport`] is added that uses this WebRTC server.\n    pub fn on_new_webrtc_transport<F>(&self, callback: F) -> HandlerId\n    where\n        F: Fn(&WebRtcTransport) + Send + 'static,\n    {\n        self.inner\n            .handlers\n            .new_webrtc_transport\n            .add(Box::new(callback))\n    }\n\n    /// Callback is called when the WebRTC server is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if WebRTC server is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    pub(crate) fn notify_new_webrtc_transport(&self, webrtc_transport: &WebRtcTransport) {\n        self.inner\n            .handlers\n            .new_webrtc_transport\n            .call(|callback| callback(webrtc_transport));\n    }\n\n    #[cfg(test)]\n    pub(crate) fn close(&self) {\n        self.inner.close();\n    }\n}\n"
  },
  {
    "path": "rust/src/worker/channel.rs",
    "content": "use crate::messages::{Notification, Request};\nuse crate::worker::common::{EventHandlers, SubscriptionTarget, WeakEventHandlers};\nuse crate::worker::utils;\nuse crate::worker::utils::{PreparedChannelRead, PreparedChannelWrite};\nuse crate::worker::{RequestError, SubscriptionHandler};\nuse atomic_take::AtomicTake;\nuse hash_hasher::HashedMap;\nuse log::{debug, error, trace, warn};\nuse lru::LruCache;\nuse mediasoup_sys::fbs::{message, notification, response};\nuse mediasoup_sys::UvAsyncT;\nuse parking_lot::Mutex;\nuse planus::ReadAsRoot;\nuse serde::Deserialize;\nuse std::collections::VecDeque;\nuse std::fmt::{Debug, Display};\nuse std::num::NonZeroUsize;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::{Arc, Weak};\nuse thiserror::Error;\nuse uuid::Uuid;\n\n#[derive(Debug, Deserialize)]\n#[serde(untagged)]\npub(super) enum InternalMessage {\n    /// Debug log\n    #[serde(skip)]\n    Debug(String),\n    /// Warn log\n    #[serde(skip)]\n    Warn(String),\n    /// Error log\n    #[serde(skip)]\n    Error(String),\n    /// Dump log\n    #[serde(skip)]\n    Dump(String),\n    /// Unknown\n    #[serde(skip)]\n    Unexpected(Vec<u8>),\n}\n\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum NotificationError {\n    #[error(\"Channel already closed\")]\n    ChannelClosed,\n}\n\n/// Flabtuffers notification parse error.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum NotificationParseError {\n    /// Invalid event\n    #[error(\"Invalid event\")]\n    InvalidEvent,\n}\n\n#[allow(clippy::type_complexity)]\npub(crate) struct BufferMessagesGuard {\n    target_id: SubscriptionTarget,\n    buffered_notifications_for: Arc<Mutex<HashedMap<SubscriptionTarget, Vec<Vec<u8>>>>>,\n    event_handlers_weak:\n        WeakEventHandlers<Arc<dyn Fn(notification::NotificationRef<'_>) + Send + Sync + 'static>>,\n}\n\nimpl Drop for BufferMessagesGuard {\n    fn drop(&mut self) {\n        let mut buffered_notifications_for = self.buffered_notifications_for.lock();\n        if let Some(notifications) = buffered_notifications_for.remove(&self.target_id) {\n            if let Some(event_handlers) = self.event_handlers_weak.upgrade() {\n                for bytes in notifications {\n                    let message_ref = message::MessageRef::read_as_root(&bytes).unwrap();\n\n                    let message::BodyRef::Notification(notification_ref) =\n                        message_ref.data().unwrap()\n                    else {\n                        panic!(\"Wrong notification stored: {message_ref:?}\");\n                    };\n\n                    event_handlers\n                        .call_callbacks_with_single_value(&self.target_id, notification_ref);\n                }\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\nenum ChannelReceiveMessage<'a> {\n    Notification(notification::NotificationRef<'a>),\n    Response(response::ResponseRef<'a>),\n    Event(InternalMessage),\n}\n\n// Remove the first 4 bytes which represent the buffer size.\n// NOTE: The prefix is only needed for NodeJS.\nfn unprefix_message(bytes: &[u8]) -> &[u8] {\n    &bytes[4..]\n}\n\nfn deserialize_message(bytes: &[u8]) -> ChannelReceiveMessage<'_> {\n    let message_ref = message::MessageRef::read_as_root(bytes).unwrap();\n\n    match message_ref.data().unwrap() {\n        message::BodyRef::Log(data) => match data.data().unwrap().chars().next() {\n            // Debug log\n            Some('D') => ChannelReceiveMessage::Event(InternalMessage::Debug(\n                String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(),\n            )),\n            // Warn log\n            Some('W') => ChannelReceiveMessage::Event(InternalMessage::Warn(\n                String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(),\n            )),\n            // Error log\n            Some('E') => ChannelReceiveMessage::Event(InternalMessage::Error(\n                String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(),\n            )),\n            // Dump log\n            Some('X') => ChannelReceiveMessage::Event(InternalMessage::Dump(\n                String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(),\n            )),\n            // This should never happen.\n            _ => ChannelReceiveMessage::Event(InternalMessage::Unexpected(Vec::from(bytes))),\n        },\n        message::BodyRef::Notification(data) => ChannelReceiveMessage::Notification(data),\n        message::BodyRef::Response(data) => ChannelReceiveMessage::Response(data),\n\n        _ => ChannelReceiveMessage::Event(InternalMessage::Unexpected(Vec::from(bytes))),\n    }\n}\n\nstruct ResponseError {\n    reason: String,\n}\n\ntype FBSResponseResult = Result<Option<Vec<u8>>, ResponseError>;\n\nstruct RequestDropGuard<'a> {\n    id: u32,\n    message: Arc<AtomicTake<Vec<u8>>>,\n    channel: &'a Channel,\n    removed: bool,\n}\n\nimpl<'a> Drop for RequestDropGuard<'a> {\n    fn drop(&mut self) {\n        if self.removed {\n            return;\n        }\n\n        // Drop pending message from memory\n        self.message.take();\n        // Remove request handler from the container\n        if let Some(requests_container) = self.channel.inner.requests_container_weak.upgrade() {\n            requests_container.lock().handlers.remove(&self.id);\n        }\n    }\n}\n\nimpl<'a> RequestDropGuard<'a> {\n    fn remove(mut self) {\n        self.removed = true;\n    }\n}\n\n#[derive(Default)]\nstruct FBSRequestsContainer {\n    next_id: u32,\n    handlers: HashedMap<u32, async_oneshot::Sender<FBSResponseResult>>,\n}\n\nstruct OutgoingMessageBuffer {\n    handle: Option<UvAsyncT>,\n    messages: VecDeque<Arc<AtomicTake<Vec<u8>>>>,\n}\n\n// TODO: use 'close' in 'request' method.\n#[allow(clippy::type_complexity, dead_code)]\nstruct Inner {\n    outgoing_message_buffer: Arc<Mutex<OutgoingMessageBuffer>>,\n    internal_message_receiver: async_channel::Receiver<InternalMessage>,\n    requests_container_weak: Weak<Mutex<FBSRequestsContainer>>,\n    buffered_notifications_for: Arc<Mutex<HashedMap<SubscriptionTarget, Vec<Vec<u8>>>>>,\n    event_handlers_weak:\n        WeakEventHandlers<Arc<dyn Fn(notification::NotificationRef<'_>) + Send + Sync + 'static>>,\n    worker_closed: Arc<AtomicBool>,\n    closed: AtomicBool,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        self.internal_message_receiver.close();\n    }\n}\n\n#[derive(Clone)]\npub(crate) struct Channel {\n    inner: Arc<Inner>,\n}\n\nimpl Channel {\n    pub(super) fn new(\n        worker_closed: Arc<AtomicBool>,\n    ) -> (Self, PreparedChannelRead, PreparedChannelWrite) {\n        let outgoing_message_buffer = Arc::new(Mutex::new(OutgoingMessageBuffer {\n            handle: None,\n            messages: VecDeque::with_capacity(10),\n        }));\n        let requests_container = Arc::<Mutex<FBSRequestsContainer>>::default();\n        let requests_container_weak = Arc::downgrade(&requests_container);\n        let buffered_notifications_for =\n            Arc::<Mutex<HashedMap<SubscriptionTarget, Vec<Vec<u8>>>>>::default();\n        let event_handlers = EventHandlers::new();\n        let event_handlers_weak = event_handlers.downgrade();\n\n        let prepared_channel_read = utils::prepare_channel_read_fn({\n            let outgoing_message_buffer = Arc::clone(&outgoing_message_buffer);\n\n            move |handle| {\n                let mut outgoing_message_buffer = outgoing_message_buffer.lock();\n                if outgoing_message_buffer.handle.is_none() {\n                    outgoing_message_buffer.handle.replace(handle);\n                }\n\n                while let Some(maybe_message) = outgoing_message_buffer.messages.pop_front() {\n                    // Request might have already been cancelled\n                    if let Some(message) = maybe_message.take() {\n                        return Some(message);\n                    }\n                }\n\n                None\n            }\n        });\n\n        let (internal_message_sender, internal_message_receiver) = async_channel::unbounded();\n\n        let prepared_channel_write = utils::prepare_channel_write_fn({\n            let buffered_notifications_for = Arc::clone(&buffered_notifications_for);\n            // This this contain cache of targets that are known to not have buffering, so\n            // that we can avoid Mutex locking overhead for them\n            let mut non_buffered_notifications = LruCache::<SubscriptionTarget, ()>::new(\n                NonZeroUsize::new(1000).expect(\"Not zero; qed\"),\n            );\n\n            move |message| {\n                trace!(\"received raw message: {}\", String::from_utf8_lossy(message));\n\n                let message = unprefix_message(message);\n\n                match deserialize_message(message) {\n                    ChannelReceiveMessage::Notification(notification) => {\n                        let target_id = notification.handler_id().unwrap();\n                        // Target id can be either the worker PID or a UUID.\n                        let target_id = match target_id.parse::<u64>() {\n                            Ok(_) => SubscriptionTarget::String(target_id.to_string()),\n                            Err(_) => SubscriptionTarget::Uuid(Uuid::parse_str(target_id).unwrap()),\n                        };\n\n                        if !non_buffered_notifications.contains(&target_id) {\n                            let mut buffer_notifications_for = buffered_notifications_for.lock();\n                            // Check if we need to buffer notifications for this\n                            // target_id\n                            if let Some(list) = buffer_notifications_for.get_mut(&target_id) {\n                                // Store the whole message removing the size prefix.\n                                list.push(Vec::from(message));\n                                return;\n                            }\n\n                            // Remember we don't need to buffer these\n                            non_buffered_notifications.put(target_id.clone(), ());\n                        }\n                        event_handlers.call_callbacks_with_single_value(&target_id, notification);\n                    }\n                    ChannelReceiveMessage::Response(response) => {\n                        let sender = requests_container\n                            .lock()\n                            .handlers\n                            .remove(&response.id().unwrap());\n                        if let Some(mut sender) = sender {\n                            // Request did not succeed.\n                            if let Ok(Some(reason)) = response.reason() {\n                                let _ = sender.send(Err(ResponseError {\n                                    reason: reason.to_string(),\n                                }));\n                            }\n                            // Request succeeded.\n                            else {\n                                match response.body().expect(\"failed accessing response body\") {\n                                    // Response has body.\n                                    Some(_) => {\n                                        let _ = sender.send(Ok(Some(Vec::from(message))));\n                                    }\n                                    // Response does not have body.\n                                    None => {\n                                        let _ = sender.send(Ok(None));\n                                    }\n                                }\n                            }\n                        } else {\n                            warn!(\n                                \"received success response does not match any sent request [id:{}]\",\n                                response.id().unwrap(),\n                            );\n                        }\n                    }\n                    ChannelReceiveMessage::Event(event_message) => {\n                        let _ = internal_message_sender.try_send(event_message);\n                    }\n                }\n            }\n        });\n\n        let inner = Arc::new(Inner {\n            outgoing_message_buffer,\n            internal_message_receiver,\n            requests_container_weak,\n            buffered_notifications_for,\n            event_handlers_weak,\n            worker_closed,\n            closed: AtomicBool::new(false),\n        });\n\n        (\n            Self { inner },\n            prepared_channel_read,\n            prepared_channel_write,\n        )\n    }\n\n    pub(super) fn get_internal_message_receiver(&self) -> async_channel::Receiver<InternalMessage> {\n        self.inner.internal_message_receiver.clone()\n    }\n\n    /// This allows to enable buffering for messages for specific target while the target itself is\n    /// being created. This allows to avoid missing notifications due to race conditions.\n    pub(crate) fn buffer_messages_for(&self, target_id: SubscriptionTarget) -> BufferMessagesGuard {\n        let buffered_notifications_for = Arc::clone(&self.inner.buffered_notifications_for);\n        let event_handlers_weak = self.inner.event_handlers_weak.clone();\n        buffered_notifications_for\n            .lock()\n            .entry(target_id.clone())\n            .or_default();\n        BufferMessagesGuard {\n            target_id,\n            buffered_notifications_for,\n            event_handlers_weak,\n        }\n    }\n\n    pub(crate) async fn request<R, HandlerId>(\n        &self,\n        handler_id: HandlerId,\n        request: R,\n    ) -> Result<R::Response, RequestError>\n    where\n        R: Request<HandlerId = HandlerId> + 'static,\n        HandlerId: Display,\n    {\n        let id;\n        let (result_sender, result_receiver) = async_oneshot::oneshot();\n\n        {\n            let requests_container = match self.inner.requests_container_weak.upgrade() {\n                Some(requests_container_lock) => requests_container_lock,\n                None => {\n                    return Err(RequestError::ChannelClosed);\n                }\n            };\n            let mut requests_container_lock = requests_container.lock();\n\n            id = requests_container_lock.next_id;\n\n            requests_container_lock.next_id = requests_container_lock.next_id.wrapping_add(1);\n            requests_container_lock.handlers.insert(id, result_sender);\n        }\n\n        debug!(\"request() [method:{:?}, id:{}]\", R::METHOD, id);\n\n        let data = request.into_bytes(id, handler_id);\n\n        let buffer = Arc::new(AtomicTake::new(data));\n\n        {\n            let mut outgoing_message_buffer = self.inner.outgoing_message_buffer.lock();\n            outgoing_message_buffer\n                .messages\n                .push_back(Arc::clone(&buffer));\n            if let Some(handle) = outgoing_message_buffer.handle {\n                if self.inner.worker_closed.load(Ordering::Acquire) {\n                    // Forbid all requests after worker closing\n                    return Err(RequestError::ChannelClosed);\n                }\n                unsafe {\n                    // Notify worker that there is something to read\n                    let ret = mediasoup_sys::uv_async_send(handle);\n                    if ret != 0 {\n                        error!(\"uv_async_send call failed with code {}\", ret);\n\n                        return Err(RequestError::ChannelClosed);\n                    }\n                }\n            }\n        }\n\n        // Drop guard to make sure to drop pending request when future is cancelled\n        let request_drop_guard = RequestDropGuard {\n            id,\n            message: buffer,\n            channel: self,\n            removed: false,\n        };\n\n        let response_result_fut = result_receiver.await;\n\n        request_drop_guard.remove();\n\n        let response_result = match response_result_fut {\n            Ok(response_result) => response_result,\n            Err(_closed) => Err(ResponseError {\n                reason: String::from(\"ChannelClosed\"),\n            }),\n        };\n\n        match response_result {\n            Ok(bytes) => {\n                debug!(\"request succeeded [method:{:?}, id:{}]\", R::METHOD, id);\n\n                match bytes {\n                    Some(bytes) => {\n                        let message_ref = message::MessageRef::read_as_root(&bytes).unwrap();\n\n                        let message::BodyRef::Response(response_ref) = message_ref.data().unwrap()\n                        else {\n                            panic!(\"Wrong response stored: {message_ref:?}\");\n                        };\n\n                        Ok(R::convert_response(response_ref.body().unwrap())\n                            .map_err(RequestError::ResponseConversion)?)\n                    }\n                    None => {\n                        Ok(R::convert_response(None).map_err(RequestError::ResponseConversion)?)\n                    }\n                }\n            }\n            Err(ResponseError { reason }) => {\n                debug!(\n                    \"request failed [method:{:?}, id:{}]: {}\",\n                    R::METHOD,\n                    id,\n                    reason\n                );\n                if reason.contains(\"not found\") {\n                    if let Some(default_response) = R::default_for_soft_error() {\n                        Ok(default_response)\n                    } else {\n                        Err(RequestError::ChannelClosed)\n                    }\n                } else {\n                    Err(RequestError::Response { reason })\n                }\n            }\n        }\n    }\n\n    pub(crate) fn notify<N, HandlerId>(\n        &self,\n        handler_id: HandlerId,\n        notification: N,\n    ) -> Result<(), NotificationError>\n    where\n        N: Notification<HandlerId = HandlerId>,\n        HandlerId: Display,\n    {\n        debug!(\"notify() [{notification:?}]\");\n\n        let data = notification.into_bytes(handler_id);\n\n        let message = Arc::new(AtomicTake::new(data));\n\n        {\n            let mut outgoing_message_buffer = self.inner.outgoing_message_buffer.lock();\n            outgoing_message_buffer\n                .messages\n                .push_back(Arc::clone(&message));\n            if let Some(handle) = outgoing_message_buffer.handle {\n                if self.inner.worker_closed.load(Ordering::Acquire) {\n                    // Forbid all notifications after worker closing except one\n                    // worker closing request\n                    if N::EVENT != notification::Event::WorkerClose {\n                        return Err(NotificationError::ChannelClosed);\n                    }\n                }\n                unsafe {\n                    // Notify worker that there is something to read\n                    let ret = mediasoup_sys::uv_async_send(handle);\n                    if ret != 0 {\n                        error!(\"uv_async_send call failed with code {}\", ret);\n                        return Err(NotificationError::ChannelClosed);\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    pub(crate) fn subscribe_to_notifications<F>(\n        &self,\n        target_id: SubscriptionTarget,\n        callback: F,\n    ) -> Option<SubscriptionHandler>\n    where\n        F: Fn(notification::NotificationRef<'_>) + Send + Sync + 'static,\n    {\n        self.inner\n            .event_handlers_weak\n            .upgrade()\n            .map(|event_handlers| event_handlers.add(target_id, Arc::new(callback)))\n    }\n}\n"
  },
  {
    "path": "rust/src/worker/common.rs",
    "content": "use hash_hasher::HashedMap;\nuse mediasoup_sys::fbs::notification;\nuse nohash_hasher::IntMap;\nuse parking_lot::Mutex;\nuse serde::Deserialize;\nuse std::sync::{Arc, Weak};\nuse uuid::Uuid;\n\nstruct EventHandlersList<F> {\n    index: usize,\n    callbacks: IntMap<usize, F>,\n}\n\nimpl<F> Default for EventHandlersList<F> {\n    fn default() -> Self {\n        Self {\n            index: 0,\n            callbacks: IntMap::default(),\n        }\n    }\n}\n\n#[derive(Clone)]\npub(super) struct EventHandlers<F> {\n    handlers: Arc<Mutex<HashedMap<SubscriptionTarget, EventHandlersList<F>>>>,\n}\n\nimpl<F: Sized + Send + Sync + 'static> EventHandlers<F> {\n    pub(super) fn new() -> Self {\n        let handlers = Arc::<Mutex<HashedMap<SubscriptionTarget, EventHandlersList<F>>>>::default();\n        Self { handlers }\n    }\n\n    pub(super) fn add(&self, target_id: SubscriptionTarget, callback: F) -> SubscriptionHandler {\n        let index;\n        {\n            let mut event_handlers = self.handlers.lock();\n            let list = event_handlers.entry(target_id.clone()).or_default();\n            index = list.index;\n            list.index += 1;\n            list.callbacks.insert(index, callback);\n            drop(event_handlers);\n        }\n\n        SubscriptionHandler::new({\n            let event_handlers_weak = Arc::downgrade(&self.handlers);\n\n            Box::new(move || {\n                if let Some(event_handlers) = event_handlers_weak.upgrade() {\n                    // We store removed handler here in order to drop after `event_handlers` lock is\n                    // released, otherwise handler will be dropped on removal from callbacks\n                    // immediately and if it happens to hold another entity that held subscription\n                    // handler, we may arrive here again trying to acquire lock that we didn't\n                    // release yet. By dropping callback only when lock is released we avoid\n                    // deadlocking.\n                    let removed_handler;\n                    {\n                        let mut handlers = event_handlers.lock();\n                        let is_empty = {\n                            let list = handlers.get_mut(&target_id).unwrap();\n                            removed_handler = list.callbacks.remove(&index);\n                            list.callbacks.is_empty()\n                        };\n                        if is_empty {\n                            handlers.remove(&target_id);\n                        }\n                    }\n                    drop(removed_handler);\n                }\n            })\n        })\n    }\n\n    pub(super) fn downgrade(&self) -> WeakEventHandlers<F> {\n        WeakEventHandlers {\n            handlers: Arc::downgrade(&self.handlers),\n        }\n    }\n}\n\nimpl EventHandlers<Arc<dyn Fn(notification::NotificationRef<'_>) + Send + Sync + 'static>> {\n    pub(super) fn call_callbacks_with_single_value(\n        &self,\n        target_id: &SubscriptionTarget,\n        value: notification::NotificationRef<'_>,\n    ) {\n        let handlers = self.handlers.lock();\n        if let Some(list) = handlers.get(target_id) {\n            for callback in list.callbacks.values() {\n                callback(value);\n            }\n        }\n    }\n}\n\n#[derive(Clone)]\npub(super) struct WeakEventHandlers<F> {\n    handlers: Weak<Mutex<HashedMap<SubscriptionTarget, EventHandlersList<F>>>>,\n}\n\nimpl<F> WeakEventHandlers<F> {\n    pub(super) fn upgrade(&self) -> Option<EventHandlers<F>> {\n        self.handlers\n            .upgrade()\n            .map(|handlers| EventHandlers { handlers })\n    }\n}\n\n#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)]\n#[serde(untagged)]\npub(crate) enum SubscriptionTarget {\n    Uuid(Uuid),\n    String(String),\n}\n\n/// Subscription handler, will remove corresponding subscription when dropped\npub(crate) struct SubscriptionHandler {\n    remove_callback: Option<Box<dyn FnOnce() + Send + Sync>>,\n}\n\nimpl SubscriptionHandler {\n    fn new(remove_callback: Box<dyn FnOnce() + Send + Sync>) -> Self {\n        Self {\n            remove_callback: Some(remove_callback),\n        }\n    }\n}\n\nimpl Drop for SubscriptionHandler {\n    fn drop(&mut self) {\n        let remove_callback = self.remove_callback.take().unwrap();\n        remove_callback();\n    }\n}\n"
  },
  {
    "path": "rust/src/worker/utils/channel_read_fn.rs",
    "content": "pub(super) use mediasoup_sys::{ChannelReadCtx, ChannelReadFn};\nuse mediasoup_sys::{ChannelReadFreeFn, UvAsyncT};\nuse std::mem;\nuse std::os::raw::c_void;\n\nunsafe extern \"C\" fn free_vec(message: *mut u8, message_len: u32, message_capacity: usize) {\n    // Create and drop vector to free its memory\n    Vec::from_raw_parts(message, message_len as usize, message_capacity);\n}\n\npub(super) struct ChannelReadCallback {\n    // Silence clippy warnings\n    _callback: Box<dyn (FnMut(UvAsyncT) -> Option<Vec<u8>>) + Send + 'static>,\n}\n\nimpl ChannelReadCallback {\n    pub(super) fn new(\n        _callback: Box<dyn (FnMut(UvAsyncT) -> Option<Vec<u8>>) + Send + 'static>,\n    ) -> Self {\n        Self { _callback }\n    }\n}\n\npub(crate) struct PreparedChannelRead {\n    channel_read_fn: ChannelReadFn,\n    channel_read_ctx: ChannelReadCtx,\n    write_callback: ChannelReadCallback,\n}\n\nimpl PreparedChannelRead {\n    /// SAFETY:\n    /// 1) `ChannelReadCallback` returned must be dropped AFTER last usage of returned function and\n    ///    context pointers\n    /// 2) `ChannelReadCtx` should not be called from multiple threads concurrently\n    pub(super) unsafe fn deconstruct(self) -> (ChannelReadFn, ChannelReadCtx, ChannelReadCallback) {\n        let Self {\n            channel_read_fn,\n            channel_read_ctx,\n            write_callback,\n        } = self;\n        (channel_read_fn, channel_read_ctx, write_callback)\n    }\n}\n\n/// Given callback function, prepares a pair of channel read function and context, which can be\n/// provided to of C++ worker and worker will effectively call the callback whenever it wants to\n/// read something from Rust (so it is reading from C++ point of view and writing from Rust).\npub(crate) fn prepare_channel_read_fn<F>(read_callback: F) -> PreparedChannelRead\nwhere\n    F: (FnMut(UvAsyncT) -> Option<Vec<u8>>) + Send + 'static,\n{\n    unsafe extern \"C\" fn wrapper<F>(\n        message: *mut *mut u8,\n        message_len: *mut u32,\n        message_capacity: *mut usize,\n        handle: UvAsyncT,\n        ChannelReadCtx(ctx): ChannelReadCtx,\n    ) -> ChannelReadFreeFn\n    where\n        F: (FnMut(UvAsyncT) -> Option<Vec<u8>>) + Send + 'static,\n    {\n        // Call Rust and try to get a new message (if there is any)\n        let mut new_message = (*(ctx as *mut F))(handle)?;\n\n        // Set pointers, give out control over memory to C++\n        *message = new_message.as_mut_ptr();\n        *message_len = new_message.len() as u32;\n        *message_capacity = new_message.capacity();\n\n        // Forget about vector in Rust\n        mem::forget(new_message);\n\n        // Function pointer that C++ can use to free vector later\n        Some(free_vec)\n    }\n\n    // Move to heap to make sure it doesn't change address later on\n    let read_callback = Box::new(read_callback);\n\n    PreparedChannelRead {\n        channel_read_fn: wrapper::<F>,\n        channel_read_ctx: ChannelReadCtx(read_callback.as_ref() as *const F as *const c_void),\n        write_callback: ChannelReadCallback::new(read_callback),\n    }\n}\n"
  },
  {
    "path": "rust/src/worker/utils/channel_write_fn.rs",
    "content": "pub(super) use mediasoup_sys::{ChannelWriteCtx, ChannelWriteFn};\nuse std::os::raw::c_void;\nuse std::slice;\n\n/// TypeAlias to silience clippy::type_complexity warnings\ntype CallbackType = Box<dyn FnMut(&[u8]) + Send + 'static>;\n\npub(super) struct ChannelReadCallback {\n    // Silence clippy warnings\n    _callback: CallbackType,\n}\n\nimpl ChannelReadCallback {\n    pub(super) fn new(_callback: CallbackType) -> Self {\n        Self { _callback }\n    }\n}\n\npub(crate) struct PreparedChannelWrite {\n    channel_write_fn: ChannelWriteFn,\n    channel_write_ctx: ChannelWriteCtx,\n    read_callback: ChannelReadCallback,\n}\n\nunsafe impl Send for PreparedChannelWrite {}\n\nimpl PreparedChannelWrite {\n    /// SAFETY:\n    /// 1) `ChannelReadCallback` returned must be dropped AFTER last usage of returned function and\n    ///    context pointers\n    /// 2) `ChannelWriteCtx` should not be called from multiple threads concurrently\n    pub(super) unsafe fn deconstruct(\n        self,\n    ) -> (ChannelWriteFn, ChannelWriteCtx, ChannelReadCallback) {\n        let Self {\n            channel_write_fn,\n            channel_write_ctx,\n            read_callback,\n        } = self;\n        (channel_write_fn, channel_write_ctx, read_callback)\n    }\n}\n\n/// Given callback function, prepares a pair of channel write function and context, which can be\n/// provided to of C++ worker and worker will effectively call the callback whenever it needs to\n/// send something to Rust (so it is writing from C++ point of view and reading from Rust).\npub(crate) fn prepare_channel_write_fn<F>(read_callback: F) -> PreparedChannelWrite\nwhere\n    F: FnMut(&[u8]) + Send + 'static,\n{\n    unsafe extern \"C\" fn wrapper<F>(\n        message: *const u8,\n        message_len: u32,\n        ChannelWriteCtx(ctx): ChannelWriteCtx,\n    ) where\n        F: FnMut(&[u8]) + Send + 'static,\n    {\n        let message = slice::from_raw_parts(message, message_len as usize);\n        (*(ctx as *mut F))(message);\n    }\n\n    // Move to heap to make sure it doesn't change address later on\n    let read_callback = Box::new(read_callback);\n\n    PreparedChannelWrite {\n        channel_write_fn: wrapper::<F>,\n        channel_write_ctx: ChannelWriteCtx(read_callback.as_ref() as *const F as *const c_void),\n        read_callback: ChannelReadCallback::new(read_callback),\n    }\n}\n"
  },
  {
    "path": "rust/src/worker/utils.rs",
    "content": "mod channel_read_fn;\nmod channel_write_fn;\n\nuse crate::worker::channel::BufferMessagesGuard;\nuse crate::worker::{Channel, SubscriptionTarget, WorkerId};\npub(super) use channel_read_fn::{prepare_channel_read_fn, PreparedChannelRead};\npub(super) use channel_write_fn::{prepare_channel_write_fn, PreparedChannelWrite};\nuse std::ffi::CString;\nuse std::os::raw::{c_char, c_int};\nuse std::sync::atomic::AtomicBool;\nuse std::sync::Arc;\nuse thiserror::Error;\n\n/// Worker exit error\n#[derive(Debug, Copy, Clone, Error)]\npub enum ExitError {\n    /// Generic error.\n    #[error(\"Worker exited with generic error\")]\n    Generic,\n    /// Settings error.\n    #[error(\"Worker exited with settings error\")]\n    Settings,\n    /// Unknown error.\n    #[error(\"Worker exited with unknown error and status code {status_code}\")]\n    Unknown {\n        /// Status code returned by worker\n        status_code: i32,\n    },\n    /// Unexpected error.\n    #[error(\"Worker exited unexpectedly\")]\n    Unexpected,\n}\n\npub(super) struct WorkerRunResult {\n    pub(super) channel: Channel,\n    pub(super) buffer_worker_messages_guard: BufferMessagesGuard,\n}\n\npub(super) fn run_worker_with_channels<OE>(\n    id: WorkerId,\n    thread_initializer: Option<Arc<dyn Fn() + Send + Sync>>,\n    args: Vec<String>,\n    worker_closed: Arc<AtomicBool>,\n    on_exit: OE,\n) -> WorkerRunResult\nwhere\n    OE: FnOnce(Result<(), ExitError>) + Send + 'static,\n{\n    let (channel, prepared_channel_read, prepared_channel_write) =\n        Channel::new(Arc::clone(&worker_closed));\n    let buffer_worker_messages_guard =\n        channel.buffer_messages_for(SubscriptionTarget::String(std::process::id().to_string()));\n\n    std::thread::Builder::new()\n        .name(format!(\"mediasoup-worker-{id}\"))\n        .spawn(move || {\n            if let Some(thread_initializer) = thread_initializer {\n                thread_initializer();\n            }\n            let argc = args.len() as c_int;\n            let args_cstring = args\n                .into_iter()\n                .map(|s| -> CString { CString::new(s).unwrap() })\n                .collect::<Vec<_>>();\n            let argv = args_cstring\n                .iter()\n                .map(|arg| arg.as_ptr().cast::<c_char>())\n                .collect::<Vec<_>>();\n            let version = CString::new(env!(\"CARGO_PKG_VERSION\")).unwrap();\n\n            let status_code = unsafe {\n                let (channel_read_fn, channel_read_ctx, _channel_write_callback) =\n                    prepared_channel_read.deconstruct();\n                let (channel_write_fn, channel_write_ctx, _channel_read_callback) =\n                    prepared_channel_write.deconstruct();\n\n                mediasoup_sys::mediasoup_worker_run(\n                    argc,\n                    argv.as_ptr(),\n                    version.as_ptr(),\n                    /*consumerChannelFd*/ 0,\n                    /*producerChannelFd*/ 0,\n                    channel_read_fn,\n                    channel_read_ctx,\n                    channel_write_fn,\n                    channel_write_ctx,\n                )\n            };\n\n            on_exit(match status_code {\n                0 => Ok(()),\n                1 => Err(ExitError::Generic),\n                42 => Err(ExitError::Settings),\n                status_code => Err(ExitError::Unknown { status_code }),\n            });\n        })\n        .expect(\"Failed to spawn mediasoup-worker thread\");\n\n    WorkerRunResult {\n        channel,\n        buffer_worker_messages_guard,\n    }\n}\n"
  },
  {
    "path": "rust/src/worker.rs",
    "content": "//! A worker represents a mediasoup C++ thread that runs on a single CPU core and handles\n//! [`Router`] instances.\n\nmod channel;\nmod common;\nmod utils;\n\nuse crate::messages::{\n    WorkerCloseNotification, WorkerCreateRouterRequest, WorkerCreateWebRtcServerRequest,\n    WorkerDumpRequest, WorkerUpdateSettingsRequest,\n};\npub use crate::ortc::RtpCapabilitiesError;\nuse crate::router::{Router, RouterId, RouterOptions};\nuse crate::webrtc_server::{WebRtcServer, WebRtcServerId, WebRtcServerOptions};\nuse crate::worker::channel::BufferMessagesGuard;\npub use crate::worker::utils::ExitError;\nuse crate::worker_manager::WorkerManager;\nuse crate::{ortc, uuid_based_wrapper_type};\nuse async_executor::Executor;\npub(crate) use channel::{Channel, NotificationError, NotificationParseError};\npub(crate) use common::{SubscriptionHandler, SubscriptionTarget};\nuse event_listener_primitives::{Bag, BagOnce, HandlerId};\nuse futures_lite::FutureExt;\nuse log::{debug, error, warn};\nuse mediasoup_sys::fbs;\nuse mediasoup_types::data_structures::AppData;\nuse parking_lot::Mutex;\nuse serde::{Deserialize, Serialize};\nuse std::error::Error;\nuse std::ops::RangeInclusive;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::Arc;\nuse std::{fmt, io};\nuse thiserror::Error;\nuse utils::WorkerRunResult;\nuse uuid::Uuid;\n\nuuid_based_wrapper_type!(\n    /// Worker identifier.\n    WorkerId\n);\n\n/// Error that caused request to mediasoup-worker request to fail.\n#[derive(Debug, Error)]\npub enum RequestError {\n    /// Channel already closed.\n    #[error(\"Channel already closed\")]\n    ChannelClosed,\n    /// Request timed out.\n    #[error(\"Request timed out\")]\n    TimedOut,\n    /// Received response error.\n    #[error(\"Received response error: {reason}\")]\n    Response {\n        /// Error reason.\n        reason: String,\n    },\n    /// Failed to parse response from worker.\n    #[error(\"Failed to parse response from worker: {error}\")]\n    FailedToParse {\n        /// Error reason.\n        error: String,\n    },\n    /// Worker did not return any data in response.\n    #[error(\"Worker did not return any data in response\")]\n    NoData,\n    /// Response conversion error.\n    #[error(\"Response conversion error: {0}\")]\n    ResponseConversion(Box<dyn Error + Send + Sync>),\n}\n\n/// Logging level for logs generated by the media worker thread (check the\n/// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/)\n/// documentation on TypeScript implementation and generic\n/// [Rust-specific](https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/log.html) [docs](https://docs.rs/env_logger)).\n///\n/// Default [`WorkerLogLevel::Error`].\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Default)]\n#[serde(rename_all = \"lowercase\")]\npub enum WorkerLogLevel {\n    /// Log all severities.\n    Debug,\n    /// Log \"warn\" and \"error\" severities.\n    Warn,\n    /// Log \"error\" severity.\n    #[default]\n    Error,\n    /// Do not log anything.\n    None,\n}\n\nimpl WorkerLogLevel {\n    pub(crate) fn as_str(self) -> &'static str {\n        match self {\n            Self::Debug => \"debug\",\n            Self::Warn => \"warn\",\n            Self::Error => \"error\",\n            Self::None => \"none\",\n        }\n    }\n}\n\n/// Log tags for debugging. Check the meaning of each available tag in the\n/// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum WorkerLogTag {\n    /// Logs about software/library versions, configuration and process information.\n    Info,\n    /// Logs about ICE.\n    Ice,\n    /// Logs about DTLS.\n    Dtls,\n    /// Logs about RTP.\n    Rtp,\n    /// Logs about SRTP encryption/decryption.\n    Srtp,\n    /// Logs about RTCP.\n    Rtcp,\n    /// Logs about RTP retransmission, including NACK/PLI/FIR.\n    Rtx,\n    /// Logs about transport bandwidth estimation.\n    Bwe,\n    /// Logs related to the scores of Producers and Consumers.\n    Score,\n    /// Logs about video simulcast.\n    Simulcast,\n    /// Logs about video SVC.\n    Svc,\n    /// Logs about SCTP (DataChannel).\n    Sctp,\n    /// Logs about messages (can be SCTP messages or direct messages).\n    Message,\n}\n\nimpl WorkerLogTag {\n    pub(crate) fn as_str(self) -> &'static str {\n        match self {\n            Self::Info => \"info\",\n            Self::Ice => \"ice\",\n            Self::Dtls => \"dtls\",\n            Self::Rtp => \"rtp\",\n            Self::Srtp => \"srtp\",\n            Self::Rtcp => \"rtcp\",\n            Self::Rtx => \"rtx\",\n            Self::Bwe => \"bwe\",\n            Self::Score => \"score\",\n            Self::Simulcast => \"simulcast\",\n            Self::Svc => \"svc\",\n            Self::Sctp => \"sctp\",\n            Self::Message => \"message\",\n        }\n    }\n}\n\n/// DTLS certificate and private key.\n#[derive(Debug, Clone)]\npub struct WorkerDtlsFiles {\n    /// Path to the DTLS public certificate file in PEM format.\n    pub certificate: PathBuf,\n    /// Path to the DTLS certificate private key file in PEM format.\n    pub private_key: PathBuf,\n}\n\n/// Settings for worker to be created with.\n#[derive(Clone)]\n#[non_exhaustive]\npub struct WorkerSettings {\n    /// Logging level for logs generated by the media worker thread.\n    ///\n    /// Default [`WorkerLogLevel::Error`].\n    pub log_level: WorkerLogLevel,\n    /// Log tags for debugging. Check the meaning of each available tag in the\n    /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation.\n    pub log_tags: Vec<WorkerLogTag>,\n    /// RTC port range for ICE, DTLS, RTP, etc. Default 10000..=59999.\n    pub rtc_port_range: RangeInclusive<u16>,\n    /// DTLS certificate and private key.\n    ///\n    /// If `None`, a certificate is dynamically created.\n    pub dtls_files: Option<WorkerDtlsFiles>,\n    /// Field trials for libwebrtc.\n    ///\n    /// NOTE: For advanced users only. An invalid value will make the worker crash.\n    /// Default value is\n    /// \"WebRTC-Bwe-AlrLimitedBackoff/Enabled/\".\n    #[doc(hidden)]\n    pub libwebrtc_field_trials: Option<String>,\n    /// Enable liburing  This option is ignored if io_uring is not supported by\n    /// current host.\n    ///\n    /// Default `true`.\n    pub enable_liburing: bool,\n    /// Use the mediasoup built-in SCTP stack instead usrsctp.\n    ///\n    /// Default `false`.\n    pub use_built_in_sctp_stack: bool,\n    /// Function that will be called under worker thread before worker starts, can be used for\n    /// pinning worker threads to CPU cores.\n    pub thread_initializer: Option<Arc<dyn Fn() + Send + Sync>>,\n    /// Custom application data.\n    pub app_data: AppData,\n}\n\nimpl Default for WorkerSettings {\n    fn default() -> Self {\n        Self {\n            log_level: WorkerLogLevel::Debug,\n            log_tags: vec![\n                WorkerLogTag::Info,\n                WorkerLogTag::Ice,\n                WorkerLogTag::Dtls,\n                WorkerLogTag::Rtp,\n                WorkerLogTag::Srtp,\n                WorkerLogTag::Rtcp,\n                WorkerLogTag::Rtx,\n                WorkerLogTag::Bwe,\n                WorkerLogTag::Score,\n                WorkerLogTag::Simulcast,\n                WorkerLogTag::Svc,\n                WorkerLogTag::Sctp,\n                WorkerLogTag::Message,\n            ],\n            rtc_port_range: 10000..=59999,\n            dtls_files: None,\n            libwebrtc_field_trials: None,\n            enable_liburing: true,\n            use_built_in_sctp_stack: false,\n            thread_initializer: None,\n            app_data: AppData::default(),\n        }\n    }\n}\n\nimpl fmt::Debug for WorkerSettings {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let WorkerSettings {\n            log_level,\n            log_tags,\n            rtc_port_range,\n            dtls_files,\n            libwebrtc_field_trials,\n            enable_liburing,\n            use_built_in_sctp_stack,\n            thread_initializer,\n            app_data,\n        } = self;\n\n        f.debug_struct(\"WorkerSettings\")\n            .field(\"log_level\", &log_level)\n            .field(\"log_tags\", &log_tags)\n            .field(\"rtc_port_range\", &rtc_port_range)\n            .field(\"dtls_files\", &dtls_files)\n            .field(\"libwebrtc_field_trials\", &libwebrtc_field_trials)\n            .field(\"enable_liburing\", &enable_liburing)\n            .field(\"use_built_in_sctp_stack\", &use_built_in_sctp_stack)\n            .field(\n                \"thread_initializer\",\n                &thread_initializer.as_ref().map(|_| \"ThreadInitializer\"),\n            )\n            .field(\"app_data\", &app_data)\n            .finish()\n    }\n}\n\n/// Worker settings that can be updated in runtime.\n#[derive(Default, Debug, Clone, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[non_exhaustive]\npub struct WorkerUpdateSettings {\n    /// Logging level for logs generated by the media worker thread.\n    ///\n    /// If `None`, logging level will not be updated.\n    pub log_level: Option<WorkerLogLevel>,\n    /// Log tags for debugging. Check the meaning of each available tag in the\n    /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation.\n    ///\n    /// If `None`, log tags will not be updated.\n    pub log_tags: Option<Vec<WorkerLogTag>>,\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\npub struct ChannelMessageHandlers {\n    pub channel_request_handlers: Vec<Uuid>,\n    pub channel_notification_handlers: Vec<Uuid>,\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]\n#[doc(hidden)]\npub struct LibUringDump {\n    pub sqe_process_count: u64,\n    pub sqe_miss_count: u64,\n    pub user_data_miss_count: u64,\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[doc(hidden)]\n#[non_exhaustive]\npub struct WorkerDump {\n    // Dump has `pid` field too, but it is useless here because of thead-based worker usage\n    pub router_ids: Vec<RouterId>,\n    #[serde(rename = \"webRtcServerIds\")]\n    pub webrtc_server_ids: Vec<WebRtcServerId>,\n    pub channel_message_handlers: ChannelMessageHandlers,\n    pub liburing: Option<LibUringDump>,\n}\n\n/// Error that caused [`Worker::create_webrtc_server`] to fail.\n#[derive(Debug, Error)]\npub enum CreateWebRtcServerError {\n    /// Request to worker failed\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n/// Error that caused [`Worker::create_router`] to fail.\n#[derive(Debug, Error)]\npub enum CreateRouterError {\n    /// RTP capabilities generation error\n    #[error(\"RTP capabilities generation error: {0}\")]\n    FailedRtpCapabilitiesGeneration(RtpCapabilitiesError),\n    /// Request to worker failed\n    #[error(\"Request to worker failed: {0}\")]\n    Request(RequestError),\n}\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_router: Bag<Arc<dyn Fn(&Router) + Send + Sync>, Router>,\n    new_webrtc_server: Bag<Arc<dyn Fn(&WebRtcServer) + Send + Sync>, WebRtcServer>,\n    #[allow(clippy::type_complexity)]\n    dead: BagOnce<Box<dyn FnOnce(Result<(), ExitError>) + Send>>,\n    close: BagOnce<Box<dyn FnOnce() + Send>>,\n}\n\nstruct Inner {\n    id: WorkerId,\n    channel: Channel,\n    executor: Arc<Executor<'static>>,\n    handlers: Handlers,\n    app_data: AppData,\n    closed: Arc<AtomicBool>,\n    // Make sure worker is not dropped until this worker manager is not dropped\n    _worker_manager: WorkerManager,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        debug!(\"drop()\");\n\n        self.close();\n    }\n}\n\nimpl Inner {\n    async fn new<OE: FnOnce() + Send + 'static>(\n        executor: Arc<Executor<'static>>,\n        WorkerSettings {\n            log_level,\n            log_tags,\n            rtc_port_range,\n            dtls_files,\n            libwebrtc_field_trials,\n            enable_liburing,\n            use_built_in_sctp_stack,\n            thread_initializer,\n            app_data,\n        }: WorkerSettings,\n        worker_manager: WorkerManager,\n        on_exit: OE,\n    ) -> io::Result<Arc<Self>> {\n        debug!(\"new()\");\n\n        let mut spawn_args: Vec<String> = vec![\"\".to_string()];\n\n        spawn_args.push(format!(\"--logLevel={}\", log_level.as_str()));\n        for log_tag in log_tags {\n            spawn_args.push(format!(\"--logTag={}\", log_tag.as_str()));\n        }\n\n        if rtc_port_range.is_empty() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"Invalid RTC ports range\",\n            ));\n        }\n        spawn_args.push(format!(\"--rtcMinPort={}\", rtc_port_range.start()));\n        spawn_args.push(format!(\"--rtcMaxPort={}\", rtc_port_range.end()));\n\n        if let Some(dtls_files) = dtls_files {\n            spawn_args.push(format!(\n                \"--dtlsCertificateFile={}\",\n                dtls_files\n                    .certificate\n                    .to_str()\n                    .expect(\"Paths are only expected to be utf8\")\n            ));\n            spawn_args.push(format!(\n                \"--dtlsPrivateKeyFile={}\",\n                dtls_files\n                    .private_key\n                    .to_str()\n                    .expect(\"Paths are only expected to be utf8\")\n            ));\n        }\n\n        if let Some(libwebrtc_field_trials) = libwebrtc_field_trials {\n            spawn_args.push(format!(\n                \"--libwebrtcFieldTrials={}\",\n                libwebrtc_field_trials.as_str()\n            ));\n        }\n\n        if !enable_liburing {\n            spawn_args.push(\"--disableLiburing=true\".to_string());\n        }\n\n        if use_built_in_sctp_stack {\n            spawn_args.push(\"--useBuiltInSctpStack=true\".to_string());\n        } else {\n            spawn_args.push(\"--useBuiltInSctpStack=false\".to_string());\n        }\n\n        let id = WorkerId::new();\n        debug!(\n            \"spawning worker with arguments [id:{}]: {}\",\n            id,\n            spawn_args.join(\" \")\n        );\n\n        let closed = Arc::new(AtomicBool::new(false));\n\n        let (mut status_sender, status_receiver) = async_oneshot::oneshot();\n        let WorkerRunResult {\n            channel,\n            buffer_worker_messages_guard,\n        } = utils::run_worker_with_channels(\n            id,\n            thread_initializer,\n            spawn_args,\n            Arc::clone(&closed),\n            move |result| {\n                let _ = status_sender.send(result);\n                on_exit();\n            },\n        );\n\n        let handlers = Handlers::default();\n\n        let mut inner = Self {\n            id,\n            channel,\n            executor,\n            handlers,\n            app_data,\n            closed,\n            _worker_manager: worker_manager,\n        };\n\n        inner.setup_message_handling();\n\n        let (mut early_status_sender, early_status_receiver) = async_oneshot::oneshot();\n\n        let inner = Arc::new(inner);\n        {\n            let inner_weak = Arc::downgrade(&inner);\n            inner\n                .executor\n                .spawn(async move {\n                    let status = status_receiver.await.unwrap_or(Err(ExitError::Unexpected));\n                    let _ = early_status_sender.send(status);\n\n                    if let Some(inner) = inner_weak.upgrade() {\n                        warn!(\"worker exited [id:{}]: {:?}\", id, status);\n\n                        if !inner.closed.swap(true, Ordering::SeqCst) {\n                            inner.handlers.dead.call(|callback| {\n                                callback(status);\n                            });\n                            inner.handlers.close.call_simple();\n                        }\n                    }\n                })\n                .detach();\n        }\n\n        inner\n            .wait_for_worker_ready(buffer_worker_messages_guard)\n            .or(async {\n                let status = early_status_receiver\n                    .await\n                    .unwrap_or(Err(ExitError::Unexpected));\n                let error_message = format!(\n                    \"worker thread exited before being ready [id:{}]: exit status {:?}\",\n                    inner.id, status,\n                );\n                Err(io::Error::new(io::ErrorKind::NotFound, error_message))\n            })\n            .await?;\n\n        Ok(inner)\n    }\n\n    async fn wait_for_worker_ready(\n        &self,\n        buffer_worker_messages_guard: BufferMessagesGuard,\n    ) -> io::Result<()> {\n        let (sender, receiver) = async_oneshot::oneshot();\n        let id = self.id;\n        let sender = Mutex::new(Some(sender));\n        let _handler = self.channel.subscribe_to_notifications(\n            SubscriptionTarget::String(std::process::id().to_string()),\n            move |notification| {\n                let result = match notification.event().unwrap() {\n                    fbs::notification::Event::WorkerRunning => {\n                        debug!(\"worker thread running [id:{}]\", id);\n                        Ok(())\n                    }\n                    _ => Err(io::Error::other(format!(\n                        \"unexpected first notification from worker [id:{id}]\"\n                    ))),\n                };\n\n                let _ = sender\n                    .lock()\n                    .take()\n                    .expect(\"Receiving more than one worker notification\")\n                    .send(result);\n            },\n        );\n\n        // Allow worker messages to go through\n        drop(buffer_worker_messages_guard);\n\n        receiver\n            .await\n            .map_err(|_closed| io::Error::other(\"Worker dropped before it is ready\"))?\n    }\n\n    fn setup_message_handling(&mut self) {\n        let channel_receiver = self.channel.get_internal_message_receiver();\n        let id = self.id;\n        let closed = Arc::clone(&self.closed);\n        self.executor\n            .spawn(async move {\n                while let Ok(message) = channel_receiver.recv().await {\n                    match message {\n                        channel::InternalMessage::Debug(text) => debug!(\"[id:{}] {}\", id, text),\n                        channel::InternalMessage::Warn(text) => warn!(\"[id:{}] {}\", id, text),\n                        channel::InternalMessage::Error(text) => {\n                            if !closed.load(Ordering::SeqCst) {\n                                error!(\"[id:{}] {}\", id, text)\n                            }\n                        }\n                        channel::InternalMessage::Dump(text) => eprintln!(\"{text}\"),\n                        channel::InternalMessage::Unexpected(data) => error!(\n                            \"worker[id:{}] unexpected channel data: {}\",\n                            id,\n                            String::from_utf8_lossy(&data)\n                        ),\n                    }\n                }\n            })\n            .detach();\n    }\n\n    fn close(&self) {\n        let already_closed = self.closed.swap(true, Ordering::SeqCst);\n\n        if !already_closed {\n            let channel = self.channel.clone();\n\n            self.executor\n                .spawn(async move {\n                    let _ = channel.notify(\"\", WorkerCloseNotification {});\n\n                    // Drop channels in here after having sent the notification\n                    drop(channel);\n                })\n                .detach();\n\n            self.handlers.close.call_simple();\n        }\n    }\n}\n\n/// A worker represents a mediasoup C++ thread that runs on a single CPU core and handles\n/// [`Router`] instances.\n#[derive(Clone)]\n#[must_use = \"Worker will be destroyed on drop, make sure to keep it around for as long as needed\"]\npub struct Worker {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for Worker {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"Worker\")\n            .field(\"id\", &self.inner.id)\n            .field(\"closed\", &self.inner.closed)\n            .finish()\n    }\n}\n\nimpl Worker {\n    pub(super) async fn new<OE: FnOnce() + Send + 'static>(\n        executor: Arc<Executor<'static>>,\n        worker_settings: WorkerSettings,\n        worker_manager: WorkerManager,\n        on_exit: OE,\n    ) -> io::Result<Self> {\n        let inner = Inner::new(executor, worker_settings, worker_manager, on_exit).await?;\n\n        Ok(Self { inner })\n    }\n\n    /// Worker id.\n    #[must_use]\n    pub fn id(&self) -> WorkerId {\n        self.inner.id\n    }\n\n    /// Worker manager to which worker belongs.\n    pub fn worker_manager(&self) -> &WorkerManager {\n        &self.inner._worker_manager\n    }\n\n    /// Custom application data.\n    #[must_use]\n    pub fn app_data(&self) -> &AppData {\n        &self.inner.app_data\n    }\n\n    /// Whether the worker is closed.\n    #[must_use]\n    pub fn closed(&self) -> bool {\n        self.inner.closed.load(Ordering::SeqCst)\n    }\n\n    /// Dump Worker.\n    #[doc(hidden)]\n    pub async fn dump(&self) -> Result<WorkerDump, RequestError> {\n        debug!(\"dump()\");\n\n        self.inner.channel.request(\"\", WorkerDumpRequest {}).await\n    }\n\n    /// Updates the worker settings in runtime. Just a subset of the worker settings can be updated.\n    pub async fn update_settings(&self, data: WorkerUpdateSettings) -> Result<(), RequestError> {\n        debug!(\"update_settings()\");\n\n        match self\n            .inner\n            .channel\n            .request(\"\", WorkerUpdateSettingsRequest { data })\n            .await\n        {\n            Ok(_) => Ok(()),\n            Err(error) => Err(error),\n        }\n    }\n\n    /// Create a WebRtcServer.\n    ///\n    /// Worker will be kept alive as long as at least one WebRTC server instance is alive.\n    pub async fn create_webrtc_server(\n        &self,\n        webrtc_server_options: WebRtcServerOptions,\n    ) -> Result<WebRtcServer, CreateWebRtcServerError> {\n        debug!(\"create_webrtc_server()\");\n\n        let WebRtcServerOptions {\n            listen_infos,\n            app_data,\n        } = webrtc_server_options;\n\n        let webrtc_server_id = WebRtcServerId::new();\n\n        let _buffer_guard = self\n            .inner\n            .channel\n            .buffer_messages_for(webrtc_server_id.into());\n\n        self.inner\n            .channel\n            .request(\n                \"\",\n                WorkerCreateWebRtcServerRequest {\n                    webrtc_server_id,\n                    listen_infos,\n                },\n            )\n            .await\n            .map_err(CreateWebRtcServerError::Request)?;\n\n        let webrtc_server = WebRtcServer::new(\n            webrtc_server_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            app_data,\n            self.clone(),\n        );\n\n        self.inner\n            .handlers\n            .new_webrtc_server\n            .call_simple(&webrtc_server);\n\n        Ok(webrtc_server)\n    }\n\n    /// Create a Router.\n    ///\n    /// Worker will be kept alive as long as at least one router instance is alive.\n    pub async fn create_router(\n        &self,\n        router_options: RouterOptions,\n    ) -> Result<Router, CreateRouterError> {\n        debug!(\"create_router()\");\n\n        let RouterOptions {\n            app_data,\n            media_codecs,\n        } = router_options;\n\n        let rtp_capabilities = ortc::generate_router_rtp_capabilities(media_codecs)\n            .map_err(CreateRouterError::FailedRtpCapabilitiesGeneration)?;\n\n        let router_id = RouterId::new();\n\n        let _buffer_guard = self.inner.channel.buffer_messages_for(router_id.into());\n\n        self.inner\n            .channel\n            .request(\"\", WorkerCreateRouterRequest { router_id })\n            .await\n            .map_err(CreateRouterError::Request)?;\n\n        let router = Router::new(\n            router_id,\n            Arc::clone(&self.inner.executor),\n            self.inner.channel.clone(),\n            rtp_capabilities,\n            app_data,\n            self.clone(),\n        );\n\n        self.inner.handlers.new_router.call_simple(&router);\n\n        Ok(router)\n    }\n\n    /// Callback is called when a new WebRTC server is created.\n    pub fn on_new_webrtc_server<F: Fn(&WebRtcServer) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner\n            .handlers\n            .new_webrtc_server\n            .add(Arc::new(callback))\n    }\n\n    /// Callback is called when a new router is created.\n    pub fn on_new_router<F: Fn(&Router) + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.new_router.add(Arc::new(callback))\n    }\n\n    /// Callback is called when the worker thread unexpectedly dies.\n    pub fn on_dead<F: FnOnce(Result<(), ExitError>) + Send + Sync + 'static>(\n        &self,\n        callback: F,\n    ) -> HandlerId {\n        self.inner.handlers.dead.add(Box::new(callback))\n    }\n\n    /// Callback is called when the worker is closed for whatever reason.\n    ///\n    /// NOTE: Callback will be called in place if worker is already closed.\n    pub fn on_close<F: FnOnce() + Send + 'static>(&self, callback: F) -> HandlerId {\n        let handler_id = self.inner.handlers.close.add(Box::new(callback));\n        if self.inner.closed.load(Ordering::Relaxed) {\n            self.inner.handlers.close.call_simple();\n        }\n        handler_id\n    }\n\n    #[cfg(test)]\n    pub(crate) fn close(&self) {\n        self.inner.close();\n    }\n}\n"
  },
  {
    "path": "rust/src/worker_manager/tests.rs",
    "content": "use super::*;\nuse std::env;\n\nfn init() {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n}\n\n#[test]\nfn worker_manager_test() {\n    init();\n\n    let worker_manager = WorkerManager::new();\n\n    future::block_on(async move {\n        let _ = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .unwrap();\n    });\n}\n"
  },
  {
    "path": "rust/src/worker_manager.rs",
    "content": "//! Container that creates [`Worker`] instances.\n\n#[cfg(test)]\nmod tests;\n\nuse crate::worker::{Worker, WorkerId, WorkerSettings};\nuse async_executor::Executor;\nuse async_oneshot::Sender;\nuse event_listener_primitives::{Bag, HandlerId};\nuse futures_lite::future;\nuse log::debug;\nuse parking_lot::Mutex;\nuse std::collections::HashMap;\nuse std::ops::DerefMut;\nuse std::sync::mpsc;\nuse std::sync::Arc;\nuse std::{fmt, io, mem};\n\n#[derive(Default)]\n#[allow(clippy::type_complexity)]\nstruct Handlers {\n    new_worker: Bag<Arc<dyn Fn(&Worker) + Send + Sync>, Worker>,\n}\n\nstruct Inner {\n    executor: Arc<Executor<'static>>,\n    handlers: Handlers,\n    /// Mapping from worker ID to the close event receiver\n    workers: Arc<Mutex<HashMap<WorkerId, mpsc::Receiver<()>>>>,\n    /// This field is only used in order to be dropped with the worker manager itself to stop the\n    /// thread created with `WorkerManager::new()` call\n    _stop_sender: Option<Sender<()>>,\n}\n\nimpl Drop for Inner {\n    fn drop(&mut self) {\n        let workers = mem::take(self.workers.lock().deref_mut());\n        for exit_receiver in workers.into_values() {\n            let _ = exit_receiver.recv();\n        }\n    }\n}\n\n/// Container that creates [`Worker`] instances.\n///\n/// # Examples\n/// ```no_run\n/// use futures_lite::future;\n/// use mediasoup::worker::WorkerSettings;\n/// use mediasoup::worker_manager::WorkerManager;\n///\n/// // Create a manager that will use specified binary for spawning new worker thread\n/// let worker_manager = WorkerManager::new();\n///\n/// future::block_on(async move {\n///     // Create a new worker with default settings\n///     let worker = worker_manager\n///         .create_worker(WorkerSettings::default())\n///         .await\n///         .unwrap();\n/// })\n/// ```\n///\n/// If you already happen to have [`async_executor::Executor`] instance available or need a\n/// multi-threaded executor, [`WorkerManager::with_executor()`] can be used to create an instance\n/// instead.\n#[derive(Clone)]\n#[must_use]\npub struct WorkerManager {\n    inner: Arc<Inner>,\n}\n\nimpl fmt::Debug for WorkerManager {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.debug_struct(\"WorkerManager\").finish()\n    }\n}\n\nimpl Default for WorkerManager {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl WorkerManager {\n    /// Create new worker manager, internally a new single-threaded executor will be created.\n    pub fn new() -> Self {\n        let executor = Arc::new(Executor::new());\n        let (stop_sender, stop_receiver) = async_oneshot::oneshot::<()>();\n        {\n            let executor = Arc::clone(&executor);\n            std::thread::spawn(move || {\n                // Will return Err(Closed) when `WorkerManager` struct is dropped\n                let _ = future::block_on(executor.run(stop_receiver));\n            });\n        }\n\n        let handlers = Handlers::default();\n\n        let inner = Arc::new(Inner {\n            executor,\n            handlers,\n            workers: Arc::default(),\n            _stop_sender: Some(stop_sender),\n        });\n\n        Self { inner }\n    }\n\n    /// Create new worker manager, uses externally provided executor.\n    pub fn with_executor(executor: Arc<Executor<'static>>) -> Self {\n        let handlers = Handlers::default();\n\n        let inner = Arc::new(Inner {\n            executor,\n            handlers,\n            workers: Arc::default(),\n            _stop_sender: None,\n        });\n\n        Self { inner }\n    }\n\n    /// Creates a new worker with the given settings.\n    ///\n    /// Worker manager will be kept alive as long as at least one worker instance is alive.\n    pub async fn create_worker(&self, worker_settings: WorkerSettings) -> io::Result<Worker> {\n        debug!(\"create_worker()\");\n\n        let (exit_sender, exit_receiver) = mpsc::channel();\n        let id = Arc::new(Mutex::new(None));\n        let worker = Worker::new(\n            Arc::clone(&self.inner.executor),\n            worker_settings,\n            self.clone(),\n            {\n                let id = Arc::clone(&id);\n                let workers = Arc::clone(&self.inner.workers);\n\n                move || {\n                    let _ = exit_sender.send(());\n                    if let Some(id) = id.lock().take() {\n                        workers.lock().remove(&id);\n                    }\n                }\n            },\n        )\n        .await?;\n\n        self.inner.handlers.new_worker.call_simple(&worker);\n\n        id.lock().replace(worker.id());\n\n        self.inner.workers.lock().insert(worker.id(), exit_receiver);\n\n        Ok(worker)\n    }\n\n    /// Callback is called when a new worker is created.\n    pub fn on_new_worker<F: Fn(&Worker) + Send + Sync + 'static>(&self, callback: F) -> HandlerId {\n        self.inner.handlers.new_worker.add(Arc::new(callback))\n    }\n}\n"
  },
  {
    "path": "rust/tests/integration/active_speaker_observer.rs",
    "content": "use async_io::Timer;\nuse futures_lite::future;\nuse mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions;\nuse mediasoup::router::RouterOptions;\nuse mediasoup::rtp_observer::RtpObserver;\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::rtp_parameters::{\n    MimeTypeAudio, RtpCodecCapability, RtpCodecParametersParameters,\n};\nuse std::env;\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::Opus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(2).unwrap(),\n        parameters: RtpCodecParametersParameters::from([\n            (\"useinbandfec\", 1_u32.into()),\n            (\"foo\", \"bar\".into()),\n        ]),\n        rtcp_feedback: vec![],\n    }]\n}\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn create() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let new_observer_count = Arc::new(AtomicUsize::new(0));\n\n        router\n            .on_new_rtp_observer({\n                let new_observer_count = Arc::clone(&new_observer_count);\n\n                move |_new_rtp_observer| {\n                    new_observer_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        assert_eq!(new_observer_count.load(Ordering::SeqCst), 1);\n        assert!(!active_speaker_observer.closed());\n        assert!(!active_speaker_observer.paused());\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(\n            dump.rtp_observer_ids.into_iter().collect::<Vec<_>>(),\n            vec![active_speaker_observer.id()]\n        );\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        let weak_active_speaker_observer = active_speaker_observer.downgrade();\n\n        assert!(weak_active_speaker_observer.upgrade().is_some());\n\n        drop(active_speaker_observer);\n\n        assert!(weak_active_speaker_observer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn pause_resume() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        active_speaker_observer\n            .pause()\n            .await\n            .expect(\"Failed to pause\");\n        assert!(active_speaker_observer.paused());\n\n        active_speaker_observer\n            .resume()\n            .await\n            .expect(\"Failed to resume\");\n        assert!(!active_speaker_observer.paused());\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        let _handler = active_speaker_observer.on_close(Box::new(move || {\n            let _ = tx.send(());\n        }));\n        drop(active_speaker_observer);\n\n        rx.await.expect(\"Failed to receive close event\");\n    });\n}\n\n#[test]\nfn drop_test() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let _active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        let active_speaker_observer_2 = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .expect(\"Failed to create ActiveSpeakerObserver\");\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(dump.rtp_observer_ids.len(), 2);\n\n        drop(active_speaker_observer_2);\n\n        // Drop is async, give it a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(dump.rtp_observer_ids.len(), 1);\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/audio_level_observer.rs",
    "content": "use async_io::Timer;\nuse futures_lite::future;\nuse mediasoup::audio_level_observer::AudioLevelObserverOptions;\nuse mediasoup::prelude::*;\nuse mediasoup::router::RouterOptions;\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::rtp_parameters::{\n    MimeTypeAudio, RtpCodecCapability, RtpCodecParametersParameters,\n};\nuse std::env;\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::Opus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(2).unwrap(),\n        parameters: RtpCodecParametersParameters::from([\n            (\"useinbandfec\", 1_u32.into()),\n            (\"foo\", \"bar\".into()),\n        ]),\n        rtcp_feedback: vec![],\n    }]\n}\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn create() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let new_observer_count = Arc::new(AtomicUsize::new(0));\n\n        router\n            .on_new_rtp_observer({\n                let new_observer_count = Arc::clone(&new_observer_count);\n\n                move |_new_rtp_observer| {\n                    new_observer_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        assert_eq!(new_observer_count.load(Ordering::SeqCst), 1);\n        assert!(!audio_level_observer.closed());\n        assert!(!audio_level_observer.paused());\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(\n            dump.rtp_observer_ids.into_iter().collect::<Vec<_>>(),\n            vec![audio_level_observer.id()]\n        );\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        let weak_audio_level_observer = audio_level_observer.downgrade();\n\n        assert!(weak_audio_level_observer.upgrade().is_some());\n\n        drop(audio_level_observer);\n\n        assert!(weak_audio_level_observer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn pause_resume() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        audio_level_observer.pause().await.expect(\"Failed to pause\");\n        assert!(audio_level_observer.paused());\n\n        audio_level_observer\n            .resume()\n            .await\n            .expect(\"Failed to resume\");\n        assert!(!audio_level_observer.paused());\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        let _handler = audio_level_observer.on_close(Box::new(move || {\n            let _ = tx.send(());\n        }));\n        drop(audio_level_observer);\n\n        rx.await.expect(\"Failed to receive close event\");\n    });\n}\n\n#[test]\nfn drop_test() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let _audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        let audio_level_observer_2 = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .expect(\"Failed to create AudioLevelObserver\");\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(dump.rtp_observer_ids.len(), 2);\n\n        drop(audio_level_observer_2);\n\n        // Drop is async, give it a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        let dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(dump.rtp_observer_ids.len(), 1);\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/consumer.rs",
    "content": "use async_executor::Executor;\nuse async_io::Timer;\nuse futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse mediasoup::consumer::{ConsumerLayers, ConsumerOptions, ConsumerScore, ConsumerType};\nuse mediasoup::prelude::*;\nuse mediasoup::producer::ProducerOptions;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::transport::ConsumeError;\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeType, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters,\n    RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters,\n    RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtension,\n    RtpHeaderExtensionDirection, RtpHeaderExtensionParameters, RtpHeaderExtensionUri,\n    RtpParameters,\n};\nuse mediasoup_types::scalability_modes::ScalabilityMode;\nuse parking_lot::Mutex;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\nuse std::{env, thread};\n\nstruct ProducerAppData {\n    _foo: i32,\n    _bar: &'static str,\n}\n\nstruct ConsumerAppData {\n    baz: &'static str,\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"foo\", \"bar\".into())]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 111,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"usedtx\", 1_u32.into()),\n                    (\"foo\", \"222.222\".into()),\n                    (\"bar\", \"333\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n            encodings: vec![RtpEncodingParameters {\n                ssrc: Some(11111111),\n                ..RtpEncodingParameters::default()\n            }],\n            rtcp: RtcpParameters {\n                cname: Some(\"FOOBAR\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: Some(\"1111-1111-1111-1111 2222-2222-2222-2222\".to_string()),\n        },\n    );\n\n    options.app_data = AppData::new(ProducerAppData { _foo: 1, _bar: \"2\" });\n\n    options\n}\n\nfn video_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Video,\n        RtpParameters {\n            mid: Some(\"VIDEO\".to_string()),\n            codecs: vec![\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::H264,\n                    payload_type: 112,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([\n                        (\"packetization-mode\", 1_u32.into()),\n                        (\"profile-level-id\", \"4d0032\".into()),\n                    ]),\n                    rtcp_feedback: vec![\n                        RtcpFeedback::Nack,\n                        RtcpFeedback::NackPli,\n                        RtcpFeedback::GoogRemb,\n                    ],\n                },\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Rtx,\n                    payload_type: 113,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([(\"apt\", 112u32.into())]),\n                    rtcp_feedback: vec![],\n                },\n            ],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 13,\n                    encrypt: false,\n                },\n            ],\n            encodings: vec![\n                RtpEncodingParameters {\n                    ssrc: Some(22222222),\n                    scalability_mode: \"L1T5\".parse().unwrap(),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222224),\n                    scalability_mode: \"L1T5\".parse().unwrap(),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222226),\n                    scalability_mode: \"L1T5\".parse().unwrap(),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222228),\n                    scalability_mode: \"L1T5\".parse().unwrap(),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }),\n                    ..RtpEncodingParameters::default()\n                },\n            ],\n            rtcp: RtcpParameters {\n                cname: Some(\"FOOBAR\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    );\n\n    options.app_data = AppData::new(ProducerAppData { _foo: 1, _bar: \"2\" });\n\n    options\n}\n\nfn consumer_device_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: Some(100),\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::Nack],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::H264,\n                preferred_payload_type: Some(101),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"level-asymmetry-allowed\", 1_u32.into()),\n                    (\"packetization-mode\", 1_u32.into()),\n                    (\"profile-level-id\", \"4d0032\".into()),\n                ]),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::GoogRemb,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: Some(102),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ],\n        header_extensions: vec![\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::RtpStreamId,\n                preferred_id: 2,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                preferred_id: 6,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::VideoOrientation,\n                preferred_id: 8,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::TimeOffset,\n                preferred_id: 9,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n        ],\n    }\n}\n\n// Keeps executor threads running until dropped\nstruct ExecutorGuard {\n    // Silence clippy warnings\n    _senders: Vec<async_oneshot::Sender<()>>,\n}\n\nimpl ExecutorGuard {\n    fn new(_senders: Vec<async_oneshot::Sender<()>>) -> Self {\n        Self { _senders }\n    }\n}\n\nfn create_executor() -> (ExecutorGuard, Arc<Executor<'static>>) {\n    let executor = Arc::new(Executor::new());\n    let thread_count = 4;\n\n    let senders = (0..thread_count)\n        .map(|_| {\n            let (tx, rx) = async_oneshot::oneshot::<()>();\n\n            thread::Builder::new()\n                .name(\"ex-mediasoup-worker\".into())\n                .spawn({\n                    let executor = Arc::clone(&executor);\n\n                    move || {\n                        future::block_on(executor.run(async move {\n                            let _ = rx.await;\n                        }));\n                    }\n                })\n                .unwrap();\n\n            tx\n        })\n        .collect();\n\n    (ExecutorGuard::new(senders), executor)\n}\n\nasync fn init() -> (\n    ExecutorGuard,\n    Worker,\n    Router,\n    WebRtcTransport,\n    WebRtcTransport,\n) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let (executor_guard, executor) = create_executor();\n    // Use multi-threaded executor in this module as a regression test for the crashes we had before\n    let worker_manager = WorkerManager::with_executor(executor);\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport_1 = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (executor_guard, worker, router, transport_1, transport_2)\n}\n\n#[test]\nfn consume_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let video_producer = transport_1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let new_consumer_count = Arc::new(AtomicUsize::new(0));\n\n        transport_2\n            .on_new_consumer({\n                let new_consumer_count = Arc::clone(&new_consumer_count);\n\n                Arc::new(move |_consumer| {\n                    new_consumer_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        let audio_consumer;\n        {\n            assert!(router.can_consume(&audio_producer.id(), &consumer_device_capabilities));\n\n            audio_consumer = transport_2\n                .consume({\n                    let mut options = ConsumerOptions::new(\n                        audio_producer.id(),\n                        consumer_device_capabilities.clone(),\n                    );\n                    options.app_data = AppData::new(ConsumerAppData { baz: \"LOL\" });\n                    options\n                })\n                .await\n                .expect(\"Failed to consume audio\");\n\n            assert_eq!(new_consumer_count.load(Ordering::SeqCst), 1);\n            assert_eq!(audio_consumer.producer_id(), audio_producer.id());\n            assert!(!audio_consumer.closed());\n            assert_eq!(audio_consumer.kind(), MediaKind::Audio);\n            assert_eq!(audio_consumer.rtp_parameters().mid, Some(\"0\".to_string()));\n            assert_eq!(\n                audio_consumer.rtp_parameters().codecs,\n                vec![RtpCodecParameters::Audio {\n                    mime_type: MimeTypeAudio::Opus,\n                    payload_type: 100,\n                    clock_rate: NonZeroU32::new(48000).unwrap(),\n                    channels: NonZeroU8::new(2).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([\n                        (\"useinbandfec\", 1_u32.into()),\n                        (\"usedtx\", 1_u32.into()),\n                        (\"foo\", \"222.222\".into()),\n                        (\"bar\", \"333\".into()),\n                    ]),\n                    rtcp_feedback: vec![],\n                }]\n            );\n            assert_eq!(\n                audio_consumer.rtp_parameters().msid,\n                Some(\"1111-1111-1111-1111 2222-2222-2222-2222\".to_string())\n            );\n            assert_eq!(audio_consumer.r#type(), ConsumerType::Simple);\n            assert!(!audio_consumer.paused());\n            assert!(!audio_consumer.producer_paused());\n            assert_eq!(audio_consumer.priority(), 1);\n            assert_eq!(\n                audio_consumer.score(),\n                ConsumerScore {\n                    score: 10,\n                    producer_score: 0,\n                    producer_scores: vec![0]\n                }\n            );\n            assert_eq!(audio_consumer.preferred_layers(), None);\n            assert_eq!(audio_consumer.current_layers(), None);\n            assert_eq!(\n                audio_consumer\n                    .app_data()\n                    .downcast_ref::<ConsumerAppData>()\n                    .unwrap()\n                    .baz,\n                \"LOL\"\n            );\n\n            let router_dump = router.dump().await.expect(\"Failed to get router dump\");\n\n            assert_eq!(router_dump.map_producer_id_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(audio_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(audio_consumer.id());\n                    set\n                });\n                map.insert(video_producer.id(), HashedSet::default());\n                map\n            });\n\n            let transport_2_dump = transport_2\n                .dump()\n                .await\n                .expect(\"Failed to get transport 2 dump\");\n\n            assert_eq!(transport_2_dump.producer_ids, vec![]);\n            assert_eq!(transport_2_dump.consumer_ids, vec![audio_consumer.id()]);\n        }\n\n        let video_consumer;\n        {\n            assert!(router.can_consume(&video_producer.id(), &consumer_device_capabilities));\n\n            video_consumer = transport_2\n                .consume({\n                    let mut options = ConsumerOptions::new(\n                        video_producer.id(),\n                        consumer_device_capabilities.clone(),\n                    );\n                    options.paused = true;\n                    options.preferred_layers = Some(ConsumerLayers {\n                        spatial_layer: 12,\n                        temporal_layer: Some(0),\n                    });\n                    options.app_data = AppData::new(ConsumerAppData { baz: \"LOL\" });\n                    options\n                })\n                .await\n                .expect(\"Failed to consume video\");\n\n            assert_eq!(new_consumer_count.load(Ordering::SeqCst), 2);\n            assert_eq!(video_consumer.producer_id(), video_producer.id());\n            assert!(!video_consumer.closed());\n            assert_eq!(video_consumer.kind(), MediaKind::Video);\n            assert_eq!(video_consumer.rtp_parameters().mid, Some(\"1\".to_string()));\n            assert_eq!(\n                video_consumer.rtp_parameters().codecs,\n                vec![\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::H264,\n                        payload_type: 103,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"packetization-mode\", 1_u32.into()),\n                            (\"profile-level-id\", \"4d0032\".into()),\n                        ]),\n                        rtcp_feedback: vec![\n                            RtcpFeedback::Nack,\n                            RtcpFeedback::NackPli,\n                            RtcpFeedback::CcmFir,\n                            RtcpFeedback::GoogRemb,\n                        ],\n                    },\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::Rtx,\n                        payload_type: 104,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([(\"apt\", 103u32.into())]),\n                        rtcp_feedback: vec![],\n                    },\n                ]\n            );\n            assert_eq!(video_consumer.rtp_parameters().msid, None);\n            assert_eq!(video_consumer.r#type(), ConsumerType::Simulcast);\n            assert!(video_consumer.paused());\n            assert!(video_consumer.producer_paused());\n            assert_eq!(video_consumer.priority(), 1);\n            assert_eq!(\n                video_consumer.score(),\n                ConsumerScore {\n                    score: 10,\n                    producer_score: 0,\n                    producer_scores: vec![0, 0, 0, 0]\n                }\n            );\n            assert_eq!(\n                video_consumer.preferred_layers(),\n                Some(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: Some(0)\n                })\n            );\n            assert_eq!(video_consumer.current_layers(), None);\n            assert_eq!(\n                video_consumer\n                    .app_data()\n                    .downcast_ref::<ConsumerAppData>()\n                    .unwrap()\n                    .baz,\n                \"LOL\"\n            );\n\n            video_consumer\n                .get_stats()\n                .await\n                .expect(\"Failed to get consumer stats\");\n\n            let router_dump = router.dump().await.expect(\"Failed to get router dump\");\n\n            assert_eq!(router_dump.map_producer_id_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(audio_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(audio_consumer.id());\n                    set\n                });\n                map.insert(video_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(video_consumer.id());\n                    set\n                });\n                map\n            });\n\n            let mut transport_2_dump = transport_2\n                .dump()\n                .await\n                .expect(\"Failed to get transport 2 dump\");\n\n            assert_eq!(transport_2_dump.producer_ids, vec![]);\n            {\n                transport_2_dump.consumer_ids.sort();\n                let mut expected_consumer_ids = vec![audio_consumer.id(), video_consumer.id()];\n                expected_consumer_ids.sort();\n                assert_eq!(transport_2_dump.consumer_ids, expected_consumer_ids);\n            }\n        }\n\n        let video_pipe_consumer;\n        {\n            assert!(router.can_consume(&video_producer.id(), &consumer_device_capabilities));\n\n            video_pipe_consumer = transport_2\n                .consume({\n                    let mut options = ConsumerOptions::new(\n                        video_producer.id(),\n                        consumer_device_capabilities.clone(),\n                    );\n                    options.pipe = true;\n                    options\n                })\n                .await\n                .expect(\"Failed to consume video\");\n\n            assert_eq!(new_consumer_count.load(Ordering::SeqCst), 3);\n            assert_eq!(video_pipe_consumer.producer_id(), video_producer.id());\n            assert!(!video_pipe_consumer.closed());\n            assert_eq!(video_pipe_consumer.kind(), MediaKind::Video);\n            assert_eq!(video_pipe_consumer.rtp_parameters().mid, None);\n            assert_eq!(\n                video_pipe_consumer.rtp_parameters().codecs,\n                vec![\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::H264,\n                        payload_type: 103,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"packetization-mode\", 1_u32.into()),\n                            (\"profile-level-id\", \"4d0032\".into()),\n                        ]),\n                        rtcp_feedback: vec![\n                            RtcpFeedback::Nack,\n                            RtcpFeedback::NackPli,\n                            RtcpFeedback::CcmFir,\n                            RtcpFeedback::GoogRemb,\n                        ],\n                    },\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::Rtx,\n                        payload_type: 104,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([(\"apt\", 103u32.into())]),\n                        rtcp_feedback: vec![],\n                    },\n                ]\n            );\n            assert_eq!(video_pipe_consumer.r#type(), ConsumerType::Pipe);\n            assert!(!video_pipe_consumer.paused());\n            assert!(video_pipe_consumer.producer_paused());\n            assert_eq!(video_pipe_consumer.priority(), 1);\n            assert_eq!(\n                video_pipe_consumer.score(),\n                ConsumerScore {\n                    score: 10,\n                    producer_score: 10,\n                    producer_scores: vec![0, 0, 0, 0]\n                },\n            );\n            assert_eq!(video_pipe_consumer.preferred_layers(), None);\n            assert_eq!(video_pipe_consumer.current_layers(), None);\n            assert_eq!(\n                video_pipe_consumer.app_data().downcast_ref::<()>().unwrap(),\n                &(),\n            );\n            video_pipe_consumer\n                .get_stats()\n                .await\n                .expect(\"Failed to get consumer stats\");\n\n            let router_dump = router.dump().await.expect(\"Failed to get router dump\");\n\n            assert_eq!(router_dump.map_producer_id_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(audio_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(audio_consumer.id());\n                    set\n                });\n                map.insert(video_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(video_consumer.id());\n                    set.insert(video_pipe_consumer.id());\n                    set\n                });\n                map\n            });\n\n            let mut transport_2_dump = transport_2\n                .dump()\n                .await\n                .expect(\"Failed to get transport 2 dump\");\n\n            assert_eq!(transport_2_dump.producer_ids, vec![]);\n            {\n                transport_2_dump.consumer_ids.sort();\n                let mut expected_consumer_ids = vec![\n                    audio_consumer.id(),\n                    video_consumer.id(),\n                    video_pipe_consumer.id(),\n                ];\n                expected_consumer_ids.sort();\n                assert_eq!(transport_2_dump.consumer_ids, expected_consumer_ids);\n            }\n        }\n    });\n}\n\n#[test]\nfn consume_with_enable_rtx_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        let audio_consumer = transport_2\n            .consume({\n                let mut options =\n                    ConsumerOptions::new(audio_producer.id(), consumer_device_capabilities.clone());\n                options.enable_rtx = Some(true);\n                options\n            })\n            .await\n            .expect(\"Failed to consume audio\");\n\n        assert_eq!(audio_consumer.kind(), MediaKind::Audio);\n        assert_eq!(audio_consumer.rtp_parameters().mid, Some(\"0\".to_string()));\n        assert_eq!(\n            audio_consumer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 100,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"usedtx\", 1_u32.into()),\n                    (\"foo\", \"222.222\".into()),\n                    (\"bar\", \"333\".into()),\n                ]),\n                rtcp_feedback: vec![RtcpFeedback::Nack],\n            }]\n        );\n    });\n}\n\n#[test]\nfn consumer_with_user_defined_mid() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let producer_1 = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let consumer_2_1 = transport_2\n            .consume(ConsumerOptions::new(\n                producer_1.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n        assert_eq!(\n            consumer_2_1.rtp_parameters().mid,\n            Some(\"0\".to_string()),\n            \"MID automatically assigned to sequential number\"\n        );\n\n        let consumer_2_2 = transport_2\n            .consume({\n                let mut options =\n                    ConsumerOptions::new(producer_1.id(), consumer_device_capabilities());\n                options.mid = Some(\"custom-mid\".to_owned());\n                options\n            })\n            .await\n            .expect(\"Failed to consume audio\");\n        assert_eq!(\n            consumer_2_2.rtp_parameters().mid,\n            Some(\"custom-mid\".to_string()),\n            \"MID is assigned to user-provided value\"\n        );\n\n        let consumer_2_3 = transport_2\n            .consume(ConsumerOptions::new(\n                producer_1.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n        assert_eq!(\n            consumer_2_3.rtp_parameters().mid,\n            Some(\"1\".to_string()),\n            \"MID automatically assigned to next sequential number\"\n        );\n    })\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let consumer = transport_2\n            .consume({\n                let mut options =\n                    ConsumerOptions::new(producer.id(), consumer_device_capabilities());\n                options.app_data = AppData::new(ConsumerAppData { baz: \"LOL\" });\n                options\n            })\n            .await\n            .expect(\"Failed to consume audio\");\n\n        let weak_consumer = consumer.downgrade();\n\n        assert!(weak_consumer.upgrade().is_some());\n\n        drop(consumer);\n\n        assert!(weak_consumer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn consume_incompatible_rtp_capabilities() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        {\n            let incompatible_device_capabilities = RtpCapabilities {\n                codecs: vec![RtpCodecCapability::Audio {\n                    mime_type: MimeTypeAudio::Isac,\n                    preferred_payload_type: Some(100),\n                    clock_rate: NonZeroU32::new(32_000).unwrap(),\n                    channels: NonZeroU8::new(1).unwrap(),\n                    parameters: RtpCodecParametersParameters::default(),\n                    rtcp_feedback: vec![],\n                }],\n                header_extensions: vec![],\n            };\n\n            assert!(!router.can_consume(&audio_producer.id(), &incompatible_device_capabilities));\n\n            assert!(matches!(\n                transport_2\n                    .consume(ConsumerOptions::new(\n                        audio_producer.id(),\n                        incompatible_device_capabilities,\n                    ))\n                    .await,\n                Err(ConsumeError::BadConsumerRtpParameters(_))\n            ));\n        }\n\n        {\n            let invalid_device_capabilities = RtpCapabilities {\n                codecs: vec![],\n                header_extensions: vec![],\n            };\n\n            assert!(!router.can_consume(&audio_producer.id(), &invalid_device_capabilities));\n\n            assert!(matches!(\n                transport_2\n                    .consume(ConsumerOptions::new(\n                        audio_producer.id(),\n                        invalid_device_capabilities,\n                    ))\n                    .await,\n                Err(ConsumeError::BadConsumerRtpParameters(_))\n            ));\n        }\n    });\n}\n\n#[test]\nfn dump_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let video_producer = transport_1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        {\n            let audio_consumer = transport_2\n                .consume(ConsumerOptions::new(\n                    audio_producer.id(),\n                    consumer_device_capabilities.clone(),\n                ))\n                .await\n                .expect(\"Failed to consume audio\");\n\n            let dump = audio_consumer\n                .dump()\n                .await\n                .expect(\"Audio consumer dump failed\");\n\n            assert_eq!(dump.id, audio_consumer.id());\n            assert_eq!(dump.producer_id, audio_consumer.producer_id());\n            assert_eq!(dump.kind, audio_consumer.kind());\n            assert_eq!(\n                dump.rtp_parameters.codecs,\n                vec![RtpCodecParameters::Audio {\n                    mime_type: MimeTypeAudio::Opus,\n                    payload_type: 100,\n                    clock_rate: NonZeroU32::new(48000).unwrap(),\n                    channels: NonZeroU8::new(2).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([\n                        (\"useinbandfec\", 1_u32.into()),\n                        (\"usedtx\", 1_u32.into()),\n                        (\"foo\", \"222.222\".into()),\n                        (\"bar\", \"333\".into()),\n                    ]),\n                    rtcp_feedback: vec![],\n                }],\n            );\n            assert_eq!(\n                dump.rtp_parameters.header_extensions,\n                vec![\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::Mid,\n                        id: 1,\n                        encrypt: false,\n                    },\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::AbsSendTime,\n                        id: 4,\n                        encrypt: false,\n                    },\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                        id: 6,\n                        encrypt: false,\n                    },\n                ],\n            );\n            assert_eq!(\n                dump.rtp_parameters.encodings,\n                vec![RtpEncodingParameters {\n                    codec_payload_type: Some(100),\n                    rtx: None,\n                    dtx: None,\n                    scalability_mode: ScalabilityMode::None,\n                    ssrc: audio_consumer\n                        .rtp_parameters()\n                        .encodings\n                        .first()\n                        .unwrap()\n                        .ssrc,\n                    rid: None,\n                    max_bitrate: None,\n                }],\n            );\n            assert_eq!(dump.r#type, ConsumerType::Simple);\n            assert_eq!(\n                dump.consumable_rtp_encodings,\n                audio_producer\n                    .consumable_rtp_parameters()\n                    .encodings\n                    .iter()\n                    .map(|encoding| RtpEncodingParameters {\n                        ssrc: encoding.ssrc,\n                        rid: None,\n                        codec_payload_type: None,\n                        rtx: None,\n                        max_bitrate: None,\n                        dtx: None,\n                        scalability_mode: ScalabilityMode::None,\n                    })\n                    .collect::<Vec<_>>()\n            );\n        }\n\n        {\n            let video_consumer = transport_2\n                .consume({\n                    let mut options =\n                        ConsumerOptions::new(video_producer.id(), consumer_device_capabilities);\n                    options.paused = true;\n                    options.preferred_layers = Some(ConsumerLayers {\n                        spatial_layer: 12,\n                        temporal_layer: None,\n                    });\n                    options\n                })\n                .await\n                .expect(\"Failed to consume video\");\n\n            let dump = video_consumer\n                .dump()\n                .await\n                .expect(\"Video consumer dump failed\");\n\n            assert_eq!(dump.id, video_consumer.id());\n            assert_eq!(dump.producer_id, video_consumer.producer_id());\n            assert_eq!(dump.kind, video_consumer.kind());\n            assert_eq!(\n                dump.rtp_parameters.codecs,\n                vec![\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::H264,\n                        payload_type: 103,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"packetization-mode\", 1_u32.into()),\n                            (\"profile-level-id\", \"4d0032\".into()),\n                        ]),\n                        rtcp_feedback: vec![\n                            RtcpFeedback::Nack,\n                            RtcpFeedback::NackPli,\n                            RtcpFeedback::CcmFir,\n                            RtcpFeedback::GoogRemb,\n                        ],\n                    },\n                    RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::Rtx,\n                        payload_type: 104,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([(\"apt\", 103u32.into())]),\n                        rtcp_feedback: vec![],\n                    }\n                ],\n            );\n            assert_eq!(\n                dump.rtp_parameters.header_extensions,\n                vec![\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::Mid,\n                        id: 1,\n                        encrypt: false,\n                    },\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::AbsSendTime,\n                        id: 4,\n                        encrypt: false,\n                    },\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::VideoOrientation,\n                        id: 8,\n                        encrypt: false,\n                    },\n                    RtpHeaderExtensionParameters {\n                        uri: RtpHeaderExtensionUri::TimeOffset,\n                        id: 9,\n                        encrypt: false,\n                    },\n                ],\n            );\n            assert_eq!(\n                dump.rtp_parameters.encodings,\n                vec![RtpEncodingParameters {\n                    codec_payload_type: Some(103),\n                    ssrc: video_consumer\n                        .rtp_parameters()\n                        .encodings\n                        .first()\n                        .unwrap()\n                        .ssrc,\n                    rtx: video_consumer\n                        .rtp_parameters()\n                        .encodings\n                        .first()\n                        .unwrap()\n                        .rtx,\n                    dtx: None,\n                    scalability_mode: \"L4T5\".parse().unwrap(),\n                    rid: None,\n                    max_bitrate: None,\n                }],\n            );\n            assert_eq!(dump.r#type, ConsumerType::Simulcast);\n            assert_eq!(\n                dump.consumable_rtp_encodings,\n                video_producer\n                    .consumable_rtp_parameters()\n                    .encodings\n                    .iter()\n                    .map(|encoding| RtpEncodingParameters {\n                        ssrc: encoding.ssrc,\n                        rid: None,\n                        codec_payload_type: None,\n                        rtx: None,\n                        max_bitrate: None,\n                        dtx: None,\n                        scalability_mode: \"L1T5\".parse().unwrap(),\n                    })\n                    .collect::<Vec<_>>()\n            );\n            assert_eq!(dump.supported_codec_payload_types, vec![103]);\n            assert!(dump.paused);\n            assert!(dump.producer_paused);\n            assert_eq!(dump.priority, 1);\n        }\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        {\n            let audio_producer = transport_1\n                .produce(audio_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let audio_consumer = transport_2\n                .consume(ConsumerOptions::new(\n                    audio_producer.id(),\n                    consumer_device_capabilities.clone(),\n                ))\n                .await\n                .expect(\"Failed to consume audio\");\n\n            let stats = audio_consumer\n                .get_stats()\n                .await\n                .expect(\"Audio consumer get_stats failed\");\n\n            let consumer_stat = stats.consumer_stats();\n\n            assert_eq!(consumer_stat.kind, MediaKind::Audio);\n            assert_eq!(\n                consumer_stat.mime_type,\n                MimeType::Audio(MimeTypeAudio::Opus)\n            );\n            assert_eq!(\n                consumer_stat.ssrc,\n                audio_consumer\n                    .rtp_parameters()\n                    .encodings\n                    .first()\n                    .unwrap()\n                    .ssrc\n                    .unwrap()\n            );\n        }\n\n        {\n            let video_producer = transport_1\n                .produce(video_producer_options())\n                .await\n                .expect(\"Failed to produce video\");\n\n            video_producer\n                .pause()\n                .await\n                .expect(\"Failed to pause video producer\");\n\n            let video_consumer = transport_2\n                .consume({\n                    let mut options =\n                        ConsumerOptions::new(video_producer.id(), consumer_device_capabilities);\n                    options.paused = true;\n                    options.preferred_layers = Some(ConsumerLayers {\n                        spatial_layer: 12,\n                        temporal_layer: None,\n                    });\n                    options\n                })\n                .await\n                .expect(\"Failed to consume video\");\n\n            let stats = video_consumer\n                .get_stats()\n                .await\n                .expect(\"Video consumer get_stats failed\");\n\n            let consumer_stat = stats.consumer_stats();\n\n            assert_eq!(consumer_stat.kind, MediaKind::Video);\n            assert_eq!(\n                consumer_stat.mime_type,\n                MimeType::Video(MimeTypeVideo::H264)\n            );\n            assert_eq!(\n                consumer_stat.ssrc,\n                video_consumer\n                    .rtp_parameters()\n                    .encodings\n                    .first()\n                    .unwrap()\n                    .ssrc\n                    .unwrap()\n            );\n        }\n    });\n}\n\n#[test]\nfn pause_resume_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_consumer = transport_2\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        {\n            audio_consumer\n                .pause()\n                .await\n                .expect(\"Failed to pause consumer\");\n\n            let dump = audio_consumer.dump().await.expect(\"Consumer dump failed\");\n\n            assert!(dump.paused);\n        }\n\n        {\n            audio_consumer\n                .resume()\n                .await\n                .expect(\"Failed to resume consumer\");\n\n            let dump = audio_consumer.dump().await.expect(\"Consumer dump failed\");\n\n            assert!(!dump.paused);\n        }\n    });\n}\n\n#[test]\nfn set_preferred_layers_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        {\n            let audio_producer = transport_1\n                .produce(audio_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let audio_consumer = transport_2\n                .consume(ConsumerOptions::new(\n                    audio_producer.id(),\n                    consumer_device_capabilities.clone(),\n                ))\n                .await\n                .expect(\"Failed to consume audio\");\n\n            audio_consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 1,\n                    temporal_layer: Some(1),\n                })\n                .await\n                .expect(\"Failed to set preferred layers consumer\");\n\n            assert_eq!(audio_consumer.preferred_layers(), None);\n        }\n\n        {\n            let video_producer = transport_1\n                .produce(video_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let video_consumer = transport_2\n                .consume({\n                    let mut options =\n                        ConsumerOptions::new(video_producer.id(), consumer_device_capabilities);\n                    options.paused = true;\n                    options.preferred_layers = Some(ConsumerLayers {\n                        spatial_layer: 12,\n                        temporal_layer: None,\n                    });\n                    options\n                })\n                .await\n                .expect(\"Failed to consume video\");\n\n            video_consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 2,\n                    temporal_layer: Some(3),\n                })\n                .await\n                .expect(\"Failed to set preferred layers consumer\");\n\n            assert_eq!(\n                video_consumer.preferred_layers(),\n                Some(ConsumerLayers {\n                    spatial_layer: 2,\n                    temporal_layer: Some(3),\n                })\n            );\n\n            video_consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: None,\n                })\n                .await\n                .expect(\"Failed to set preferred layers consumer\");\n\n            assert_eq!(\n                video_consumer.preferred_layers(),\n                Some(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: Some(4),\n                })\n            );\n\n            video_consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: Some(0),\n                })\n                .await\n                .expect(\"Failed to set preferred layers consumer\");\n\n            assert_eq!(\n                video_consumer.preferred_layers(),\n                Some(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: Some(0),\n                })\n            );\n\n            video_consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 66,\n                    temporal_layer: Some(66),\n                })\n                .await\n                .expect(\"Failed to set preferred layers consumer\");\n\n            assert_eq!(\n                video_consumer.preferred_layers(),\n                Some(ConsumerLayers {\n                    spatial_layer: 3,\n                    temporal_layer: Some(4),\n                })\n            );\n        }\n    });\n}\n\n#[test]\nfn set_unset_priority_succeeds() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let video_producer = transport_1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let video_consumer = transport_2\n            .consume({\n                let mut options =\n                    ConsumerOptions::new(video_producer.id(), consumer_device_capabilities());\n                options.paused = true;\n                options.preferred_layers = Some(ConsumerLayers {\n                    spatial_layer: 12,\n                    temporal_layer: None,\n                });\n                options\n            })\n            .await\n            .expect(\"Failed to consume video\");\n\n        video_consumer\n            .set_priority(2)\n            .await\n            .expect(\"Failed to ser priority\");\n\n        assert_eq!(video_consumer.priority(), 2);\n\n        video_consumer\n            .unset_priority()\n            .await\n            .expect(\"Failed to ser priority\");\n\n        assert_eq!(video_consumer.priority(), 1);\n    });\n}\n\n#[test]\nfn producer_pause_resume_events() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_consumer = transport_2\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        {\n            let (tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = audio_consumer.on_producer_pause({\n                let tx = Mutex::new(Some(tx));\n\n                move || {\n                    let _ = tx.lock().take().unwrap().send(());\n                }\n            });\n            audio_producer\n                .pause()\n                .await\n                .expect(\"Failed to pause producer\");\n            rx.await.expect(\"Failed to receive producer paused event\");\n\n            assert!(!audio_consumer.paused());\n            assert!(audio_consumer.producer_paused());\n        }\n\n        {\n            let (tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = audio_consumer.on_producer_resume({\n                let tx = Mutex::new(Some(tx));\n\n                move || {\n                    let _ = tx.lock().take().unwrap().send(());\n                }\n            });\n            audio_producer\n                .resume()\n                .await\n                .expect(\"Failed to pause producer\");\n            rx.await.expect(\"Failed to receive producer paused event\");\n\n            assert!(!audio_consumer.paused());\n            assert!(!audio_consumer.producer_paused());\n        }\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_executor_guard, _worker, router, transport_1, transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_consumer = transport_2\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        {\n            let (mut tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = audio_consumer.on_close(move || {\n                let _ = tx.send(());\n            });\n            drop(audio_consumer);\n\n            rx.await.expect(\"Failed to receive close event\");\n        }\n\n        // Drop is async, give consumer a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_producer_id_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(audio_producer.id(), HashedSet::default());\n                map\n            });\n            assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default());\n        }\n\n        {\n            let dump = transport_2.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.producer_ids, vec![]);\n            assert_eq!(dump.consumer_ids, vec![]);\n        }\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/data/dtls-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFazCCA1OgAwIBAgIUXy3udbf5+Rvhx3MaNGn7vj+zi+UwDQYJKoZIhvcNAQEL\nBQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM\nGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA2MjQyMTQwNTZaFw0yMzA2\nMjQyMTQwNTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw\nHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQDLXJlS6702GKmSsfOFWzMP2+NvlgNySLiqnAf6bBuX\nVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf21qaY7VTUbUag6i+Ghc4I15ZRzONP\ngv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa+h1spKyBHs96pFQQzpVNAnGSifk7\nhPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8KlJgm1fIbxdtVn60T7hz54/OuikHBD\njgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjptUGA3BUISf66SS31rP+ol2frlOY9\nQZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQKMO76LaFcRnkbO0GxDnKw/T+1LGqb\nJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtOzDxueJT9fXBAW5etrp7KvWmDY6/5\nHn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXDtZzACRaZ9hjCe8WmzPXVEEOvVIP9\nh5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKuf/ax+0gpeu5VpHNkvOIUyM/QTiBi\nD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT112E3RJJueYTc3IzUPefiHFmCfFZ\neUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7QpKOLIJik9tPm8hScggwLvq0YIwnvN\nVQIDAQABo1MwUTAdBgNVHQ4EFgQUe/9tLjutYDz+myVgALKjHDuXm2QwHwYDVR0j\nBBgwFoAUe/9tLjutYDz+myVgALKjHDuXm2QwDwYDVR0TAQH/BAUwAwEB/zANBgkq\nhkiG9w0BAQsFAAOCAgEAXi18lNqjeOXV6P/snVs4b34OiWpkRlDxKKMG95rdRKeF\nkEmPpO7T293nXCGvFmGfME6KBhio4w0MVMlbtC5TVdTFJk6mSgkCEtncA5yEv8Ga\nw2HCoEWfkOpea3S1XL9i5EWVPKvJG/rJ3YMZuh1BvppfC73dHVFSdik5SXNCGqEy\n9OuhJHbpbdlMuwFTLKwQCKDwh5Yvzd0eASyYlJ6Ytpf7TLCc3nvUS9haSOKEfnHQ\nv3TLt2WA+xHK7XIj9qoYuWIsnkXAWIsSJy/utJrDhtym7BcxB+ss7doHkS0LCTHd\nDiAhJwvTMKPYSRA55oF9iejynrcBOidH+tQxYMXHaDisNtH+6ZOtxkcLleneKhNB\n9EAMw9qeyiVl+MHDQi+sA5ksMfVoXzvxqObNGSz9g5z1AfnNuiaX1z9ajXsiHJ5e\n7EQFVCU4Id519RcSEUY2qcKTNMBPyXNbTQfd/oV1C6pzF01tVaNpl12dsXdx+JTh\n9vbK9tI2U+jzIb0Y8ersKrAjRQO/1lfIbjdJ+e0rFlxX1+SmlKlcArOfd8PSuifx\nwbzCJeRjFdOdf2D6mVHO0uMghjnVTHxB3KX6QonKOFzkupwWBeVqginXm4pEaUhK\nnssuSpkKrVshQ3RSEq3H9yRdQQ0qqwdOd3dISEqucVvaEsxhUwHBX/R5p8W0OCo=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "rust/tests/integration/data/dtls-key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDLXJlS6702GKmS\nsfOFWzMP2+NvlgNySLiqnAf6bBuXVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf2\n1qaY7VTUbUag6i+Ghc4I15ZRzONPgv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa\n+h1spKyBHs96pFQQzpVNAnGSifk7hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8Kl\nJgm1fIbxdtVn60T7hz54/OuikHBDjgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjp\ntUGA3BUISf66SS31rP+ol2frlOY9QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQK\nMO76LaFcRnkbO0GxDnKw/T+1LGqbJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtO\nzDxueJT9fXBAW5etrp7KvWmDY6/5Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXD\ntZzACRaZ9hjCe8WmzPXVEEOvVIP9h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKu\nf/ax+0gpeu5VpHNkvOIUyM/QTiBiD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT\n112E3RJJueYTc3IzUPefiHFmCfFZeUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7Qp\nKOLIJik9tPm8hScggwLvq0YIwnvNVQIDAQABAoICAAd5PTdDa5431M+L06fEfMFp\n8tdQe4rxKjw25MIqw+7RaE0U6SB1Ei2M1chblACVGgtXKT0oCBWAo32aUOAQ5Muz\nwmM6iAmZFEPV7HPQJFBxP4finqjYq7Hpf2WuqRHdjx6jBMndOlhH/a/KHle2S5Kp\nN7XJoT9G4EzGuLbKdErgLXfiF5bReGwVZqREHPOI7CLWO5gfxgWUoEYiejvdujXY\niKo7hrr5su2OWfiM91s8Nj2jWfsoCTEiI93xBcG3n+W1xTSzlLdt8z53h1M9g1Zd\nJcvh0ZsUQwcGnW6Wd8mrhbYgOHBxjAVnJLqupX+1L1Ii8ANMDMyK/P104+t0zte9\nBpuBzerm9h69UQq7d728YKW2KfY4M5+oLG2l4PKH6YDhoymIso38TYMixutkXpIs\nxZXATFA82sZlcpbSjLJypeJnKxBCbKxu8eYTq5K2SDdq8r82DwZZT/N8FfQMGZoO\nYYtP2KfUSxc2azpQtEHmB00fiPXgCdJNU2I8+J84Ve/ltBDlJcNkJN09h5ob9q/g\nQ8qJQRrzpLedBRhyqharYyDS9oGDUlR9OfmR7vFzUNP5npgNts0TDGy/JpVdY9aC\nYmqWsC8c/Kmms7X+4KaXMsBEpaVNSx9FsoQiPi1CMdy/KpWGcshusdVwgdklrOnz\niKBvvMNvP+gu7bV+uoR3AoIBAQDqBkUaMbVUVup94WqOIFjEl13CQwVwLgmLQaw0\n9BjPkv8BaPiMUfg7ANqEi8V+iJX5m27fcsvWIMUKBe0FvLdo/kH/NahNxD0bub3S\nOikGeJEo0QDsIKhVXTLjYJ/N+qk2P3WYWy/7yGV3kVDkEam0oebmdYNQsfpXK+wu\nGt0hpjU2RDMB2NCmtLHQXCI5DUxX0CtfJD5JTop9tLTRJKnputsIsQZPSmNSTuTl\nFVO0JdyJNv97Xhc8YUzwTfwl73EW7srcJNKO2ZwkzOZ8EghaEa1c5BZ6oV2MWe5f\nHIrRbW9LCYnBavoTC7VGWYkKiklAm5pRfvj9fQylv4tiSRUfAoIBAQDedTl3fAFf\ni/8ak0WfJKn/H0Z2W3AcgeChyBAWdrOrTDZUdhV0olcIT7m1v2qbWQju8boPdJiY\nBqG2DD3CZHS+qETt4lP5r+9j2gvy5g3wdQ4bYpjyr2hRXkwMK2yYQbXCmgmDETb+\nyNTj2avhAA32BmTH9QyxXSGN7ympVwEP1kDcXHM8vDGL7FvGGdRPFg0h4LKHrCHa\nxCQEWPjBv8oPo8Gb2ateSZjeWCraRhTXd7yVWC5s+3uEfW/h+g/QJVrRh2p8ipGv\nzlWDR1BaF+t4racWHFeftrSkc3ZpN/eAVyLXBSTo7plOx8tFujrakJedmZssrHDc\nRc0JeDOrjnsLAoIBAQDGXoo0qe4Kj6I1Ed5Amyqjear//8+cR2nPoNtYB5EAYpnF\nmDUWvGStnwubTt8ZYq295wMUZTpjR2O+G0fOlSji1qMasWD4il9CIS/GA4bC9XAW\nKROfFA+cTGPWWREciFzmnuQPQTxrMHLR51up907izlnq/7FPtY1+VrzcV+kZnMl+\nNlEGP8KdjI0tEOvxcFRGGy6odxBVEz5RT9v1bB6bAMiplWTD0UpfeoCLrohFK9LE\nfNoSuK75f4C4MWKKxWwXBFLwSEYy0EKK7yRwBtkNf+5zzuM/D4k8bv6foJIK87hi\n4rLiQMu5WTNPbpW7WXy+RyeH7Rkhxd3yoWqE5W4BAoIBADjNOdU2hqs89fB1NkvC\nct2/wKAsDN5ak1771I/H02yj0yOR2zyizxJCOSsdKz1raIqKknWr0eLPnq77RTHD\nsMOV97O+HK8eq0OVw4NMFrcVTHrVnDQrcbmFGGnrFJlz/dMovdEHrkE0Spe7VtXm\ny6nMTCN6gLkxDIZPURX6Lz05+enKeWpCq2wM+AoHQlzHRqcl1rAp1aMkfgXWKf5e\n2FtR9veyhr1WkYAEhzygtGWoHzELCR+uvwU/ejf7P9poD15880XFpBl91/vjU7MN\ndISl4ooUxpLzdgCfstZ/AeV1WmII4DnR4rdo8JBnUuvIC86kEClCBrdX41jNpnPh\nt60CggEAQnioYh4vwJctZENvO6nKlexCui2sQmHi4hWwzzA0s8tG2iG++nWXUEV+\nT+SBuCnIZMhnXlFIbqx+gjke3xLxJVbsgeRHDwDMZbkcaE35L1ZdAjIhuWgoNa20\nRSXApmp376VNEoU3lJH6jK0Wa67N9IaD3P5Mb+jekU16beqVgMYWf/YXycvD6bEp\nL9CODZuTPS/Ue1waBP7bKWAH8PRKAdXQACkn+aRBqYm9a5o7Z5jD/Qzo9ia5VaK4\nj+EQ0Gk4BaVBnvIu4LQ/HKuZm4v1NWNy3UhFuoTAzxNoCrQamYII/EaCMqiLATGH\njBJvmV3y/KoVcZGzNGFVB29Ns8VK1g==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "rust/tests/integration/data_consumer.rs",
    "content": "use async_io::Timer;\nuse futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse mediasoup::data_consumer::{DataConsumerOptions, DataConsumerType};\nuse mediasoup::data_producer::{DataProducer, DataProducerOptions};\nuse mediasoup::direct_transport::DirectTransportOptions;\nuse mediasoup::plain_transport::PlainTransportOptions;\nuse mediasoup::prelude::*;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nstruct CustomAppData {\n    baz: &'static str,\n}\n\nstruct CustomAppData2 {\n    hehe: &'static str,\n}\n\nfn sctp_data_producer_options() -> DataProducerOptions {\n    let mut options = DataProducerOptions::new_sctp(\n        SctpStreamParameters::new_unordered_with_life_time(12345, 5000),\n    );\n\n    options.label = \"foo\".to_string();\n    options.protocol = \"bar\".to_string();\n\n    options\n}\n\nasync fn init() -> (Worker, Router, WebRtcTransport, DataProducer) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let webrtc_transport = router\n        .create_webrtc_transport({\n            let mut transport_options =\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }));\n\n            transport_options.enable_sctp = true;\n\n            transport_options\n        })\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let sctp_data_producer = webrtc_transport\n        .produce_data(sctp_data_producer_options())\n        .await\n        .expect(\"Failed to create data producer\");\n\n    (worker, router, webrtc_transport, sctp_data_producer)\n}\n\n#[test]\nfn consume_data_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let plain_transport = router\n            .create_plain_transport({\n                let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                transport_options.enable_sctp = true;\n\n                transport_options\n            })\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let new_data_consumer_count = Arc::new(AtomicUsize::new(0));\n\n        plain_transport\n            .on_new_data_consumer({\n                let new_data_consumer_count = Arc::clone(&new_data_consumer_count);\n\n                Arc::new(move |_data_consumer| {\n                    new_data_consumer_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let data_consumer = plain_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_sctp(sctp_data_producer.id());\n\n                options.subchannels = Some(vec![0, 1, 1, 1, 2, 65535, 100]);\n                options.app_data = AppData::new(CustomAppData { baz: \"LOL\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        assert_eq!(data_consumer.data_producer_id(), sctp_data_producer.id());\n        assert!(!data_consumer.closed());\n        assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = data_consumer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(\n                sctp_stream_parameters.unwrap().max_packet_life_time(),\n                Some(5000)\n            );\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(data_consumer.label().as_str(), \"foo\");\n        assert_eq!(data_consumer.protocol().as_str(), \"bar\");\n\n        let mut sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [0, 1, 2, 100, 65535]);\n        assert_eq!(\n            data_consumer\n                .app_data()\n                .downcast_ref::<CustomAppData>()\n                .unwrap()\n                .baz,\n            \"LOL\",\n        );\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_data_producer_id_data_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(sctp_data_producer.id(), {\n                    let mut set = HashedSet::default();\n                    set.insert(data_consumer.id());\n                    set\n                });\n                map\n            });\n            assert_eq!(dump.map_data_consumer_id_data_producer_id, {\n                let mut map = HashedMap::default();\n                map.insert(data_consumer.id(), sctp_data_producer.id());\n                map\n            });\n        }\n\n        {\n            let dump = plain_transport\n                .dump()\n                .await\n                .expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, plain_transport.id());\n            assert_eq!(dump.data_producer_ids, vec![]);\n            assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]);\n        }\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let plain_transport = router\n            .create_plain_transport({\n                let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                transport_options.enable_sctp = true;\n\n                transport_options\n            })\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let data_consumer = plain_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time(\n                    sctp_data_producer.id(),\n                    4000,\n                );\n\n                options.app_data = AppData::new(CustomAppData { baz: \"LOL\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        let weak_data_consumer = data_consumer.downgrade();\n\n        assert!(weak_data_consumer.upgrade().is_some());\n\n        drop(data_consumer);\n\n        assert!(weak_data_consumer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn dump_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, webrtc_transport, sctp_data_producer) = init().await;\n\n        let data_consumer = webrtc_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_sctp_ordered(sctp_data_producer.id());\n\n                options.app_data = AppData::new(CustomAppData { baz: \"LOL\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        let dump = data_consumer\n            .dump()\n            .await\n            .expect(\"Data consumer dump failed\");\n\n        assert_eq!(dump.id, data_consumer.id());\n        assert_eq!(dump.data_producer_id, data_consumer.data_producer_id());\n        assert_eq!(dump.r#type, DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = dump.sctp_stream_parameters;\n            assert!(sctp_stream_parameters.is_some());\n            assert_eq!(\n                sctp_stream_parameters.unwrap().stream_id(),\n                data_consumer.sctp_stream_parameters().unwrap().stream_id(),\n            );\n            assert!(sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(dump.label.as_str(), \"foo\");\n        assert_eq!(dump.protocol.as_str(), \"bar\");\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, webrtc_transport, sctp_data_producer) = init().await;\n\n        let data_consumer = webrtc_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time(\n                    sctp_data_producer.id(),\n                    4000,\n                );\n\n                options.app_data = AppData::new(CustomAppData { baz: \"LOL\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        let stats = data_consumer\n            .get_stats()\n            .await\n            .expect(\"Failed to get data consumer stats\");\n\n        assert_eq!(stats.len(), 1);\n        assert_eq!(&stats[0].label, data_consumer.label());\n        assert_eq!(&stats[0].protocol, data_consumer.protocol());\n        assert_eq!(stats[0].messages_sent, 0);\n        assert_eq!(stats[0].bytes_sent, 0);\n    });\n}\n\n#[test]\nfn set_subchannels() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, sctp_data_producer) = init().await;\n\n        let data_consumer = transport1\n            .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time(\n                sctp_data_producer.id(),\n                4000,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        data_consumer\n            .set_subchannels([999, 999, 998, 0].to_vec())\n            .await\n            .expect(\"Failed to set data consumer subchannels\");\n\n        let mut sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [0, 998, 999]);\n    });\n}\n\n#[test]\nfn add_and_remove_subchannel() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, sctp_data_producer) = init().await;\n\n        let data_consumer = transport1\n            .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time(\n                sctp_data_producer.id(),\n                4000,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        data_consumer\n            .set_subchannels([].to_vec())\n            .await\n            .expect(\"Failed to set data consumer subchannels\");\n\n        assert_eq!(data_consumer.subchannels(), []);\n\n        data_consumer\n            .add_subchannel(5)\n            .await\n            .expect(\"Failed to add data consumer subchannel\");\n\n        assert_eq!(data_consumer.subchannels(), [5]);\n\n        data_consumer\n            .add_subchannel(10)\n            .await\n            .expect(\"Failed to add data consumer subchannel\");\n\n        let mut sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [5, 10]);\n\n        data_consumer\n            .add_subchannel(5)\n            .await\n            .expect(\"Failed to add data consumer subchannel\");\n\n        sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [5, 10]);\n\n        data_consumer\n            .remove_subchannel(666)\n            .await\n            .expect(\"Failed to remove data consumer subchannel\");\n\n        sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [5, 10]);\n\n        data_consumer\n            .remove_subchannel(5)\n            .await\n            .expect(\"Failed to remove data consumer subchannel\");\n\n        sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [10]);\n\n        data_consumer\n            .add_subchannel(5)\n            .await\n            .expect(\"Failed to add data consumer subchannel\");\n\n        sorted_subchannels = data_consumer.subchannels();\n        sorted_subchannels.sort();\n\n        assert_eq!(sorted_subchannels, [5, 10]);\n\n        data_consumer\n            .set_subchannels([].to_vec())\n            .await\n            .expect(\"Failed to set data consumer subchannels\");\n\n        assert_eq!(data_consumer.subchannels(), []);\n    });\n}\n\n#[test]\nfn consume_data_from_a_direct_data_producer_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, webrtc_transport, sctp_data_producer) = init().await;\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        let direct_data_producer = direct_transport\n            .produce_data(DataProducerOptions::new_direct())\n            .await\n            .expect(\"Failed to create data producer\");\n\n        let data_consumer = webrtc_transport\n            .consume_data(DataConsumerOptions::new_direct(\n                direct_data_producer.id(),\n                None,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        assert_eq!(data_consumer.data_producer_id(), direct_data_producer.id());\n        assert!(!data_consumer.closed());\n        assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = data_consumer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(data_consumer.label().as_str(), \"\");\n        assert_eq!(data_consumer.protocol().as_str(), \"\");\n\n        {\n            let dump = webrtc_transport\n                .dump()\n                .await\n                .expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, webrtc_transport.id());\n            assert_eq!(dump.data_producer_ids, vec![sctp_data_producer.id()]);\n            assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]);\n        }\n    });\n}\n\n#[test]\nfn dump_consuming_from_a_direct_data_producer_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, webrtc_transport, _sctp_data_producer) = init().await;\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        let direct_data_producer = direct_transport\n            .produce_data(DataProducerOptions::new_direct())\n            .await\n            .expect(\"Failed to create data producer\");\n\n        let data_consumer = webrtc_transport\n            .consume_data(DataConsumerOptions::new_sctp_unordered_with_retransmits(\n                direct_data_producer.id(),\n                2,\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let dump = data_consumer\n            .dump()\n            .await\n            .expect(\"Data consumer dump failed\");\n\n        assert_eq!(dump.id, data_consumer.id());\n        assert_eq!(dump.data_producer_id, data_consumer.data_producer_id());\n        assert_eq!(dump.r#type, DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = data_consumer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(2));\n        }\n        assert_eq!(dump.label.as_str(), \"\");\n        assert_eq!(dump.protocol.as_str(), \"\");\n    });\n}\n\n#[test]\nfn consume_data_on_direct_transport_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        let new_data_consumer_count = Arc::new(AtomicUsize::new(0));\n\n        direct_transport\n            .on_new_data_consumer({\n                let new_data_consumer_count = Arc::clone(&new_data_consumer_count);\n\n                Arc::new(move |_data_consumer| {\n                    new_data_consumer_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let data_consumer = direct_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None);\n\n                options.app_data = AppData::new(CustomAppData2 { hehe: \"HEHE\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        assert_eq!(new_data_consumer_count.load(Ordering::SeqCst), 1);\n        assert_eq!(data_consumer.data_producer_id(), sctp_data_producer.id());\n        assert!(!data_consumer.closed());\n        assert_eq!(data_consumer.r#type(), DataConsumerType::Direct);\n        assert_eq!(data_consumer.sctp_stream_parameters(), None);\n        assert_eq!(data_consumer.label().as_str(), \"foo\");\n        assert_eq!(data_consumer.protocol().as_str(), \"bar\");\n        assert_eq!(\n            data_consumer\n                .app_data()\n                .downcast_ref::<CustomAppData2>()\n                .unwrap()\n                .hehe,\n            \"HEHE\",\n        );\n\n        {\n            let dump = direct_transport\n                .dump()\n                .await\n                .expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, direct_transport.id());\n            assert_eq!(dump.data_producer_ids, vec![]);\n            assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]);\n        }\n    });\n}\n\n#[test]\nfn dump_on_direct_transport_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        let data_consumer = direct_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None);\n\n                options.app_data = AppData::new(CustomAppData2 { hehe: \"HEHE\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        let dump = data_consumer\n            .dump()\n            .await\n            .expect(\"Data consumer dump failed\");\n\n        assert_eq!(dump.id, data_consumer.id());\n        assert_eq!(dump.data_producer_id, data_consumer.data_producer_id());\n        assert_eq!(dump.r#type, DataConsumerType::Direct);\n        assert_eq!(dump.sctp_stream_parameters, None);\n        assert_eq!(dump.label.as_str(), \"foo\");\n        assert_eq!(dump.protocol.as_str(), \"bar\");\n    });\n}\n\n#[test]\nfn get_stats_on_direct_transport_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        let data_consumer = direct_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None);\n\n                options.app_data = AppData::new(CustomAppData2 { hehe: \"HEHE\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        let stats = data_consumer\n            .get_stats()\n            .await\n            .expect(\"Failed to get data consumer stats\");\n\n        assert_eq!(stats.len(), 1);\n        assert_eq!(&stats[0].label, data_consumer.label());\n        assert_eq!(&stats[0].protocol, data_consumer.protocol());\n        assert_eq!(stats[0].messages_sent, 0);\n        assert_eq!(stats[0].bytes_sent, 0);\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await;\n\n        let plain_transport = router\n            .create_plain_transport({\n                let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                transport_options.enable_sctp = true;\n\n                transport_options\n            })\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let data_consumer = plain_transport\n            .consume_data({\n                let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time(\n                    sctp_data_producer.id(),\n                    4000,\n                );\n\n                options.app_data = AppData::new(CustomAppData { baz: \"LOL\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to consume data\");\n\n        {\n            let (mut tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = data_consumer.on_close(move || {\n                let _ = tx.send(());\n            });\n            drop(data_consumer);\n\n            rx.await.expect(\"Failed to receive close event\");\n        }\n\n        // Drop is async, give consumer a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_data_producer_id_data_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(sctp_data_producer.id(), HashedSet::default());\n                map\n            });\n            assert_eq!(\n                dump.map_data_consumer_id_data_producer_id,\n                HashedMap::default()\n            );\n        }\n\n        {\n            let dump = plain_transport\n                .dump()\n                .await\n                .expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.data_producer_ids, vec![]);\n            assert_eq!(dump.data_consumer_ids, vec![]);\n        }\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/data_producer.rs",
    "content": "use async_io::Timer;\nuse futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse mediasoup::data_producer::{DataProducerOptions, DataProducerType};\nuse mediasoup::plain_transport::{PlainTransport, PlainTransportOptions};\nuse mediasoup::prelude::*;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::transport::ProduceDataError;\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::{RequestError, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\n\n#[derive(Debug, PartialEq)]\nstruct CustomAppData {\n    foo: u8,\n    baz: &'static str,\n}\n\nasync fn init() -> (Worker, Router, WebRtcTransport, PlainTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport1 = router\n        .create_webrtc_transport({\n            let mut transport_options =\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }));\n\n            transport_options.enable_sctp = true;\n\n            transport_options\n        })\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport2 = router\n        .create_plain_transport({\n            let mut transport_options = PlainTransportOptions::new(ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            });\n\n            transport_options.enable_sctp = true;\n\n            transport_options\n        })\n        .await\n        .expect(\"Failed to create transport1\");\n\n    (worker, router, transport1, transport2)\n}\n\n#[test]\nfn transport_1_produce_data_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, transport1, _transport2) = init().await;\n\n        let new_data_producer_count = Arc::new(AtomicUsize::new(0));\n\n        transport1\n            .on_new_data_producer({\n                let new_data_producer_count = Arc::clone(&new_data_producer_count);\n\n                Arc::new(move |_data_producer| {\n                    new_data_producer_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let data_producer1 = transport1\n            .produce_data({\n                let mut options =\n                    DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        assert_eq!(new_data_producer_count.load(Ordering::SeqCst), 1);\n        assert!(!data_producer1.closed());\n        assert_eq!(data_producer1.r#type(), DataProducerType::Sctp);\n        {\n            let sctp_stream_parameters = data_producer1.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 666);\n            assert!(sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(data_producer1.label().as_str(), \"foo\");\n        assert_eq!(data_producer1.protocol().as_str(), \"bar\");\n        assert!(!data_producer1.paused());\n        assert_eq!(\n            data_producer1\n                .app_data()\n                .downcast_ref::<CustomAppData>()\n                .unwrap(),\n            &CustomAppData { foo: 1, baz: \"2\" },\n        );\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_data_producer_id_data_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(data_producer1.id(), HashedSet::default());\n                map\n            });\n            assert_eq!(\n                dump.map_data_consumer_id_data_producer_id,\n                HashedMap::default()\n            );\n        }\n\n        {\n            let dump = transport1.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, transport1.id());\n            assert_eq!(dump.data_producer_ids, vec![data_producer1.id()]);\n            assert_eq!(dump.data_consumer_ids, vec![]);\n        }\n    });\n}\n\n#[test]\nfn transport_2_produce_data_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, _transport1, transport2) = init().await;\n\n        let new_data_producer_count = Arc::new(AtomicUsize::new(0));\n\n        transport2\n            .on_new_data_producer({\n                let new_data_producer_count = Arc::clone(&new_data_producer_count);\n\n                Arc::new(move |_data_producer| {\n                    new_data_producer_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let data_producer2 = transport2\n            .produce_data({\n                let mut options = DataProducerOptions::new_sctp(\n                    SctpStreamParameters::new_unordered_with_retransmits(777, 3),\n                );\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.paused = true;\n                options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        assert_eq!(new_data_producer_count.load(Ordering::SeqCst), 1);\n        assert!(!data_producer2.closed());\n        assert_eq!(data_producer2.r#type(), DataProducerType::Sctp);\n        {\n            let sctp_stream_parameters = data_producer2.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 777);\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(3));\n        }\n        assert_eq!(data_producer2.label().as_str(), \"foo\");\n        assert_eq!(data_producer2.protocol().as_str(), \"bar\");\n        assert!(data_producer2.paused());\n        assert_eq!(\n            data_producer2\n                .app_data()\n                .downcast_ref::<CustomAppData>()\n                .unwrap(),\n            &CustomAppData { foo: 1, baz: \"2\" },\n        );\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_data_producer_id_data_consumer_ids, {\n                let mut map = HashedMap::default();\n                map.insert(data_producer2.id(), HashedSet::default());\n                map\n            });\n            assert_eq!(\n                dump.map_data_consumer_id_data_producer_id,\n                HashedMap::default()\n            );\n        }\n\n        {\n            let dump = transport2.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, transport2.id());\n            assert_eq!(dump.data_producer_ids, vec![data_producer2.id()]);\n            assert_eq!(dump.data_consumer_ids, vec![]);\n        }\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, _transport2) = init().await;\n\n        let data_producer = transport1\n            .produce_data({\n                let mut options =\n                    DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        let weak_data_producer = data_producer.downgrade();\n\n        assert!(weak_data_producer.upgrade().is_some());\n\n        drop(data_producer);\n\n        assert!(weak_data_producer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn produce_data_used_stream_id_rejects() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, _transport2) = init().await;\n\n        let _data_producer1 = transport1\n            .produce_data(DataProducerOptions::new_sctp(\n                SctpStreamParameters::new_ordered(666),\n            ))\n            .await\n            .expect(\"Failed to produce data\");\n\n        assert!(matches!(\n            transport1\n                .produce_data(DataProducerOptions::new_sctp(\n                    SctpStreamParameters::new_ordered(666),\n                ))\n                .await,\n            Err(ProduceDataError::Request(RequestError::Response { .. })),\n        ));\n    });\n}\n\n#[test]\nfn dump_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, transport2) = init().await;\n\n        {\n            let data_producer1 = transport1\n                .produce_data({\n                    let mut options =\n                        DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                    options.label = \"foo\".to_string();\n                    options.protocol = \"bar\".to_string();\n                    options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                    options\n                })\n                .await\n                .expect(\"Failed to produce data\");\n\n            let dump = data_producer1\n                .dump()\n                .await\n                .expect(\"Data producer dump failed\");\n\n            assert_eq!(dump.id, data_producer1.id());\n            assert_eq!(dump.r#type, DataProducerType::Sctp);\n            {\n                let sctp_stream_parameters = dump.sctp_stream_parameters;\n                assert!(sctp_stream_parameters.is_some());\n                assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 666);\n                assert!(sctp_stream_parameters.unwrap().ordered());\n                assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n                assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n            }\n            assert_eq!(dump.label.as_str(), \"foo\");\n            assert_eq!(dump.protocol.as_str(), \"bar\");\n            assert!(!dump.paused);\n        }\n\n        {\n            let data_producer2 = transport2\n                .produce_data({\n                    let mut options = DataProducerOptions::new_sctp(\n                        SctpStreamParameters::new_unordered_with_retransmits(777, 3),\n                    );\n\n                    options.label = \"foo\".to_string();\n                    options.protocol = \"bar\".to_string();\n                    options.paused = true;\n                    options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                    options\n                })\n                .await\n                .expect(\"Failed to produce data\");\n\n            let dump = data_producer2\n                .dump()\n                .await\n                .expect(\"Data producer dump failed\");\n\n            assert_eq!(dump.id, data_producer2.id());\n            assert_eq!(dump.r#type, DataProducerType::Sctp);\n            {\n                let sctp_stream_parameters = dump.sctp_stream_parameters;\n                assert!(sctp_stream_parameters.is_some());\n                assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 777);\n                assert!(!sctp_stream_parameters.unwrap().ordered());\n                assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None);\n                assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(3));\n            }\n            assert_eq!(dump.label.as_str(), \"foo\");\n            assert_eq!(dump.protocol.as_str(), \"bar\");\n            assert!(dump.paused);\n        }\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, transport2) = init().await;\n\n        {\n            let data_producer1 = transport1\n                .produce_data({\n                    let mut options =\n                        DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                    options.label = \"foo\".to_string();\n                    options.protocol = \"bar\".to_string();\n                    options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                    options\n                })\n                .await\n                .expect(\"Failed to produce data\");\n\n            let stats = data_producer1\n                .get_stats()\n                .await\n                .expect(\"Failed to get data producer stats\");\n\n            assert_eq!(stats.len(), 1);\n            assert_eq!(&stats[0].label, data_producer1.label());\n            assert_eq!(&stats[0].protocol, data_producer1.protocol());\n            assert_eq!(stats[0].messages_received, 0);\n            assert_eq!(stats[0].bytes_received, 0);\n        }\n\n        {\n            let data_producer2 = transport2\n                .produce_data({\n                    let mut options = DataProducerOptions::new_sctp(\n                        SctpStreamParameters::new_unordered_with_retransmits(777, 3),\n                    );\n\n                    options.label = \"foo\".to_string();\n                    options.protocol = \"bar\".to_string();\n                    options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                    options\n                })\n                .await\n                .expect(\"Failed to produce data\");\n\n            let stats = data_producer2\n                .get_stats()\n                .await\n                .expect(\"Failed to get data producer stats\");\n\n            assert_eq!(stats.len(), 1);\n            assert_eq!(&stats[0].label, data_producer2.label());\n            assert_eq!(&stats[0].protocol, data_producer2.protocol());\n            assert_eq!(stats[0].messages_received, 0);\n            assert_eq!(stats[0].bytes_received, 0);\n        }\n    });\n}\n\n#[test]\nfn pause_and_resume_succeed() {\n    future::block_on(async move {\n        let (_worker, _router, transport1, _) = init().await;\n\n        {\n            let data_producer1 = transport1\n                .produce_data({\n                    let mut options =\n                        DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                    options.label = \"foo\".to_string();\n                    options.protocol = \"bar\".to_string();\n                    options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                    options\n                })\n                .await\n                .expect(\"Failed to produce data\");\n\n            {\n                data_producer1\n                    .pause()\n                    .await\n                    .expect(\"Failed to pause data producer\");\n\n                assert!(data_producer1.paused());\n\n                let dump = data_producer1\n                    .dump()\n                    .await\n                    .expect(\"Failed to dump data producer\");\n\n                assert!(dump.paused);\n            }\n\n            {\n                data_producer1\n                    .resume()\n                    .await\n                    .expect(\"Failed to resume data producer\");\n\n                assert!(!data_producer1.paused());\n\n                let dump = data_producer1\n                    .dump()\n                    .await\n                    .expect(\"Failed to dump data producer\");\n\n                assert!(!dump.paused);\n            }\n        }\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, router, transport1, _transport2) = init().await;\n\n        let data_producer = transport1\n            .produce_data({\n                let mut options =\n                    DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666));\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.app_data = AppData::new(CustomAppData { foo: 1, baz: \"2\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        {\n            let (mut tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = data_producer.on_close(move || {\n                let _ = tx.send(());\n            });\n            drop(data_producer);\n\n            rx.await.expect(\"Failed to receive close event\");\n        }\n\n        // Drop is async, give consumer a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(\n                dump.map_data_producer_id_data_consumer_ids,\n                HashedMap::default()\n            );\n            assert_eq!(\n                dump.map_data_consumer_id_data_producer_id,\n                HashedMap::default()\n            );\n        }\n\n        {\n            let dump = transport1.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.data_producer_ids, vec![]);\n            assert_eq!(dump.data_consumer_ids, vec![]);\n        }\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/direct_transport.rs",
    "content": "use futures_lite::future;\nuse hash_hasher::HashedSet;\nuse mediasoup::data_consumer::DataConsumerOptions;\nuse mediasoup::data_producer::{DataProducer, DataProducerOptions};\nuse mediasoup::direct_transport::{DirectTransport, DirectTransportOptions};\nuse mediasoup::prelude::*;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, WebRtcMessage};\nuse parking_lot::Mutex;\nuse std::borrow::Cow;\nuse std::env;\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\n\nstruct CustomAppData {\n    foo: &'static str,\n}\n\nasync fn init() -> (Worker, Router, DirectTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::default())\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport = router\n        .create_direct_transport(DirectTransportOptions::default())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    (worker, router, transport)\n}\n\n#[test]\nfn create_succeeds() {\n    future::block_on(async move {\n        let (_worker, router, transport) = init().await;\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.transport_ids, {\n                let mut set = HashedSet::default();\n                set.insert(transport.id());\n                set\n            });\n        }\n\n        let new_transports_count = Arc::new(AtomicUsize::new(0));\n\n        router\n            .on_new_transport({\n                let new_transports_count = Arc::clone(&new_transports_count);\n\n                move |_transport| {\n                    new_transports_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        let transport1 = router\n            .create_direct_transport({\n                let mut direct_transport_options = DirectTransportOptions::default();\n                direct_transport_options.max_message_size = 1024;\n                direct_transport_options.app_data = AppData::new(CustomAppData { foo: \"bar\" });\n\n                direct_transport_options\n            })\n            .await\n            .expect(\"Failed to create Direct transport\");\n\n        assert_eq!(new_transports_count.load(Ordering::SeqCst), 1);\n        assert!(!transport1.closed());\n        assert_eq!(\n            transport1\n                .app_data()\n                .downcast_ref::<CustomAppData>()\n                .unwrap()\n                .foo,\n            \"bar\",\n        );\n\n        {\n            let dump = transport1.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.id, transport1.id());\n            assert!(dump.direct);\n            assert_eq!(dump.producer_ids, vec![]);\n            assert_eq!(dump.consumer_ids, vec![]);\n            assert_eq!(dump.data_producer_ids, vec![]);\n            assert_eq!(dump.data_consumer_ids, vec![]);\n        }\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, _router, transport) = init().await;\n\n        let weak_transport = transport.downgrade();\n\n        assert!(weak_transport.upgrade().is_some());\n\n        drop(transport);\n\n        assert!(weak_transport.upgrade().is_none());\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport) = init().await;\n\n        let stats = transport\n            .get_stats()\n            .await\n            .expect(\"Failed to get stats on Direct transport\");\n\n        assert_eq!(stats.len(), 1);\n\n        assert_eq!(stats[0].transport_id, transport.id());\n        assert_eq!(stats[0].bytes_received, 0);\n        assert_eq!(stats[0].recv_bitrate, 0);\n        assert_eq!(stats[0].bytes_sent, 0);\n        assert_eq!(stats[0].send_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_received, 0);\n        assert_eq!(stats[0].rtp_recv_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_sent, 0);\n        assert_eq!(stats[0].rtp_send_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_received, 0);\n        assert_eq!(stats[0].rtx_recv_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_sent, 0);\n        assert_eq!(stats[0].rtx_send_bitrate, 0);\n        assert_eq!(stats[0].probation_bytes_sent, 0);\n        assert_eq!(stats[0].probation_send_bitrate, 0);\n        assert_eq!(stats[0].rtp_packet_loss_received, None);\n        assert_eq!(stats[0].rtp_packet_loss_sent, None);\n    });\n}\n\n#[test]\nfn send_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport) = init().await;\n\n        let data_producer = transport\n            .produce_data({\n                let mut options = DataProducerOptions::new_direct();\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.app_data = AppData::new(CustomAppData { foo: \"bar\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        let data_consumer = transport\n            .consume_data(DataConsumerOptions::new_direct(data_producer.id(), None))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let num_messages = 200_usize;\n        let pause_sending_at_message = 10_usize;\n        let resume_sending_at_message = 20_usize;\n        let pause_receiving_at_message = 40_usize;\n        let resume_receiving_at_message = 60_usize;\n        let expected_received_num_messages = num_messages\n            - (resume_sending_at_message - pause_sending_at_message)\n            - (resume_receiving_at_message - pause_receiving_at_message);\n\n        let mut sent_message_bytes = 0_usize;\n        let mut effectively_sent_message_bytes = 0_usize;\n        let recv_message_bytes = Arc::new(AtomicUsize::new(0));\n        let mut last_sent_message_id = 0_usize;\n        let last_recv_message_id = Arc::new(AtomicUsize::new(0));\n\n        let (received_messages_tx, received_messages_rx) = async_oneshot::oneshot::<()>();\n        let _handler = data_consumer.on_message({\n            let received_messages_tx = Mutex::new(Some(received_messages_tx));\n            let recv_message_bytes = Arc::clone(&recv_message_bytes);\n            let last_recv_message_id = Arc::clone(&last_recv_message_id);\n\n            move |message| {\n                let id: usize = match message {\n                    WebRtcMessage::String(binary) => {\n                        recv_message_bytes.fetch_add(binary.len(), Ordering::SeqCst);\n                        String::from_utf8(binary.to_vec()).unwrap().parse().unwrap()\n                    }\n                    WebRtcMessage::Binary(binary) => {\n                        recv_message_bytes.fetch_add(binary.len(), Ordering::SeqCst);\n                        String::from_utf8(binary.to_vec()).unwrap().parse().unwrap()\n                    }\n                    WebRtcMessage::EmptyString => {\n                        panic!(\"Unexpected empty message!\");\n                    }\n                    WebRtcMessage::EmptyBinary => {\n                        panic!(\"Unexpected empty message!\");\n                    }\n                };\n\n                if id < num_messages / 2 {\n                    assert!(matches!(message, &WebRtcMessage::String(_)));\n                } else {\n                    assert!(matches!(message, &WebRtcMessage::Binary(_)));\n                }\n\n                last_recv_message_id.fetch_add(1, Ordering::SeqCst);\n\n                if id == num_messages {\n                    let _ = received_messages_tx.lock().take().unwrap().send(());\n                }\n            }\n        });\n\n        let direct_data_producer = match &data_producer {\n            DataProducer::Direct(direct_data_producer) => direct_data_producer,\n            _ => {\n                panic!(\"Expected direct data producer\")\n            }\n        };\n\n        loop {\n            last_sent_message_id += 1;\n            let id = last_sent_message_id;\n\n            if id == pause_sending_at_message {\n                data_producer\n                    .pause()\n                    .await\n                    .expect(\"Failed to pause data producer\");\n            } else if id == resume_sending_at_message {\n                data_producer\n                    .resume()\n                    .await\n                    .expect(\"Failed to resume data producer\");\n            } else if id == pause_receiving_at_message {\n                data_consumer\n                    .pause()\n                    .await\n                    .expect(\"Failed to pause data consumer\");\n            } else if id == resume_receiving_at_message {\n                data_consumer\n                    .resume()\n                    .await\n                    .expect(\"Failed to resume data consumer\");\n            }\n\n            let message = if id < num_messages / 2 {\n                let content = id.to_string().into_bytes();\n                sent_message_bytes += content.len();\n\n                if !data_producer.paused() && !data_consumer.paused() {\n                    effectively_sent_message_bytes += content.len();\n                }\n\n                WebRtcMessage::String(Cow::from(content))\n            } else {\n                let content = id.to_string().into_bytes();\n                sent_message_bytes += content.len();\n\n                if !data_producer.paused() && !data_consumer.paused() {\n                    effectively_sent_message_bytes += content.len();\n                }\n\n                WebRtcMessage::Binary(Cow::from(content))\n            };\n\n            direct_data_producer\n                .send(message, None, None)\n                .expect(\"Failed to send message\");\n\n            if id == num_messages {\n                break;\n            }\n        }\n\n        received_messages_rx\n            .await\n            .expect(\"Failed tor receive all messages\");\n\n        assert_eq!(last_sent_message_id, num_messages);\n        assert_eq!(\n            last_recv_message_id.load(Ordering::SeqCst),\n            expected_received_num_messages\n        );\n        assert_eq!(\n            recv_message_bytes.load(Ordering::SeqCst),\n            effectively_sent_message_bytes,\n        );\n\n        {\n            let stats = data_producer\n                .get_stats()\n                .await\n                .expect(\"Failed to get stats on data producer\");\n\n            assert_eq!(stats.len(), 1);\n            assert_eq!(&stats[0].label, data_producer.label());\n            assert_eq!(&stats[0].protocol, data_producer.protocol());\n            assert_eq!(stats[0].messages_received, num_messages as u64);\n            assert_eq!(stats[0].bytes_received, sent_message_bytes as u64);\n        }\n\n        {\n            let stats = data_consumer\n                .get_stats()\n                .await\n                .expect(\"Failed to get stats on data consumer\");\n\n            assert_eq!(stats.len(), 1);\n            assert_eq!(&stats[0].label, data_consumer.label());\n            assert_eq!(&stats[0].protocol, data_consumer.protocol());\n            assert_eq!(\n                stats[0].messages_sent,\n                expected_received_num_messages as u64\n            );\n            assert_eq!(\n                stats[0].bytes_sent,\n                recv_message_bytes.load(Ordering::SeqCst) as u64,\n            );\n        }\n    });\n}\n\n#[test]\nfn send_with_subchannels_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport) = init().await;\n\n        let data_producer = transport\n            .produce_data({\n                let mut options = DataProducerOptions::new_direct();\n\n                options.label = \"foo\".to_string();\n                options.protocol = \"bar\".to_string();\n                options.app_data = AppData::new(CustomAppData { foo: \"bar\" });\n\n                options\n            })\n            .await\n            .expect(\"Failed to produce data\");\n\n        let data_consumer_1 = transport\n            .consume_data(DataConsumerOptions::new_direct(\n                data_producer.id(),\n                Some(vec![1, 11, 666]),\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let data_consumer_2 = transport\n            .consume_data(DataConsumerOptions::new_direct(\n                data_producer.id(),\n                Some(vec![2, 22, 666]),\n            ))\n            .await\n            .expect(\"Failed to consume data\");\n\n        let expected_received_num_messages_1 = 7;\n        let expected_received_num_messages_2 = 5;\n        let received_messages_1: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));\n        let received_messages_2: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));\n\n        let (received_messages_tx_1, received_messages_rx_1) = async_oneshot::oneshot::<()>();\n        let _handler_1 = data_consumer_1.on_message({\n            let received_messages_tx_1 = Mutex::new(Some(received_messages_tx_1));\n            let received_messages_1 = Arc::clone(&received_messages_1);\n\n            move |message| {\n                let text: String = match message {\n                    WebRtcMessage::String(binary) => String::from_utf8(binary.to_vec()).unwrap(),\n                    _ => {\n                        panic!(\"Unexpected empty message!\");\n                    }\n                };\n\n                received_messages_1.lock().push(text);\n\n                if received_messages_1.lock().len() == expected_received_num_messages_1 {\n                    let _ = received_messages_tx_1.lock().take().unwrap().send(());\n                }\n            }\n        });\n\n        let (received_messages_tx_2, received_messages_rx_2) = async_oneshot::oneshot::<()>();\n        let _handler_2 = data_consumer_2.on_message({\n            let received_messages_tx_2 = Mutex::new(Some(received_messages_tx_2));\n            let received_messages_2 = Arc::clone(&received_messages_2);\n\n            move |message| {\n                let text: String = match message {\n                    WebRtcMessage::String(binary) => String::from_utf8(binary.to_vec()).unwrap(),\n                    _ => {\n                        panic!(\"Unexpected empty message!\");\n                    }\n                };\n\n                received_messages_2.lock().push(text);\n\n                if received_messages_2.lock().len() == expected_received_num_messages_2 {\n                    let _ = received_messages_tx_2.lock().take().unwrap().send(());\n                }\n            }\n        });\n\n        let direct_data_producer = match &data_producer {\n            DataProducer::Direct(direct_data_producer) => direct_data_producer,\n            _ => {\n                panic!(\"Expected direct data producer\")\n            }\n        };\n\n        let _ = match &data_consumer_2 {\n            DataConsumer::Direct(direct_data_consumer) => direct_data_consumer,\n            _ => {\n                panic!(\"Expected direct data consumer\")\n            }\n        };\n\n        let both: &'static str = \"both\";\n        let none: &'static str = \"none\";\n        let dc1: &'static str = \"dc1\";\n        let dc2: &'static str = \"dc2\";\n\n        // Must be received by dataConsumer1 and dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(both.as_bytes())),\n                None,\n                None,\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer1 and dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(both.as_bytes())),\n                Some(vec![1, 2]),\n                None,\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer1 and dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(both.as_bytes())),\n                Some(vec![11, 22, 33]),\n                Some(666),\n            )\n            .expect(\"Failed to send message\");\n\n        // Must not be received by neither dataConsumer1 nor dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(none.as_bytes())),\n                Some(vec![3]),\n                Some(666),\n            )\n            .expect(\"Failed to send message\");\n\n        // Must not be received by neither dataConsumer1 nor dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(none.as_bytes())),\n                Some(vec![666]),\n                Some(3),\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer1.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())),\n                Some(vec![1]),\n                None,\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer1.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())),\n                Some(vec![11]),\n                None,\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer1.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())),\n                Some(vec![666]),\n                Some(11),\n            )\n            .expect(\"Failed to send message\");\n\n        // Must be received by dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(dc2.as_bytes())),\n                Some(vec![666]),\n                Some(2),\n            )\n            .expect(\"Failed to send message\");\n\n        let mut subchannels = data_consumer_2.subchannels();\n        subchannels.push(1);\n\n        data_consumer_2\n            .set_subchannels(subchannels)\n            .await\n            .expect(\"Failed to set subchannels\");\n\n        // Must be received by dataConsumer2.\n        direct_data_producer\n            .send(\n                WebRtcMessage::String(Cow::Borrowed(both.as_bytes())),\n                Some(vec![1]),\n                Some(666),\n            )\n            .expect(\"Failed to send message\");\n\n        received_messages_rx_1\n            .await\n            .expect(\"Failed tor receive all messages\");\n\n        received_messages_rx_2\n            .await\n            .expect(\"Failed tor receive all messages\");\n\n        let received_messages_1 = received_messages_1.lock().clone();\n        assert!(received_messages_1.contains(&both.to_string()));\n        assert!(received_messages_1.contains(&dc1.to_string()));\n        assert!(!received_messages_1.contains(&dc2.to_string()));\n\n        let received_messages_2 = received_messages_2.lock().clone();\n        assert!(received_messages_2.contains(&both.to_string()));\n        assert!(!received_messages_2.contains(&dc1.to_string()));\n        assert!(received_messages_2.contains(&dc2.to_string()));\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, _router, transport) = init().await;\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        drop(transport);\n\n        close_rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/main.rs",
    "content": "mod active_speaker_observer;\nmod audio_level_observer;\nmod consumer;\nmod data_consumer;\nmod data_producer;\nmod direct_transport;\nmod multiopus;\nmod pipe_transport;\nmod plain_transport;\nmod producer;\nmod router;\nmod smoke;\nmod webrtc_server;\nmod webrtc_transport;\nmod worker;\n"
  },
  {
    "path": "rust/tests/integration/multiopus.rs",
    "content": "use futures_lite::future;\nuse mediasoup::prelude::*;\nuse mediasoup::producer::ProducerOptions;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::WorkerSettings;\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters,\n    RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters,\n    RtpHeaderExtensionUri, RtpParameters,\n};\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![RtpCodecCapability::Audio {\n        mime_type: MimeTypeAudio::MultiChannelOpus,\n        preferred_payload_type: None,\n        clock_rate: NonZeroU32::new(48000).unwrap(),\n        channels: NonZeroU8::new(6).unwrap(),\n        parameters: RtpCodecParametersParameters::from([\n            (\"useinbandfec\", 1_u32.into()),\n            (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n            (\"num_streams\", 4_u32.into()),\n            (\"coupled_streams\", 2_u32.into()),\n        ]),\n        rtcp_feedback: vec![],\n    }]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                payload_type: 0,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(6).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                    (\"num_streams\", 4_u32.into()),\n                    (\"coupled_streams\", 2_u32.into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n            ..RtpParameters::default()\n        },\n    )\n}\n\nfn consumer_device_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::MultiChannelOpus,\n            preferred_payload_type: Some(100),\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(6).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                (\"num_streams\", 4_u32.into()),\n                (\"coupled_streams\", 2_u32.into()),\n            ]),\n            rtcp_feedback: vec![],\n        }],\n        header_extensions: vec![\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::Mid,\n                preferred_id: 1,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                preferred_id: 10,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n        ],\n    }\n}\n\nasync fn init() -> (Router, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport\");\n\n    (router, transport)\n}\n\n#[test]\nfn produce_and_consume_succeeds() {\n    future::block_on(async move {\n        let (router, transport) = init().await;\n\n        let audio_producer = transport\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        assert_eq!(\n            audio_producer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                payload_type: 0,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(6).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                    (\"num_streams\", 4_u32.into()),\n                    (\"coupled_streams\", 2_u32.into()),\n                ]),\n                rtcp_feedback: vec![],\n            }]\n        );\n\n        let consumer_device_capabilities = consumer_device_capabilities();\n\n        assert!(router.can_consume(&audio_producer.id(), &consumer_device_capabilities));\n\n        let audio_consumer = transport\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities.clone(),\n            ))\n            .await\n            .expect(\"Failed to consume audio\");\n\n        assert_eq!(\n            audio_consumer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                payload_type: 100,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(6).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                    (\"num_streams\", 4_u32.into()),\n                    (\"coupled_streams\", 2_u32.into()),\n                ]),\n                rtcp_feedback: vec![],\n            }]\n        );\n    });\n}\n\n#[test]\nfn fails_to_consume_wrong_parameters() {\n    future::block_on(async move {\n        let (_router, transport) = init().await;\n\n        assert!(transport\n            .produce(ProducerOptions::new(\n                MediaKind::Audio,\n                RtpParameters {\n                    mid: Some(\"AUDIO\".to_string()),\n                    codecs: vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::MultiChannelOpus,\n                        payload_type: 0,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(6).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                            (\"num_streams\", 2_u32.into()),\n                            (\"coupled_streams\", 2_u32.into()),\n                        ]),\n                        rtcp_feedback: vec![],\n                    }],\n                    ..RtpParameters::default()\n                },\n            ))\n            .await\n            .is_err());\n\n        assert!(transport\n            .produce(ProducerOptions::new(\n                MediaKind::Audio,\n                RtpParameters {\n                    mid: Some(\"AUDIO\".to_string()),\n                    codecs: vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::MultiChannelOpus,\n                        payload_type: 0,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(6).unwrap(),\n                        parameters: RtpCodecParametersParameters::from([\n                            (\"channel_mapping\", \"0,4,1,2,3,5\".into()),\n                            (\"num_streams\", 4_u32.into()),\n                            (\"coupled_streams\", 1_u32.into()),\n                        ]),\n                        rtcp_feedback: vec![],\n                    }],\n                    ..RtpParameters::default()\n                },\n            ))\n            .await\n            .is_err());\n    });\n}\n\n#[test]\nfn fails_to_consume_wrong_channels() {\n    future::block_on(async move {\n        let (router, transport) = init().await;\n\n        let audio_producer = transport\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let consumer_device_capabilities = RtpCapabilities {\n            codecs: vec![RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::MultiChannelOpus,\n                preferred_payload_type: Some(100),\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(8).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: Vec::new(),\n        };\n\n        assert!(!router.can_consume(&audio_producer.id(), &consumer_device_capabilities));\n\n        let audio_consumer_result = transport\n            .consume(ConsumerOptions::new(\n                audio_producer.id(),\n                consumer_device_capabilities.clone(),\n            ))\n            .await;\n\n        assert!(audio_consumer_result.is_err());\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/pipe_transport.rs",
    "content": "use futures_lite::future;\nuse mediasoup::consumer::{ConsumerOptions, ConsumerScore, ConsumerType};\nuse mediasoup::data_consumer::{DataConsumerOptions, DataConsumerType};\nuse mediasoup::data_producer::{DataProducerOptions, DataProducerType};\nuse mediasoup::pipe_transport::{PipeTransportOptions, PipeTransportRemoteParameters};\nuse mediasoup::prelude::*;\nuse mediasoup::producer::ProducerOptions;\nuse mediasoup::router::{\n    PipeDataProducerToRouterPair, PipeProducerToRouterPair, PipeToRouterOptions, Router,\n    RouterOptions,\n};\nuse mediasoup::transport::ProduceError;\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::{RequestError, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities,\n    RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters,\n    RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters,\n    RtpHeaderExtensionUri, RtpParameters,\n};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters};\nuse parking_lot::Mutex;\nuse portpicker::pick_unused_port;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\n\nstruct CustomAppData {\n    _foo: &'static str,\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n    ]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 111,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"foo\", \"bar1\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: vec![RtpHeaderExtensionParameters {\n                uri: RtpHeaderExtensionUri::Mid,\n                id: 10,\n                encrypt: false,\n            }],\n            encodings: vec![RtpEncodingParameters {\n                ssrc: Some(11111111),\n                ..RtpEncodingParameters::default()\n            }],\n            rtcp: RtcpParameters {\n                cname: Some(\"FOOBAR\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    );\n\n    options.app_data = AppData::new(CustomAppData { _foo: \"bar1\" });\n\n    options\n}\n\nfn video_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Video,\n        RtpParameters {\n            mid: Some(\"VIDEO\".to_string()),\n            codecs: vec![RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                payload_type: 112,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::NackPli,\n                    RtcpFeedback::GoogRemb,\n                ],\n            }],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsSendTime,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 13,\n                    encrypt: false,\n                },\n            ],\n            encodings: vec![\n                RtpEncodingParameters {\n                    ssrc: Some(22222222),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222223),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222224),\n                    ..RtpEncodingParameters::default()\n                },\n            ],\n            rtcp: RtcpParameters {\n                cname: Some(\"FOOBAR\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: Some(\"aaaa-bbbb\".to_string()),\n        },\n    );\n\n    options.app_data = AppData::new(CustomAppData { _foo: \"bar2\" });\n\n    options\n}\n\nfn data_producer_options() -> DataProducerOptions {\n    let mut options = DataProducerOptions::new_sctp(\n        SctpStreamParameters::new_unordered_with_life_time(666, 5000),\n    );\n\n    options.label = \"foo\".to_string();\n    options.protocol = \"bar\".to_string();\n\n    options\n}\n\nfn consumer_device_capabilities() -> RtpCapabilities {\n    RtpCapabilities {\n        codecs: vec![\n            RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: Some(100),\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                preferred_payload_type: Some(101),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![\n                    RtcpFeedback::Nack,\n                    RtcpFeedback::CcmFir,\n                    RtcpFeedback::TransportCc,\n                ],\n            },\n            RtpCodecCapability::Video {\n                mime_type: MimeTypeVideo::Rtx,\n                preferred_payload_type: Some(102),\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                rtcp_feedback: vec![],\n            },\n        ],\n        header_extensions: vec![\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::AbsSendTime,\n                preferred_id: 4,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::SendRecv,\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Video,\n                uri: RtpHeaderExtensionUri::TransportWideCcDraft01,\n                preferred_id: 5,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n            RtpHeaderExtension {\n                kind: MediaKind::Audio,\n                uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                preferred_id: 6,\n                preferred_encrypt: false,\n                direction: RtpHeaderExtensionDirection::default(),\n            },\n        ],\n    }\n}\n\nasync fn init() -> (\n    Worker,\n    Worker,\n    Router,\n    Router,\n    WebRtcTransport,\n    WebRtcTransport,\n) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker1 = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let worker2 = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router1 = worker1\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let router2 = worker2\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let mut transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n    transport_options.enable_sctp = true;\n\n    let transport_1 = router1\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router2\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (worker1, worker2, router1, router2, transport_1, transport_2)\n}\n\n#[test]\nfn pipe_to_router_succeeds_with_audio() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await;\n\n        let audio_producer = transport1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let PipeProducerToRouterPair {\n            pipe_producer,\n            pipe_consumer,\n        } = router1\n            .pipe_producer_to_router(\n                audio_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe audio producer to router\");\n\n        let pipe_producer = pipe_producer.into_inner();\n\n        {\n            let dump = router1.dump().await.expect(\"Failed to dump router\");\n\n            // There should be two Transports in router1:\n            // - WebRtcTransport for audio_producer.\n            // - PipeTransport between router1 and router2.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        {\n            let dump = router2.dump().await.expect(\"Failed to dump router\");\n\n            // There should be two Transports in router2:\n            // - WebRtcTransport for audio_consumer.\n            // - pipeTransport between router2 and router1.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        assert_eq!(pipe_consumer.kind(), MediaKind::Audio);\n        assert_eq!(pipe_consumer.rtp_parameters().mid, None);\n        assert_eq!(\n            pipe_consumer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 100,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"foo\", \"bar1\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n        );\n        assert_eq!(\n            pipe_consumer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 6,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                    id: 7,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::PlayoutDelay,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe);\n        assert!(!pipe_consumer.paused());\n        assert!(!pipe_consumer.producer_paused());\n        assert_eq!(\n            pipe_consumer.score(),\n            ConsumerScore {\n                score: 10,\n                producer_score: 10,\n                producer_scores: vec![0],\n            },\n        );\n        assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &());\n\n        assert_eq!(pipe_producer.id(), audio_producer.id());\n        assert_eq!(pipe_producer.kind(), MediaKind::Audio);\n        assert_eq!(pipe_producer.rtp_parameters().mid, None);\n        assert_eq!(\n            pipe_producer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 100,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"foo\", \"bar1\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n        );\n        assert_eq!(\n            pipe_producer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 6,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                    id: 7,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::PlayoutDelay,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert!(!pipe_producer.paused());\n    });\n}\n\n#[test]\nfn pipe_to_router_succeeds_with_video() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await;\n\n        let audio_producer = transport1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        router1\n            .pipe_producer_to_router(\n                audio_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe audio producer to router\");\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        // Pause the videoProducer.\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let PipeProducerToRouterPair {\n            pipe_producer,\n            pipe_consumer,\n        } = router1\n            .pipe_producer_to_router(\n                video_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe video producer to router\");\n\n        let pipe_producer = pipe_producer.into_inner();\n\n        {\n            let dump = router1.dump().await.expect(\"Failed to dump router\");\n\n            // No new PipeTransport should has been created. The existing one is used.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        {\n            let dump = router2.dump().await.expect(\"Failed to dump router\");\n\n            // No new PipeTransport should has been created. The existing one is used.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        assert_eq!(pipe_consumer.kind(), MediaKind::Video);\n        assert_eq!(pipe_consumer.rtp_parameters().mid, None);\n        assert_eq!(\n            pipe_consumer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                payload_type: 101,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir],\n            }],\n        );\n        assert_eq!(\n            pipe_consumer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                    id: 7,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 8,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::TimeOffset,\n                    id: 9,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::PlayoutDelay,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe);\n        assert!(!pipe_consumer.paused());\n        assert!(pipe_consumer.producer_paused());\n        assert_eq!(\n            pipe_consumer.score(),\n            ConsumerScore {\n                score: 10,\n                producer_score: 10,\n                producer_scores: vec![0, 0, 0],\n            },\n        );\n        assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &());\n\n        assert_eq!(pipe_producer.id(), video_producer.id());\n        assert_eq!(pipe_producer.kind(), MediaKind::Video);\n        assert_eq!(pipe_producer.rtp_parameters().mid, None);\n        assert_eq!(\n            pipe_consumer.rtp_parameters().codecs,\n            vec![RtpCodecParameters::Video {\n                mime_type: MimeTypeVideo::Vp8,\n                payload_type: 101,\n                clock_rate: NonZeroU32::new(90000).unwrap(),\n                parameters: RtpCodecParametersParameters::default(),\n                rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir],\n            }],\n        );\n        assert_eq!(\n            pipe_consumer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                    id: 7,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 8,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::TimeOffset,\n                    id: 9,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::PlayoutDelay,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert!(pipe_producer.paused());\n    });\n}\n\n#[test]\nfn pipe_to_router_with_keep_id_true_fails_if_both_routers_belong_to_the_same_worker() {\n    future::block_on(async move {\n        let (worker1, _worker2, router1, _router2, transport1, _transport2) = init().await;\n\n        let router1bis = worker1\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        let result = router1\n            .pipe_producer_to_router(\n                video_producer.id(),\n                PipeToRouterOptions::new(router1bis.clone()),\n            )\n            .await;\n\n        if let Err(PipeProducerToRouterError::ProduceFailed(ProduceError::Request(\n            RequestError::Response { reason },\n        ))) = result\n        {\n            assert!(reason.contains(\"already exists [method:transport.produce]\"));\n        } else {\n            panic!(\"Unexpected result: {result:?}\");\n        }\n    });\n}\n\n#[test]\nfn pipe_to_router_with_keep_id_false_does_not_fail_if_both_routers_belong_to_the_same_worker() {\n    future::block_on(async move {\n        let (worker1, _worker2, router1, _router2, transport1, _transport2) = init().await;\n\n        let router1bis = worker1\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        let PipeProducerToRouterPair {\n            pipe_producer,\n            pipe_consumer: _,\n        } = router1\n            .pipe_producer_to_router(video_producer.id(), {\n                let mut options = PipeToRouterOptions::new(router1bis.clone());\n                options.keep_id = false;\n                options\n            })\n            .await\n            .expect(\"Failed to pipe producer to router\");\n\n        let pipe_producer = pipe_producer.into_inner();\n\n        assert_ne!(pipe_producer.id(), video_producer.id());\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await;\n\n        let pipe_transport = router1\n            .create_pipe_transport({\n                let mut options = PipeTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                options.enable_rtx = true;\n\n                options\n            })\n            .await\n            .expect(\"Failed to create Pipe transport\");\n\n        let weak_pipe_transport = pipe_transport.downgrade();\n\n        assert!(weak_pipe_transport.upgrade().is_some());\n\n        drop(pipe_transport);\n\n        assert!(weak_pipe_transport.upgrade().is_none());\n    });\n}\n\n#[test]\nfn create_with_fixed_port_succeeds() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await;\n\n        let port = pick_unused_port().unwrap();\n\n        let pipe_transport = router1\n            .create_pipe_transport({\n                PipeTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                })\n            })\n            .await\n            .expect(\"Failed to create Pipe transport\");\n\n        assert_eq!(pipe_transport.tuple().local_port(), port);\n    });\n}\n\n#[test]\nfn create_with_enable_rtx_succeeds() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, _router2, transport1, _transport2) = init().await;\n\n        let pipe_transport = router1\n            .create_pipe_transport({\n                let mut options = PipeTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                options.enable_rtx = true;\n\n                options\n            })\n            .await\n            .expect(\"Failed to create Pipe transport\");\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        // Pause the videoProducer.\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let pipe_consumer = pipe_transport\n            .consume(ConsumerOptions::new(\n                video_producer.id(),\n                RtpCapabilities::default(),\n            ))\n            .await\n            .expect(\"Failed to create pipe consumer\");\n\n        assert_eq!(pipe_consumer.kind(), MediaKind::Video);\n        assert_eq!(pipe_consumer.rtp_parameters().mid, None);\n        assert_eq!(\n            pipe_consumer.rtp_parameters().codecs,\n            vec![\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Vp8,\n                    payload_type: 101,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::default(),\n                    rtcp_feedback: vec![\n                        RtcpFeedback::Nack,\n                        RtcpFeedback::NackPli,\n                        RtcpFeedback::CcmFir\n                    ],\n                },\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Rtx,\n                    payload_type: 102,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                    rtcp_feedback: vec![],\n                },\n            ],\n        );\n        assert_eq!(\n            pipe_consumer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::DependencyDescriptor,\n                    id: 7,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 8,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::TimeOffset,\n                    id: 9,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsCaptureTime,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::PlayoutDelay,\n                    id: 11,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::MediasoupPacketId,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe);\n        assert!(!pipe_consumer.paused());\n        assert!(pipe_consumer.producer_paused());\n        assert_eq!(\n            pipe_consumer.score(),\n            ConsumerScore {\n                score: 10,\n                producer_score: 10,\n                producer_scores: vec![0, 0, 0],\n            },\n        );\n        assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &());\n    });\n}\n\n#[test]\nfn create_with_enable_srtp_succeeds() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await;\n\n        let pipe_transport = router1\n            .create_pipe_transport({\n                let mut options = PipeTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                options.enable_srtp = true;\n\n                options\n            })\n            .await\n            .expect(\"Failed to create Pipe transport\");\n\n        assert!(pipe_transport.srtp_parameters().is_some());\n        assert_eq!(\n            pipe_transport.srtp_parameters().unwrap().key_base64.len(),\n            60\n        );\n\n        // Missing srtp_parameters.\n        assert!(matches!(\n            pipe_transport\n                .connect(PipeTransportRemoteParameters {\n                    ip: \"127.0.0.2\".parse().unwrap(),\n                    port: 9999,\n                    srtp_parameters: None,\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        // Valid srtp_parameters.\n        pipe_transport\n            .connect(PipeTransportRemoteParameters {\n                ip: \"127.0.0.2\".parse().unwrap(),\n                port: 9999,\n                srtp_parameters: Some(SrtpParameters {\n                    crypto_suite: SrtpCryptoSuite::AeadAes256Gcm,\n                    key_base64: \"YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=\"\n                        .to_string(),\n                }),\n            })\n            .await\n            .expect(\"Failed to establish Pipe transport connection\");\n    });\n}\n\n#[test]\nfn create_with_invalid_srtp_parameters_fails() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await;\n\n        let pipe_transport = router1\n            .create_pipe_transport(PipeTransportOptions::new(ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            }))\n            .await\n            .expect(\"Failed to create Pipe transport\");\n\n        // No SRTP enabled so passing srtp_parameters must fail.\n        assert!(matches!(\n            pipe_transport\n                .connect(PipeTransportRemoteParameters {\n                    ip: \"127.0.0.2\".parse().unwrap(),\n                    port: 9999,\n                    srtp_parameters: Some(SrtpParameters {\n                        crypto_suite: SrtpCryptoSuite::AeadAes256Gcm,\n                        key_base64: \"YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=\"\n                            .to_string(),\n                    }),\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n    });\n}\n\n#[test]\nfn consume_for_pipe_producer_succeeds() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await;\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        // Pause the videoProducer.\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let PipeProducerToRouterPair { pipe_producer, .. } = router1\n            .pipe_producer_to_router(\n                video_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe video producer to router\");\n\n        let pipe_video_producer = pipe_producer.into_inner();\n\n        let video_consumer = transport2\n            .consume(ConsumerOptions::new(\n                pipe_video_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume video\");\n\n        assert_eq!(video_consumer.kind(), MediaKind::Video);\n        assert_eq!(video_consumer.rtp_parameters().mid, Some(\"0\".to_string()));\n        assert_eq!(\n            video_consumer.rtp_parameters().codecs,\n            vec![\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Vp8,\n                    payload_type: 101,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::default(),\n                    rtcp_feedback: vec![\n                        RtcpFeedback::Nack,\n                        RtcpFeedback::CcmFir,\n                        RtcpFeedback::TransportCc,\n                    ],\n                },\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Rtx,\n                    payload_type: 102,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([(\"apt\", 101_u32.into())]),\n                    rtcp_feedback: vec![],\n                },\n            ],\n        );\n        assert_eq!(\n            video_consumer.rtp_parameters().header_extensions,\n            vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::AbsSendTime,\n                    id: 4,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::TransportWideCcDraft01,\n                    id: 5,\n                    encrypt: false,\n                },\n            ],\n        );\n        assert_eq!(video_consumer.rtp_parameters().encodings.len(), 1);\n        assert!(video_consumer.rtp_parameters().encodings[0].ssrc.is_some());\n        assert!(video_consumer.rtp_parameters().encodings[0].rtx.is_some());\n        assert_eq!(\n            video_consumer.rtp_parameters().msid,\n            Some(\"aaaa-bbbb\".to_string())\n        );\n        assert_eq!(video_consumer.r#type(), ConsumerType::Simulcast);\n        assert!(!video_consumer.paused());\n        assert!(video_consumer.producer_paused());\n        assert_eq!(\n            video_consumer.score(),\n            ConsumerScore {\n                score: 10,\n                producer_score: 0,\n                producer_scores: vec![0, 0, 0],\n            },\n        );\n        assert_eq!(video_consumer.app_data().downcast_ref::<()>().unwrap(), &());\n    });\n}\n\n#[test]\nfn producer_pause_resume_are_transmitted_to_pipe_consumer() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await;\n\n        let video_producer = transport1\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        // Pause the videoProducer.\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        let PipeProducerToRouterPair {\n            pipe_producer,\n            pipe_consumer: _,\n        } = router1\n            .pipe_producer_to_router(\n                video_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe video producer to router\");\n\n        let pipe_video_producer = pipe_producer.into_inner();\n\n        let video_consumer = transport2\n            .consume(ConsumerOptions::new(\n                pipe_video_producer.id(),\n                consumer_device_capabilities(),\n            ))\n            .await\n            .expect(\"Failed to consume video\");\n\n        assert!(video_producer.paused());\n        assert!(video_consumer.producer_paused());\n        assert!(!video_consumer.paused());\n\n        let (producer_resume_tx, producer_resume_rx) = async_oneshot::oneshot::<()>();\n        let _handler = video_consumer.on_producer_resume({\n            let producer_resume_tx = Mutex::new(Some(producer_resume_tx));\n\n            move || {\n                let _ = producer_resume_tx.lock().take().unwrap().send(());\n            }\n        });\n\n        video_producer\n            .resume()\n            .await\n            .expect(\"Failed to resume video producer\");\n\n        producer_resume_rx\n            .await\n            .expect(\"Failed to receive producer resume event\");\n\n        assert!(!video_consumer.producer_paused());\n        assert!(!video_consumer.paused());\n\n        let (producer_pause_tx, producer_pause_rx) = async_oneshot::oneshot::<()>();\n        let _handler = video_consumer.on_producer_pause({\n            let producer_pause_tx = Mutex::new(Some(producer_pause_tx));\n\n            move || {\n                let _ = producer_pause_tx.lock().take().unwrap().send(());\n            }\n        });\n\n        video_producer\n            .pause()\n            .await\n            .expect(\"Failed to pause video producer\");\n\n        producer_pause_rx\n            .await\n            .expect(\"Failed to receive producer pause event\");\n\n        assert!(video_consumer.producer_paused());\n        assert!(!video_consumer.paused());\n    });\n}\n\n#[test]\nfn pipe_to_router_succeeds_with_data() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await;\n\n        let data_producer = transport1\n            .produce_data(data_producer_options())\n            .await\n            .expect(\"Failed to produce data\");\n\n        let PipeDataProducerToRouterPair {\n            pipe_data_producer,\n            pipe_data_consumer,\n        } = router1\n            .pipe_data_producer_to_router(\n                data_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe data producer to router\");\n\n        let pipe_data_producer = pipe_data_producer.into_inner();\n\n        {\n            let dump = router1.dump().await.expect(\"Failed to dump router\");\n\n            // There should be two Transports in router1:\n            // - WebRtcTransport for data_producer.\n            // - PipeTransport between router1 and router2.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        {\n            let dump = router2.dump().await.expect(\"Failed to dump router\");\n\n            // There should be two Transports in router2:\n            // - WebRtcTransport for data_consumer.\n            // - PipeTransport between router2 and router1.\n            assert_eq!(dump.transport_ids.len(), 2);\n        }\n\n        assert_eq!(pipe_data_consumer.r#type(), DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = pipe_data_consumer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(\n                sctp_stream_parameters.unwrap().max_packet_life_time(),\n                Some(5000),\n            );\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(pipe_data_consumer.label().as_str(), \"foo\");\n        assert_eq!(pipe_data_consumer.protocol().as_str(), \"bar\");\n\n        assert_eq!(pipe_data_producer.r#type(), DataProducerType::Sctp);\n        {\n            let sctp_stream_parameters = pipe_data_producer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(\n                sctp_stream_parameters.unwrap().max_packet_life_time(),\n                Some(5000),\n            );\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(pipe_data_producer.label().as_str(), \"foo\");\n        assert_eq!(pipe_data_producer.protocol().as_str(), \"bar\");\n    });\n}\n\n#[test]\nfn data_consume_for_pipe_data_producer_succeeds() {\n    future::block_on(async move {\n        let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await;\n\n        let data_producer = transport1\n            .produce_data(data_producer_options())\n            .await\n            .expect(\"Failed to produce data\");\n\n        let PipeDataProducerToRouterPair {\n            pipe_data_producer,\n            pipe_data_consumer: _,\n        } = router1\n            .pipe_data_producer_to_router(\n                data_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe data producer to router\");\n\n        let pipe_data_producer = pipe_data_producer.into_inner();\n\n        let data_consumer = transport2\n            .consume_data(DataConsumerOptions::new_sctp(pipe_data_producer.id()))\n            .await\n            .expect(\"Failed to create data consumer\");\n\n        assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp);\n        {\n            let sctp_stream_parameters = data_consumer.sctp_stream_parameters();\n            assert!(sctp_stream_parameters.is_some());\n            assert!(!sctp_stream_parameters.unwrap().ordered());\n            assert_eq!(\n                sctp_stream_parameters.unwrap().max_packet_life_time(),\n                Some(5000),\n            );\n            assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None);\n        }\n        assert_eq!(data_consumer.label().as_str(), \"foo\");\n        assert_eq!(data_consumer.protocol().as_str(), \"bar\");\n    });\n}\n\n#[test]\nfn pipe_to_router_called_twice_generates_single_pair() {\n    future::block_on(async move {\n        let (worker1, worker2, _router1, _router2, _transport1, _transport2) = init().await;\n\n        let router_a = worker1\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let router_b = worker2\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let mut transport_options =\n            WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                protocol: Protocol::Udp,\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                announced_address: None,\n                expose_internal_ip: false,\n                port: None,\n                port_range: None,\n                flags: None,\n                send_buffer_size: None,\n                recv_buffer_size: None,\n            }));\n        transport_options.enable_sctp = true;\n\n        let transport1 = router_a\n            .create_webrtc_transport(transport_options.clone())\n            .await\n            .expect(\"Failed to create transport1\");\n\n        let transport2 = router_a\n            .create_webrtc_transport(transport_options)\n            .await\n            .expect(\"Failed to create transport2\");\n\n        let audio_producer1 = transport1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let audio_producer2 = transport2\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        router_a\n            .pipe_producer_to_router(\n                audio_producer1.id(),\n                PipeToRouterOptions::new(router_b.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe audio producer to router\");\n\n        router_a\n            .pipe_producer_to_router(\n                audio_producer2.id(),\n                PipeToRouterOptions::new(router_b.clone()),\n            )\n            .await\n            .expect(\"Failed to pipe audio producer to router\");\n\n        {\n            let dump = router_a.dump().await.expect(\"Failed to dump router\");\n\n            // There should be 3 Transports in router_b:\n            // - WebRtcTransport for audio_producer1 and audio_producer2.\n            // - PipeTransport between router_a and router_b.\n            assert_eq!(dump.transport_ids.len(), 3);\n        }\n\n        {\n            let dump = router_b.dump().await.expect(\"Failed to dump router\");\n\n            // There should be 1 Transport in router_b:\n            // - PipeTransport between router_a and router_b.\n            assert_eq!(dump.transport_ids.len(), 1);\n        }\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/plain_transport.rs",
    "content": "use futures_lite::future;\nuse hash_hasher::HashedSet;\nuse mediasoup::plain_transport::{PlainTransportOptions, PlainTransportRemoteParameters};\nuse mediasoup::prelude::*;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::worker::{RequestError, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\n#[cfg(not(target_os = \"windows\"))]\nuse mediasoup_types::data_structures::SocketFlags;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol, SctpState, TransportTuple};\nuse mediasoup_types::rtp_parameters::{\n    MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters,\n};\nuse mediasoup_types::sctp_parameters::SctpParameters;\nuse mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters};\nuse portpicker::pick_unused_port;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\n\nstruct CustomAppData {\n    foo: &'static str,\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"useinbandfec\", 1_u32.into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![], // Will be ignored.\n        },\n    ]\n}\n\nasync fn init() -> (Worker, Router) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    (worker, router)\n}\n\n#[test]\nfn create_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        {\n            let transport = router\n                .create_plain_transport({\n                    let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: Some(\"4.4.4.4\".to_string()),\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n                    plain_transport_options.rtcp_mux = false;\n\n                    plain_transport_options\n                })\n                .await\n                .expect(\"Failed to create Plain transport\");\n\n            let router_dump = router.dump().await.expect(\"Failed to dump router\");\n            assert_eq!(router_dump.transport_ids, {\n                let mut set = HashedSet::default();\n                set.insert(transport.id());\n                set\n            });\n        }\n\n        {\n            let new_transports_count = Arc::new(AtomicUsize::new(0));\n\n            router\n                .on_new_transport({\n                    let new_transports_count = Arc::clone(&new_transports_count);\n\n                    move |_transport| {\n                        new_transports_count.fetch_add(1, Ordering::SeqCst);\n                    }\n                })\n                .detach();\n\n            let transport1 = router\n                .create_plain_transport({\n                    let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: Some(\"9.9.9.1\".to_string()),\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n                    plain_transport_options.rtcp_mux = true;\n                    plain_transport_options.enable_sctp = true;\n                    plain_transport_options.app_data = AppData::new(CustomAppData { foo: \"bar\" });\n\n                    plain_transport_options\n                })\n                .await\n                .expect(\"Failed to create Plain transport\");\n\n            assert_eq!(new_transports_count.load(Ordering::SeqCst), 1);\n            assert!(!transport1.closed());\n            assert_eq!(\n                transport1\n                    .app_data()\n                    .downcast_ref::<CustomAppData>()\n                    .unwrap()\n                    .foo,\n                \"bar\",\n            );\n\n            assert!(matches!(\n                transport1.tuple(),\n                TransportTuple::LocalOnly { .. },\n            ));\n            if let TransportTuple::LocalOnly {\n                local_address,\n                protocol,\n                ..\n            } = transport1.tuple()\n            {\n                assert_eq!(local_address, \"9.9.9.1\");\n                assert_eq!(protocol, Protocol::Udp);\n            }\n            assert_eq!(transport1.rtcp_tuple(), None);\n            assert_eq!(\n                transport1.sctp_parameters(),\n                Some(SctpParameters {\n                    port: 5000,\n                    os: 1024,\n                    mis: 1024,\n                    max_message_size: 262_144,\n                }),\n            );\n            assert_eq!(transport1.sctp_state(), Some(SctpState::New));\n            assert_eq!(transport1.srtp_parameters(), None);\n\n            {\n                let transport_dump = transport1\n                    .dump()\n                    .await\n                    .expect(\"Failed to dump Plain transport\");\n\n                assert_eq!(transport_dump.id, transport1.id());\n                assert!(!transport_dump.direct);\n                assert_eq!(transport_dump.producer_ids, vec![]);\n                assert_eq!(transport_dump.consumer_ids, vec![]);\n                assert_eq!(transport_dump.tuple, transport1.tuple());\n                assert_eq!(transport_dump.rtcp_tuple, transport1.rtcp_tuple());\n                assert_eq!(transport_dump.srtp_parameters, transport1.srtp_parameters());\n                assert_eq!(transport_dump.sctp_state, transport1.sctp_state());\n            }\n        }\n\n        {\n            let rtcp_port = pick_unused_port().unwrap();\n            let transport2 = router\n                .create_plain_transport({\n                    let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n                    plain_transport_options.rtcp_mux = false;\n\n                    plain_transport_options.rtcp_listen_info = Some(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(rtcp_port),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n\n                    plain_transport_options\n                })\n                .await\n                .expect(\"Failed to create Plain transport\");\n\n            assert!(!transport2.closed());\n            assert_eq!(transport2.app_data().downcast_ref::<()>().unwrap(), &(),);\n            assert!(matches!(\n                transport2.tuple(),\n                TransportTuple::LocalOnly { .. },\n            ));\n            if let TransportTuple::LocalOnly {\n                local_address,\n                protocol,\n                ..\n            } = transport2.tuple()\n            {\n                assert_eq!(local_address, \"127.0.0.1\");\n                assert_eq!(protocol, Protocol::Udp);\n            }\n            assert!(transport2.rtcp_tuple().is_some());\n            if let TransportTuple::LocalOnly {\n                local_address,\n                local_port,\n                protocol,\n                ..\n            } = transport2.rtcp_tuple().unwrap()\n            {\n                assert_eq!(local_address, \"127.0.0.1\");\n                assert_eq!(local_port, rtcp_port);\n                assert_eq!(protocol, Protocol::Udp);\n            }\n            assert_eq!(transport2.srtp_parameters(), None);\n            assert_eq!(transport2.sctp_state(), None);\n\n            {\n                let transport_dump = transport2\n                    .dump()\n                    .await\n                    .expect(\"Failed to dump Plain transport\");\n\n                assert_eq!(transport_dump.id, transport2.id());\n                assert!(!transport_dump.direct);\n                assert_eq!(transport_dump.tuple, transport2.tuple());\n                assert_eq!(transport_dump.rtcp_tuple, transport2.rtcp_tuple());\n                assert_eq!(transport_dump.sctp_state, transport2.sctp_state());\n            }\n        }\n    });\n}\n\n#[test]\nfn create_with_fixed_port_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let port = pick_unused_port().unwrap();\n\n        let transport = router\n            .create_plain_transport({\n                PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                })\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        assert_eq!(transport.tuple().local_port(), port);\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        let weak_transport = transport.downgrade();\n\n        assert!(weak_transport.upgrade().is_some());\n\n        drop(transport);\n\n        assert!(weak_transport.upgrade().is_none());\n    });\n}\n\n#[test]\nfn create_enable_srtp_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        // Use default cryptoSuite: 'AES_CM_128_HMAC_SHA1_80'.\n        let transport1 = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.enable_srtp = true;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        assert!(transport1.srtp_parameters().is_some());\n        assert_eq!(\n            transport1.srtp_parameters().unwrap().crypto_suite,\n            SrtpCryptoSuite::AesCm128HmacSha180,\n        );\n        assert_eq!(transport1.srtp_parameters().unwrap().key_base64.len(), 40);\n\n        // Missing srtp_parameters.\n        assert!(matches!(\n            transport1\n                .connect(PlainTransportRemoteParameters {\n                    ip: Some(\"127.0.0.2\".parse().unwrap()),\n                    port: Some(9999),\n                    rtcp_port: None,\n                    srtp_parameters: None\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        // Valid srtp_parameters. And let's update the crypto suite.\n        transport1\n            .connect(PlainTransportRemoteParameters {\n                ip: Some(\"127.0.0.2\".parse().unwrap()),\n                port: Some(9999),\n                rtcp_port: None,\n                srtp_parameters: Some(SrtpParameters {\n                    crypto_suite: SrtpCryptoSuite::AeadAes256Gcm,\n                    key_base64: \"YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=\"\n                        .to_string(),\n                }),\n            })\n            .await\n            .expect(\"Failed to establish Plain transport connection\");\n\n        assert_eq!(\n            transport1.srtp_parameters().unwrap().crypto_suite,\n            SrtpCryptoSuite::AeadAes256Gcm,\n        );\n        assert_eq!(transport1.srtp_parameters().unwrap().key_base64.len(), 60);\n    });\n}\n\n#[test]\nfn create_non_bindable_ip() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        assert!(matches!(\n            router\n                .create_plain_transport(PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: \"8.8.8.8\".parse().unwrap(),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }))\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n    });\n}\n\n#[cfg(not(target_os = \"windows\"))]\n#[test]\nfn create_two_transports_binding_to_same_ip_port_with_udp_reuse_port_flag_succeed() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let multicast_ip = \"224.0.0.1\".parse().unwrap();\n        let port = pick_unused_port().unwrap();\n\n        let transport1 = router\n            .create_plain_transport({\n                PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: multicast_ip,\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    // NOTE: ipv6Only flag will be ignored since ip is IPv4.\n                    flags: Some(SocketFlags {\n                        ipv6_only: true,\n                        udp_reuse_port: true,\n                    }),\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                })\n            })\n            .await\n            .expect(\"Failed to create first Plain transport\");\n\n        let transport2 = router\n            .create_plain_transport({\n                PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: multicast_ip,\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: Some(SocketFlags {\n                        ipv6_only: false,\n                        udp_reuse_port: true,\n                    }),\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                })\n            })\n            .await\n            .expect(\"Failed to create second Plain transport\");\n\n        assert_eq!(transport1.tuple().local_port(), port);\n        assert_eq!(transport2.tuple().local_port(), port);\n    });\n}\n\n#[cfg(not(target_os = \"windows\"))]\n#[test]\nfn create_two_transports_binding_to_same_ip_port_without_udp_reuse_port_flag_fails() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let multicast_ip = \"224.0.0.1\".parse().unwrap();\n        let port = pick_unused_port().unwrap();\n\n        let transport1 = router\n            .create_plain_transport({\n                PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: multicast_ip,\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: Some(SocketFlags {\n                        ipv6_only: false,\n                        udp_reuse_port: false,\n                    }),\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                })\n            })\n            .await\n            .expect(\"Failed to create first Plain transport\");\n\n        assert!(matches!(\n            router\n                .create_plain_transport(PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: multicast_ip,\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: Some(SocketFlags {\n                        ipv6_only: false,\n                        udp_reuse_port: false,\n                    }),\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }))\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        assert_eq!(transport1.tuple().local_port(), port);\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        let stats = transport\n            .get_stats()\n            .await\n            .expect(\"Failed to get stats on Plain transport\");\n\n        assert_eq!(stats.len(), 1);\n        assert_eq!(stats[0].transport_id, transport.id());\n        assert_eq!(stats[0].bytes_received, 0);\n        assert_eq!(stats[0].recv_bitrate, 0);\n        assert_eq!(stats[0].bytes_sent, 0);\n        assert_eq!(stats[0].send_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_received, 0);\n        assert_eq!(stats[0].rtp_recv_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_sent, 0);\n        assert_eq!(stats[0].rtp_send_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_received, 0);\n        assert_eq!(stats[0].rtx_recv_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_sent, 0);\n        assert_eq!(stats[0].rtx_send_bitrate, 0);\n        assert_eq!(stats[0].probation_bytes_sent, 0);\n        assert_eq!(stats[0].probation_send_bitrate, 0);\n        assert_eq!(stats[0].rtp_packet_loss_received, None);\n        assert_eq!(stats[0].rtp_packet_loss_sent, None);\n        assert!(matches!(stats[0].tuple, TransportTuple::LocalOnly { .. },));\n        if let TransportTuple::LocalOnly {\n            local_address,\n            protocol,\n            ..\n        } = &stats[0].tuple\n        {\n            assert_eq!(local_address, \"4.4.4.4\");\n            assert_eq!(*protocol, Protocol::Udp);\n        }\n        assert_eq!(stats[0].rtcp_tuple, None);\n    });\n}\n\n#[test]\nfn connect_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        transport\n            .connect(PlainTransportRemoteParameters {\n                ip: Some(\"1.2.3.4\".parse().unwrap()),\n                port: Some(1234),\n                rtcp_port: Some(1235),\n                srtp_parameters: None,\n            })\n            .await\n            .expect(\"Failed to establish Plain transport connection\");\n\n        // Must fail if connected.\n        assert!(matches!(\n            transport\n                .connect(PlainTransportRemoteParameters {\n                    ip: Some(\"1.2.3.4\".parse().unwrap()),\n                    port: Some(1234),\n                    rtcp_port: Some(1235),\n                    srtp_parameters: None,\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        assert!(matches!(\n            transport.tuple(),\n            TransportTuple::WithRemote { .. },\n        ));\n        if let TransportTuple::WithRemote {\n            remote_ip,\n            remote_port,\n            protocol,\n            ..\n        } = transport.tuple()\n        {\n            assert_eq!(remote_ip, \"1.2.3.4\".parse::<IpAddr>().unwrap());\n            assert_eq!(remote_port, 1234);\n            assert_eq!(protocol, Protocol::Udp);\n        }\n        assert!(transport.rtcp_tuple().is_some());\n        if let TransportTuple::WithRemote {\n            remote_ip,\n            remote_port,\n            protocol,\n            ..\n        } = transport.rtcp_tuple().unwrap()\n        {\n            assert_eq!(remote_ip, \"1.2.3.4\".parse::<IpAddr>().unwrap());\n            assert_eq!(remote_port, 1235);\n            assert_eq!(protocol, Protocol::Udp);\n        }\n    });\n}\n\n#[test]\nfn connect_wrong_arguments() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        // No SRTP enabled so passing srtp_parameters must fail.\n        assert!(matches!(\n            transport\n                .connect(PlainTransportRemoteParameters {\n                    ip: Some(\"127.0.0.2\".parse().unwrap()),\n                    port: Some(9998),\n                    rtcp_port: Some(9999),\n                    srtp_parameters: Some(SrtpParameters {\n                        crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180,\n                        key_base64: \"ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv\".to_string(),\n                    }),\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_plain_transport({\n                let mut plain_transport_options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"4.4.4.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                plain_transport_options.rtcp_mux = false;\n\n                plain_transport_options\n            })\n            .await\n            .expect(\"Failed to create Plain transport\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        drop(transport);\n\n        close_rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/producer.rs",
    "content": "use async_io::Timer;\nuse futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse mediasoup::prelude::*;\nuse mediasoup::producer::{ProducerOptions, ProducerTraceEventType, ProducerType};\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::transport::ProduceError;\nuse mediasoup::webrtc_transport::{\n    WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions,\n};\nuse mediasoup::worker::{Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCodecCapability,\n    RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters,\n    RtpEncodingParametersRtx, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters,\n};\nuse mediasoup_types::scalability_modes::ScalabilityMode;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\nuse std::time::Duration;\n\nstruct ProducerAppData {\n    foo: i32,\n    bar: &'static str,\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([(\"foo\", \"111\".into())]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ]\n}\n\nfn audio_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Audio,\n        RtpParameters {\n            mid: Some(\"AUDIO\".to_string()),\n            codecs: vec![RtpCodecParameters::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                payload_type: 0,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: RtpCodecParametersParameters::from([\n                    (\"useinbandfec\", 1_u32.into()),\n                    (\"usedtx\", 1_u32.into()),\n                    (\"foo\", \"222.222\".into()),\n                    (\"bar\", \"333\".into()),\n                ]),\n                rtcp_feedback: vec![],\n            }],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::SsrcAudioLevel,\n                    id: 12,\n                    encrypt: false,\n                },\n            ],\n            // Missing encodings on purpose.\n            encodings: vec![],\n            rtcp: RtcpParameters {\n                cname: Some(\"audio-1\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    );\n\n    options.app_data = AppData::new(ProducerAppData { foo: 1, bar: \"2\" });\n\n    options\n}\n\nfn video_producer_options() -> ProducerOptions {\n    let mut options = ProducerOptions::new(\n        MediaKind::Video,\n        RtpParameters {\n            mid: Some(\"VIDEO\".to_string()),\n            codecs: vec![\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::H264,\n                    payload_type: 112,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([\n                        (\"packetization-mode\", 1_u32.into()),\n                        (\"profile-level-id\", \"4d0032\".into()),\n                    ]),\n                    rtcp_feedback: vec![\n                        RtcpFeedback::Nack,\n                        RtcpFeedback::NackPli,\n                        RtcpFeedback::GoogRemb,\n                    ],\n                },\n                RtpCodecParameters::Video {\n                    mime_type: MimeTypeVideo::Rtx,\n                    payload_type: 113,\n                    clock_rate: NonZeroU32::new(90000).unwrap(),\n                    parameters: RtpCodecParametersParameters::from([(\"apt\", 112u32.into())]),\n                    rtcp_feedback: vec![],\n                },\n            ],\n            header_extensions: vec![\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::Mid,\n                    id: 10,\n                    encrypt: false,\n                },\n                RtpHeaderExtensionParameters {\n                    uri: RtpHeaderExtensionUri::VideoOrientation,\n                    id: 13,\n                    encrypt: false,\n                },\n            ],\n            encodings: vec![\n                RtpEncodingParameters {\n                    ssrc: Some(22222222),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }),\n                    scalability_mode: \"L1T3\".parse().unwrap(),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222224),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222226),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }),\n                    ..RtpEncodingParameters::default()\n                },\n                RtpEncodingParameters {\n                    ssrc: Some(22222228),\n                    rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }),\n                    ..RtpEncodingParameters::default()\n                },\n            ],\n            rtcp: RtcpParameters {\n                cname: Some(\"video-1\".to_string()),\n                ..RtcpParameters::default()\n            },\n            msid: None,\n        },\n    );\n\n    options.app_data = AppData::new(ProducerAppData { foo: 1, bar: \"2\" });\n\n    options\n}\n\nasync fn init() -> (Worker, Router, WebRtcTransport, WebRtcTransport) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    let transport_options =\n        WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n            protocol: Protocol::Udp,\n            ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n            announced_address: None,\n            expose_internal_ip: false,\n            port: None,\n            port_range: None,\n            flags: None,\n            send_buffer_size: None,\n            recv_buffer_size: None,\n        }));\n\n    let transport_1 = router\n        .create_webrtc_transport(transport_options.clone())\n        .await\n        .expect(\"Failed to create transport1\");\n\n    let transport_2 = router\n        .create_webrtc_transport(transport_options)\n        .await\n        .expect(\"Failed to create transport2\");\n\n    (worker, router, transport_1, transport_2)\n}\n\n#[test]\nfn produce_succeeds_1() {\n    future::block_on(async move {\n        let (_worker, router, transport_1, _transport_2) = init().await;\n\n        let new_producers_count = Arc::new(AtomicUsize::new(0));\n\n        transport_1\n            .on_new_producer({\n                let new_producers_count = Arc::clone(&new_producers_count);\n\n                Arc::new(move |_producer| {\n                    new_producers_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        assert_eq!(new_producers_count.load(Ordering::SeqCst), 1);\n        assert!(!audio_producer.closed());\n        assert_eq!(audio_producer.kind(), MediaKind::Audio);\n        assert_eq!(audio_producer.r#type(), ProducerType::Simple);\n        assert!(!audio_producer.paused());\n        assert_eq!(audio_producer.score(), vec![]);\n        assert_eq!(\n            audio_producer\n                .app_data()\n                .downcast_ref::<ProducerAppData>()\n                .unwrap()\n                .foo,\n            1\n        );\n        assert_eq!(\n            audio_producer\n                .app_data()\n                .downcast_ref::<ProducerAppData>()\n                .unwrap()\n                .bar,\n            \"2\"\n        );\n\n        let router_dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(router_dump.map_producer_id_consumer_ids, {\n            let mut map = HashedMap::default();\n            map.insert(audio_producer.id(), HashedSet::default());\n            map\n        });\n        assert_eq!(\n            router_dump.map_consumer_id_producer_id,\n            HashedMap::default()\n        );\n\n        let transport_1_dump = transport_1\n            .dump()\n            .await\n            .expect(\"Failed to get transport 1 dump\");\n\n        assert_eq!(transport_1_dump.producer_ids, vec![audio_producer.id()]);\n        assert_eq!(transport_1_dump.consumer_ids, vec![]);\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        transport_1\n            .on_close(Box::new(move || {\n                let _ = tx.send(());\n            }))\n            .detach();\n\n        drop(audio_producer);\n        drop(transport_1);\n\n        // This means producer was definitely dropped\n        rx.await.expect(\"Failed to receive transport close event\");\n    });\n}\n\n#[test]\nfn produce_succeeds_2() {\n    future::block_on(async move {\n        let (_worker, router, _transport_1, transport_2) = init().await;\n\n        let new_producers_count = Arc::new(AtomicUsize::new(0));\n\n        transport_2\n            .on_new_producer({\n                let new_producers_count = Arc::clone(&new_producers_count);\n\n                Arc::new(move |_producer| {\n                    new_producers_count.fetch_add(1, Ordering::SeqCst);\n                })\n            })\n            .detach();\n\n        let video_producer = transport_2\n            .produce(video_producer_options())\n            .await\n            .expect(\"Failed to produce video\");\n\n        assert_eq!(new_producers_count.load(Ordering::SeqCst), 1);\n        assert!(!video_producer.closed());\n        assert_eq!(video_producer.kind(), MediaKind::Video);\n        assert_eq!(video_producer.r#type(), ProducerType::Simulcast);\n        assert!(!video_producer.paused());\n        assert_eq!(video_producer.score(), vec![]);\n        assert_eq!(\n            video_producer\n                .app_data()\n                .downcast_ref::<ProducerAppData>()\n                .unwrap()\n                .foo,\n            1\n        );\n        assert_eq!(\n            video_producer\n                .app_data()\n                .downcast_ref::<ProducerAppData>()\n                .unwrap()\n                .bar,\n            \"2\"\n        );\n\n        let router_dump = router.dump().await.expect(\"Failed to get router dump\");\n\n        assert_eq!(router_dump.map_producer_id_consumer_ids, {\n            let mut map = HashedMap::default();\n            map.insert(video_producer.id(), HashedSet::default());\n            map\n        });\n        assert_eq!(\n            router_dump.map_consumer_id_producer_id,\n            HashedMap::default()\n        );\n\n        let transport_2_dump = transport_2\n            .dump()\n            .await\n            .expect(\"Failed to get transport 2 dump\");\n\n        assert_eq!(transport_2_dump.producer_ids, vec![video_producer.id()]);\n        assert_eq!(transport_2_dump.consumer_ids, vec![]);\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        let producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        let weak_producer = producer.downgrade();\n\n        assert!(weak_producer.upgrade().is_some());\n\n        drop(producer);\n\n        assert!(weak_producer.upgrade().is_none());\n    });\n}\n\n#[test]\nfn produce_wrong_arguments() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        // Empty rtp_parameters.codecs.\n        assert!(matches!(\n            transport_1\n                .produce(ProducerOptions::new(\n                    MediaKind::Audio,\n                    RtpParameters::default()\n                ))\n                .await,\n            Err(ProduceError::Request(_)),\n        ));\n\n        {\n            // Empty rtp_parameters.encodings.\n            let produce_result = transport_1\n                .produce(ProducerOptions::new(MediaKind::Video, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.codecs = vec![\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::H264,\n                            payload_type: 112,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([\n                                (\"packetization-mode\", 1_u32.into()),\n                                (\"profile-level-id\", \"4d0032\".into()),\n                            ]),\n                            rtcp_feedback: vec![],\n                        },\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::Rtx,\n                            payload_type: 113,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([(\n                                \"apt\",\n                                112u32.into(),\n                            )]),\n                            rtcp_feedback: vec![],\n                        },\n                    ];\n                    parameters.rtcp = RtcpParameters {\n                        cname: Some(\"qwerty\".to_string()),\n                        ..RtcpParameters::default()\n                    };\n\n                    parameters\n                }))\n                .await;\n\n            assert!(matches!(produce_result, Err(ProduceError::Request(_))));\n        }\n\n        {\n            // Wrong apt in RTX codec.\n            let produce_result = transport_1\n                .produce(ProducerOptions::new(MediaKind::Video, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.codecs = vec![\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::H264,\n                            payload_type: 112,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([\n                                (\"packetization-mode\", 1_u32.into()),\n                                (\"profile-level-id\", \"4d0032\".into()),\n                            ]),\n                            rtcp_feedback: vec![],\n                        },\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::Rtx,\n                            payload_type: 113,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([(\n                                \"apt\",\n                                111_u32.into(),\n                            )]),\n                            rtcp_feedback: vec![],\n                        },\n                    ];\n                    parameters.encodings = vec![RtpEncodingParameters {\n                        ssrc: Some(6666),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }),\n                        ..RtpEncodingParameters::default()\n                    }];\n                    parameters.rtcp = RtcpParameters {\n                        cname: Some(\"video-1\".to_string()),\n                        ..RtcpParameters::default()\n                    };\n\n                    parameters\n                }))\n                .await;\n\n            assert!(matches!(\n                produce_result,\n                Err(ProduceError::FailedRtpParametersMapping(_))\n            ));\n        }\n    });\n}\n\n#[test]\nfn produce_unsupported_codecs() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        // Empty rtp_parameters.codecs.\n        assert!(matches!(\n            transport_1\n                .produce(ProducerOptions::new(MediaKind::Audio, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.codecs = vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::Isac,\n                        payload_type: 108,\n                        clock_rate: NonZeroU32::new(32000).unwrap(),\n                        channels: NonZeroU8::new(1).unwrap(),\n                        parameters: RtpCodecParametersParameters::default(),\n                        rtcp_feedback: vec![],\n                    }];\n                    parameters.header_extensions = vec![];\n                    parameters.encodings = vec![RtpEncodingParameters {\n                        ssrc: Some(1111),\n                        ..RtpEncodingParameters::default()\n                    }];\n                    parameters.rtcp = RtcpParameters {\n                        cname: Some(\"audio\".to_string()),\n                        ..RtcpParameters::default()\n                    };\n\n                    parameters\n                }))\n                .await,\n            Err(ProduceError::FailedRtpParametersMapping(_)),\n        ));\n\n        {\n            // Invalid H264 profile-level-id.\n            let produce_result = transport_1\n                .produce(ProducerOptions::new(MediaKind::Video, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.codecs = vec![\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::H264,\n                            payload_type: 112,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([\n                                (\"packetization-mode\", 1_u32.into()),\n                                (\"profile-level-id\", \"CHICKEN\".into()),\n                            ]),\n                            rtcp_feedback: vec![],\n                        },\n                        RtpCodecParameters::Video {\n                            mime_type: MimeTypeVideo::Rtx,\n                            payload_type: 113,\n                            clock_rate: NonZeroU32::new(90000).unwrap(),\n                            parameters: RtpCodecParametersParameters::from([(\n                                \"apt\",\n                                112u32.into(),\n                            )]),\n                            rtcp_feedback: vec![],\n                        },\n                    ];\n                    parameters.encodings = vec![RtpEncodingParameters {\n                        ssrc: Some(6666),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }),\n                        ..RtpEncodingParameters::default()\n                    }];\n\n                    parameters\n                }))\n                .await;\n\n            assert!(matches!(\n                produce_result,\n                Err(ProduceError::FailedRtpParametersMapping(_))\n            ));\n        }\n    });\n}\n\n#[test]\nfn produce_already_used_mid_ssrc() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, transport_2) = init().await;\n\n        {\n            let _first_producer = transport_1\n                .produce(audio_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let produce_result = transport_1\n                .produce(ProducerOptions::new(MediaKind::Audio, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.mid = Some(\"AUDIO\".to_string());\n                    parameters.codecs = vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::Opus,\n                        payload_type: 0,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(2).unwrap(),\n                        parameters: RtpCodecParametersParameters::default(),\n                        rtcp_feedback: vec![],\n                    }];\n                    parameters.header_extensions = vec![];\n                    parameters.encodings = vec![RtpEncodingParameters {\n                        ssrc: Some(33333333),\n                        ..RtpEncodingParameters::default()\n                    }];\n                    parameters.rtcp = RtcpParameters {\n                        cname: Some(\"audio-2\".to_string()),\n                        ..RtcpParameters::default()\n                    };\n\n                    parameters\n                }))\n                .await;\n\n            assert!(matches!(produce_result, Err(ProduceError::Request(_)),));\n        }\n\n        {\n            let _first_producer = transport_2\n                .produce(video_producer_options())\n                .await\n                .expect(\"Failed to produce video\");\n\n            // Invalid H264 profile-level-id.\n            let produce_result = transport_2\n                .produce(ProducerOptions::new(MediaKind::Video, {\n                    let mut parameters = RtpParameters::default();\n\n                    parameters.mid = Some(\"VIDEO2\".to_string());\n                    parameters.codecs = vec![RtpCodecParameters::Video {\n                        mime_type: MimeTypeVideo::Vp8,\n                        payload_type: 112,\n                        clock_rate: NonZeroU32::new(90000).unwrap(),\n                        parameters: RtpCodecParametersParameters::default(),\n                        rtcp_feedback: vec![],\n                    }];\n                    parameters.encodings = vec![RtpEncodingParameters {\n                        ssrc: Some(22222222),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }),\n                        ..RtpEncodingParameters::default()\n                    }];\n\n                    parameters\n                }))\n                .await;\n\n            assert!(matches!(produce_result, Err(ProduceError::Request(_))));\n        }\n    });\n}\n\n#[test]\nfn produce_no_mid_single_encoding_without_rid_or_ssrc() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        let produce_result = transport_1\n            .produce(ProducerOptions::new(MediaKind::Audio, {\n                let mut parameters = RtpParameters::default();\n\n                parameters.codecs = vec![RtpCodecParameters::Audio {\n                    mime_type: MimeTypeAudio::Opus,\n                    payload_type: 111,\n                    clock_rate: NonZeroU32::new(48000).unwrap(),\n                    channels: NonZeroU8::new(2).unwrap(),\n                    parameters: RtpCodecParametersParameters::default(),\n                    rtcp_feedback: vec![],\n                }];\n                parameters.header_extensions = vec![];\n                parameters.encodings = vec![RtpEncodingParameters::default()];\n                parameters.rtcp = RtcpParameters {\n                    cname: Some(\"audio-2\".to_string()),\n                    ..RtcpParameters::default()\n                };\n\n                parameters\n            }))\n            .await;\n\n        assert!(matches!(produce_result, Err(ProduceError::Request(_)),));\n    });\n}\n\n#[test]\nfn dump_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, transport_2) = init().await;\n\n        {\n            let audio_producer = transport_1\n                .produce(audio_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let dump = audio_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump audio producer\");\n\n            assert_eq!(dump.id, audio_producer.id());\n            assert_eq!(dump.kind, audio_producer.kind());\n            assert_eq!(\n                dump.rtp_parameters.codecs,\n                audio_producer_options().rtp_parameters.codecs,\n            );\n            assert_eq!(\n                dump.rtp_parameters.header_extensions,\n                audio_producer_options().rtp_parameters.header_extensions,\n            );\n            assert_eq!(\n                dump.rtp_parameters.encodings,\n                vec![RtpEncodingParameters {\n                    ssrc: None,\n                    rid: None,\n                    codec_payload_type: Some(0),\n                    rtx: None,\n                    dtx: None,\n                    scalability_mode: ScalabilityMode::None,\n                    max_bitrate: None\n                }],\n            );\n            assert_eq!(dump.r#type, ProducerType::Simple);\n        }\n\n        {\n            let video_producer = transport_2\n                .produce(video_producer_options())\n                .await\n                .expect(\"Failed to produce video\");\n\n            let dump = video_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump video producer\");\n\n            assert_eq!(dump.id, video_producer.id());\n            assert_eq!(dump.kind, video_producer.kind());\n            assert_eq!(\n                dump.rtp_parameters.codecs,\n                video_producer_options().rtp_parameters.codecs,\n            );\n            assert_eq!(\n                dump.rtp_parameters.header_extensions,\n                video_producer_options().rtp_parameters.header_extensions,\n            );\n            assert_eq!(\n                dump.rtp_parameters.encodings,\n                vec![\n                    RtpEncodingParameters {\n                        ssrc: Some(22222222),\n                        rid: None,\n                        codec_payload_type: Some(112),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }),\n                        dtx: None,\n                        scalability_mode: \"L1T3\".parse().unwrap(),\n                        max_bitrate: None\n                    },\n                    RtpEncodingParameters {\n                        ssrc: Some(22222224),\n                        rid: None,\n                        codec_payload_type: Some(112),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }),\n                        dtx: None,\n                        scalability_mode: ScalabilityMode::None,\n                        max_bitrate: None\n                    },\n                    RtpEncodingParameters {\n                        ssrc: Some(22222226),\n                        rid: None,\n                        codec_payload_type: Some(112),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }),\n                        dtx: None,\n                        scalability_mode: ScalabilityMode::None,\n                        max_bitrate: None\n                    },\n                    RtpEncodingParameters {\n                        ssrc: Some(22222228),\n                        rid: None,\n                        codec_payload_type: Some(112),\n                        rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }),\n                        dtx: None,\n                        scalability_mode: ScalabilityMode::None,\n                        max_bitrate: None\n                    },\n                ],\n            );\n            assert_eq!(dump.r#type, ProducerType::Simulcast);\n        }\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, transport_2) = init().await;\n\n        {\n            let audio_producer = transport_1\n                .produce(audio_producer_options())\n                .await\n                .expect(\"Failed to produce audio\");\n\n            let stats = audio_producer\n                .get_stats()\n                .await\n                .expect(\"Failed to get stats on audio producer\");\n\n            assert_eq!(stats, vec![]);\n        }\n\n        {\n            let video_producer = transport_2\n                .produce(video_producer_options())\n                .await\n                .expect(\"Failed to produce video\");\n\n            let stats = video_producer\n                .get_stats()\n                .await\n                .expect(\"Failed to get stats on video producer\");\n\n            assert_eq!(stats, vec![]);\n        }\n    });\n}\n\n#[test]\nfn pause_resume_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        {\n            audio_producer\n                .pause()\n                .await\n                .expect(\"Failed to pause audio producer\");\n\n            assert!(audio_producer.paused());\n\n            let dump = audio_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump audio producer\");\n\n            assert!(dump.paused);\n        }\n\n        {\n            audio_producer\n                .resume()\n                .await\n                .expect(\"Failed to resume audio producer\");\n\n            assert!(!audio_producer.paused());\n\n            let dump = audio_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump audio producer\");\n\n            assert!(!dump.paused);\n        }\n    });\n}\n\n#[test]\nfn enable_trace_event_succeeds() {\n    future::block_on(async move {\n        let (_worker, _router, transport_1, _transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        {\n            audio_producer\n                .enable_trace_event(vec![\n                    ProducerTraceEventType::Rtp,\n                    ProducerTraceEventType::Pli,\n                ])\n                .await\n                .expect(\"Failed to enable trace event\");\n\n            let dump = audio_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump audio producer\");\n\n            assert_eq!(\n                dump.trace_event_types,\n                vec![ProducerTraceEventType::Rtp, ProducerTraceEventType::Pli]\n            );\n        }\n\n        {\n            audio_producer\n                .enable_trace_event(vec![])\n                .await\n                .expect(\"Failed to enable trace event\");\n\n            let dump = audio_producer\n                .dump()\n                .await\n                .expect(\"Failed to dump audio producer\");\n\n            assert_eq!(dump.trace_event_types, vec![]);\n        }\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, router, transport_1, _transport_2) = init().await;\n\n        let audio_producer = transport_1\n            .produce(audio_producer_options())\n            .await\n            .expect(\"Failed to produce audio\");\n\n        {\n            let (mut tx, rx) = async_oneshot::oneshot::<()>();\n            let _handler = audio_producer.on_close(move || {\n                let _ = tx.send(());\n            });\n            drop(audio_producer);\n\n            rx.await.expect(\"Failed to receive close event\");\n        }\n\n        // Drop is async, give producer a bit of time to finish\n        Timer::after(Duration::from_millis(200)).await;\n\n        {\n            let dump = router.dump().await.expect(\"Failed to dump router\");\n\n            assert_eq!(dump.map_producer_id_consumer_ids, HashedMap::default());\n            assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default());\n        }\n\n        {\n            let dump = transport_1.dump().await.expect(\"Failed to dump transport\");\n\n            assert_eq!(dump.producer_ids, vec![]);\n            assert_eq!(dump.consumer_ids, vec![]);\n        }\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/router.rs",
    "content": "use futures_lite::future;\nuse hash_hasher::{HashedMap, HashedSet};\nuse mediasoup::router::RouterOptions;\nuse mediasoup::worker::{ChannelMessageHandlers, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::AppData;\nuse mediasoup_types::rtp_parameters::{\n    MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters,\n};\nuse std::env;\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"useinbandfec\", 1_u32.into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n            ]),\n            rtcp_feedback: vec![], // Will be ignored.\n        },\n    ]\n}\n\nasync fn init() -> Worker {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\")\n}\n\n#[test]\nfn create_router_succeeds() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let new_router_count = Arc::new(AtomicUsize::new(0));\n\n        worker\n            .on_new_router({\n                let new_router_count = Arc::clone(&new_router_count);\n\n                move |_router| {\n                    new_router_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        #[derive(Debug, PartialEq)]\n        struct CustomAppData {\n            foo: u32,\n        }\n\n        let router = worker\n            .create_router({\n                let mut router_options = RouterOptions::new(media_codecs());\n\n                router_options.app_data = AppData::new(CustomAppData { foo: 123 });\n\n                router_options\n            })\n            .await\n            .expect(\"Failed to create router\");\n\n        assert_eq!(new_router_count.load(Ordering::SeqCst), 1);\n        assert!(!router.closed());\n        // 3 codecs + 2 RTX codecs.\n        assert_eq!(router.rtp_capabilities().codecs.len(), 5);\n        assert_eq!(\n            router.app_data().downcast_ref::<CustomAppData>(),\n            Some(&CustomAppData { foo: 123 }),\n        );\n\n        let worker_dump = worker.dump().await.expect(\"Failed to dump worker\");\n\n        assert_eq!(worker_dump.router_ids, vec![router.id()]);\n        assert_eq!(worker_dump.webrtc_server_ids, vec![]);\n        assert_eq!(\n            worker_dump.channel_message_handlers,\n            ChannelMessageHandlers {\n                channel_request_handlers: vec![router.id().into()],\n                channel_notification_handlers: vec![]\n            }\n        );\n\n        let dump = router.dump().await.expect(\"Failed to dump router\");\n\n        assert_eq!(dump.id, router.id());\n        assert_eq!(dump.transport_ids, HashedSet::default());\n        assert_eq!(dump.rtp_observer_ids, HashedSet::default());\n        assert_eq!(dump.map_producer_id_consumer_ids, HashedMap::default());\n        assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default());\n        assert_eq!(dump.map_producer_id_observer_ids, HashedMap::default());\n        assert_eq!(\n            dump.map_data_producer_id_data_consumer_ids,\n            HashedMap::default()\n        );\n        assert_eq!(\n            dump.map_data_consumer_id_data_producer_id,\n            HashedMap::default()\n        );\n    });\n}\n\n#[test]\nfn update_media_codecs_succeeds() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let mut router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        assert!(!router.closed());\n        // 3 codecs + 2 RTX codecs.\n        assert_eq!(router.rtp_capabilities().codecs.len(), 5);\n\n        let _ = router.update_media_codecs([].to_vec());\n\n        assert_eq!(router.rtp_capabilities().codecs.len(), 0);\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let worker = init().await;\n\n        let router = worker\n            .create_router(RouterOptions::new(media_codecs()))\n            .await\n            .expect(\"Failed to create router\");\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        let _handler = router.on_close(move || {\n            let _ = tx.send(());\n        });\n        drop(router);\n\n        rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/smoke.rs",
    "content": "use futures_lite::future;\nuse mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions;\nuse mediasoup::audio_level_observer::AudioLevelObserverOptions;\nuse mediasoup::consumer::{ConsumerLayers, ConsumerOptions, ConsumerTraceEventType};\nuse mediasoup::data_consumer::DataConsumerOptions;\nuse mediasoup::data_producer::DataProducerOptions;\nuse mediasoup::direct_transport::DirectTransportOptions;\nuse mediasoup::plain_transport::PlainTransportOptions;\nuse mediasoup::prelude::*;\nuse mediasoup::producer::{ProducerOptions, ProducerTraceEventType};\nuse mediasoup::router::{PipeToRouterOptions, RouterOptions};\nuse mediasoup::rtp_observer::RtpObserverAddProducerOptions;\nuse mediasoup::transport::TransportTraceEventType;\nuse mediasoup::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions};\nuse mediasoup::worker::{WorkerLogLevel, WorkerSettings, WorkerUpdateSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{ListenInfo, Protocol};\nuse mediasoup_types::rtp_parameters::{\n    MediaKind, MimeTypeAudio, RtpCapabilities, RtpCodecCapability, RtpCodecParameters,\n    RtpParameters,\n};\nuse mediasoup_types::sctp_parameters::SctpStreamParameters;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::{env, thread};\n\nfn init() {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n}\n\n#[test]\nfn smoke() {\n    init();\n\n    let worker_manager = WorkerManager::new();\n\n    future::block_on(async move {\n        let worker = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .unwrap();\n\n        println!(\"Worker dump: {:#?}\", worker.dump().await.unwrap());\n        println!(\n            \"Update settings: {:?}\",\n            worker\n                .update_settings({\n                    let mut settings = WorkerUpdateSettings::default();\n\n                    settings.log_level = Some(WorkerLogLevel::Debug);\n                    settings.log_tags = Some(vec![]);\n\n                    settings\n                })\n                .await\n                .unwrap()\n        );\n\n        let router = worker\n            .create_router({\n                let mut router_options = RouterOptions::default();\n                router_options.media_codecs = vec![RtpCodecCapability::Audio {\n                    mime_type: MimeTypeAudio::Opus,\n                    preferred_payload_type: None,\n                    clock_rate: NonZeroU32::new(48000).unwrap(),\n                    channels: NonZeroU8::new(2).unwrap(),\n                    parameters: Default::default(),\n                    rtcp_feedback: vec![],\n                }];\n                router_options\n            })\n            .await\n            .unwrap();\n        println!(\"Router created: {:?}\", router.id());\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n\n        let webrtc_transport = router\n            .create_webrtc_transport({\n                let mut options =\n                    WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    }));\n\n                options.enable_sctp = true;\n\n                options\n            })\n            .await\n            .unwrap();\n        println!(\"WebRTC transport created: {:?}\", webrtc_transport.id());\n        println!(\n            \"WebRTC transport stats: {:#?}\",\n            webrtc_transport.get_stats().await.unwrap()\n        );\n        println!(\n            \"WebRTC transport dump: {:#?}\",\n            webrtc_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\n            \"WebRTC transport enable trace event: {:#?}\",\n            webrtc_transport\n                .enable_trace_event(vec![TransportTraceEventType::Bwe])\n                .await\n                .unwrap()\n        );\n\n        let producer = webrtc_transport\n            .produce(ProducerOptions::new(\n                MediaKind::Audio,\n                RtpParameters {\n                    mid: Some(\"AUDIO\".to_string()),\n                    codecs: vec![RtpCodecParameters::Audio {\n                        mime_type: MimeTypeAudio::Opus,\n                        payload_type: 111,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(2).unwrap(),\n                        parameters: Default::default(),\n                        rtcp_feedback: vec![],\n                    }],\n                    ..RtpParameters::default()\n                },\n            ))\n            .await\n            .unwrap();\n\n        println!(\"Producer created: {:?}\", producer.id());\n        println!(\n            \"WebRTC transport dump: {:#?}\",\n            webrtc_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n\n        println!(\"Producer stats: {:#?}\", producer.get_stats().await.unwrap());\n        println!(\"Producer dump: {:#?}\", producer.dump().await.unwrap());\n        println!(\"Producer pause: {:#?}\", producer.pause().await.unwrap());\n        println!(\"Producer resume: {:#?}\", producer.resume().await.unwrap());\n        println!(\n            \"Producer enable trace event: {:#?}\",\n            producer\n                .enable_trace_event(vec![ProducerTraceEventType::KeyFrame])\n                .await\n                .unwrap()\n        );\n\n        let consumer = webrtc_transport\n            .consume(ConsumerOptions::new(\n                producer.id(),\n                RtpCapabilities {\n                    codecs: vec![RtpCodecCapability::Audio {\n                        mime_type: MimeTypeAudio::Opus,\n                        preferred_payload_type: None,\n                        clock_rate: NonZeroU32::new(48000).unwrap(),\n                        channels: NonZeroU8::new(2).unwrap(),\n                        parameters: Default::default(),\n                        rtcp_feedback: vec![],\n                    }],\n                    header_extensions: vec![],\n                },\n            ))\n            .await\n            .unwrap();\n        println!(\"Consumer created: {:?}\", consumer.id());\n        println!(\n            \"WebRTC transport dump: {:#?}\",\n            webrtc_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\"Consumer stats: {:#?}\", consumer.get_stats().await.unwrap());\n        println!(\"Producer dump: {:#?}\", producer.dump().await.unwrap());\n        println!(\"Consumer dump: {:#?}\", consumer.dump().await.unwrap());\n        println!(\"Consumer pause: {:#?}\", consumer.pause().await.unwrap());\n        println!(\"Consumer resume: {:#?}\", consumer.resume().await.unwrap());\n        println!(\n            \"Consumer set preferred layers: {:#?}\",\n            consumer\n                .set_preferred_layers(ConsumerLayers {\n                    spatial_layer: 1,\n                    temporal_layer: None\n                })\n                .await\n                .unwrap()\n        );\n        println!(\n            \"Consumer set priority: {:#?}\",\n            consumer.set_priority(10).await.unwrap()\n        );\n        println!(\n            \"Consumer unset priority: {:#?}\",\n            consumer.unset_priority().await.unwrap()\n        );\n        println!(\n            \"Consumer enable trace event: {:#?}\",\n            consumer\n                .enable_trace_event(vec![ConsumerTraceEventType::KeyFrame])\n                .await\n                .unwrap()\n        );\n\n        let data_producer = webrtc_transport\n            .produce_data(DataProducerOptions::new_sctp(\n                SctpStreamParameters::new_ordered(1),\n            ))\n            .await\n            .unwrap();\n\n        println!(\"Data producer created: {:?}\", producer.id());\n        println!(\n            \"WebRTC transport dump: {:#?}\",\n            webrtc_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n\n        println!(\n            \"Data producer stats: {:#?}\",\n            data_producer.get_stats().await.unwrap()\n        );\n        println!(\n            \"Data producer dump: {:#?}\",\n            data_producer.dump().await.unwrap()\n        );\n\n        let data_consumer = webrtc_transport\n            .consume_data(DataConsumerOptions::new_sctp(data_producer.id()))\n            .await\n            .unwrap();\n\n        println!(\"Data consumer created: {:?}\", consumer.id());\n        println!(\n            \"WebRTC transport dump: {:#?}\",\n            webrtc_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n\n        println!(\n            \"Data producer stats: {:#?}\",\n            data_producer.get_stats().await.unwrap()\n        );\n        println!(\n            \"Data consumer stats: {:#?}\",\n            data_consumer.get_stats().await.unwrap()\n        );\n        println!(\n            \"Data consumer dump: {:#?}\",\n            data_consumer.dump().await.unwrap()\n        );\n        println!(\n            \"Data consumer get buffered amount: {:#?}\",\n            data_consumer.get_buffered_amount().await.unwrap()\n        );\n        println!(\n            \"Data consumer set buffered amount low threshold: {:#?}\",\n            data_consumer\n                .set_buffered_amount_low_threshold(256)\n                .await\n                .unwrap()\n        );\n\n        let plain_transport = router\n            .create_plain_transport({\n                let mut options = PlainTransportOptions::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n\n                options.enable_sctp = true;\n\n                options\n            })\n            .await\n            .unwrap();\n        println!(\"Plain transport created: {:?}\", plain_transport.id());\n        println!(\n            \"Plain transport stats: {:#?}\",\n            plain_transport.get_stats().await.unwrap()\n        );\n        println!(\n            \"Plain transport dump: {:#?}\",\n            plain_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\n            \"Plain transport enable trace event: {:#?}\",\n            plain_transport\n                .enable_trace_event(vec![TransportTraceEventType::Bwe])\n                .await\n                .unwrap()\n        );\n\n        let direct_transport = router\n            .create_direct_transport(DirectTransportOptions::default())\n            .await\n            .unwrap();\n        println!(\"Direct transport created: {:?}\", direct_transport.id());\n        println!(\n            \"Direct transport stats: {:#?}\",\n            direct_transport.get_stats().await.unwrap()\n        );\n        println!(\n            \"Direct transport dump: {:#?}\",\n            direct_transport.dump().await.unwrap()\n        );\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\n            \"Direct transport enable trace event: {:#?}\",\n            direct_transport\n                .enable_trace_event(vec![TransportTraceEventType::Bwe])\n                .await\n                .unwrap()\n        );\n\n        let audio_level_observer = router\n            .create_audio_level_observer(AudioLevelObserverOptions::default())\n            .await\n            .unwrap();\n\n        println!(\"Audio level observer: {:#?}\", audio_level_observer.id());\n        println!(\n            \"Add producer to audio level observer: {:#?}\",\n            audio_level_observer\n                .add_producer(RtpObserverAddProducerOptions::new(producer.id()))\n                .await\n                .unwrap()\n        );\n\n        let active_speaker_observer = router\n            .create_active_speaker_observer(ActiveSpeakerObserverOptions::default())\n            .await\n            .unwrap();\n\n        println!(\n            \"Active speaker observer: {:#?}\",\n            active_speaker_observer.id()\n        );\n        println!(\n            \"Add producer to active speaker observer: {:#?}\",\n            active_speaker_observer\n                .add_producer(RtpObserverAddProducerOptions::new(producer.id()))\n                .await\n                .unwrap()\n        );\n\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\n            \"Remove producer from audio level observer: {:#?}\",\n            audio_level_observer\n                .remove_producer(producer.id())\n                .await\n                .unwrap()\n        );\n\n        let worker2 = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .unwrap();\n\n        let router2 = worker2\n            .create_router(RouterOptions::new(vec![RtpCodecCapability::Audio {\n                mime_type: MimeTypeAudio::Opus,\n                preferred_payload_type: None,\n                clock_rate: NonZeroU32::new(48000).unwrap(),\n                channels: NonZeroU8::new(2).unwrap(),\n                parameters: Default::default(),\n                rtcp_feedback: vec![],\n            }]))\n            .await\n            .unwrap();\n        println!(\"Second router created: {:?}\", router.id());\n\n        router\n            .pipe_producer_to_router(producer.id(), PipeToRouterOptions::new(router2.clone()))\n            .await\n            .unwrap();\n        println!(\"Piped producer to other router\",);\n\n        router\n            .pipe_data_producer_to_router(\n                data_producer.id(),\n                PipeToRouterOptions::new(router2.clone()),\n            )\n            .await\n            .unwrap();\n        println!(\"Piped data producer to other router\",);\n\n        println!(\"Router dump: {:#?}\", router.dump().await.unwrap());\n        println!(\"Router 2 dump: {:#?}\", router2.dump().await.unwrap());\n    });\n\n    // Just to give it time to finish everything\n    thread::sleep(std::time::Duration::from_millis(200));\n}\n"
  },
  {
    "path": "rust/tests/integration/webrtc_server.rs",
    "content": "use futures_lite::future;\nuse hash_hasher::HashedSet;\nuse mediasoup::webrtc_server::{WebRtcServerIpPort, WebRtcServerListenInfos, WebRtcServerOptions};\nuse mediasoup::worker::{ChannelMessageHandlers, CreateWebRtcServerError, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{AppData, ListenInfo, Protocol};\nuse portpicker::pick_unused_port;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\n\n#[derive(Debug, PartialEq)]\nstruct CustomAppData {\n    foo: u32,\n}\n\nasync fn init() -> (Worker, Worker) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    (\n        worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker\"),\n        worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker\"),\n    )\n}\n\n#[test]\nfn create_webrtc_server_succeeds() {\n    future::block_on(async move {\n        let (worker1, _worker2) = init().await;\n\n        let new_webrtc_server_count = Arc::new(AtomicUsize::new(0));\n\n        let port1 = pick_unused_port().unwrap();\n        let port2 = pick_unused_port().unwrap();\n        worker1\n            .on_new_webrtc_server({\n                let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count);\n\n                move |_webrtc_server| {\n                    new_webrtc_server_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        let webrtc_server = worker1\n            .create_webrtc_server({\n                let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port1),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let listen_infos = listen_infos.insert(ListenInfo {\n                    protocol: Protocol::Tcp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"foo.bar.org\".to_string()),\n                    expose_internal_ip: false,\n                    port: Some(port2),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let mut webrtc_server_options = WebRtcServerOptions::new(listen_infos);\n\n                webrtc_server_options.app_data = AppData::new(CustomAppData { foo: 123 });\n\n                webrtc_server_options\n            })\n            .await\n            .expect(\"Failed to create router\");\n\n        assert_eq!(new_webrtc_server_count.load(Ordering::SeqCst), 1);\n        assert!(!webrtc_server.closed());\n        assert_eq!(\n            webrtc_server.app_data().downcast_ref::<CustomAppData>(),\n            Some(&CustomAppData { foo: 123 }),\n        );\n\n        let worker_dump = worker1.dump().await.expect(\"Failed to dump worker\");\n\n        assert_eq!(worker_dump.router_ids, vec![]);\n        assert_eq!(worker_dump.webrtc_server_ids, vec![webrtc_server.id()]);\n        assert_eq!(\n            worker_dump.channel_message_handlers,\n            ChannelMessageHandlers {\n                channel_request_handlers: vec![webrtc_server.id().into()],\n                channel_notification_handlers: vec![]\n            }\n        );\n\n        let dump = webrtc_server\n            .dump()\n            .await\n            .expect(\"Failed to dump WebRTC server\");\n\n        assert_eq!(dump.id, webrtc_server.id());\n        assert_eq!(\n            dump.udp_sockets,\n            vec![WebRtcServerIpPort {\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                port: port1\n            }]\n        );\n        assert_eq!(\n            dump.tcp_servers,\n            vec![WebRtcServerIpPort {\n                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                port: port2\n            }]\n        );\n        assert_eq!(dump.webrtc_transport_ids, HashedSet::default());\n        assert_eq!(dump.local_ice_username_fragments, vec![]);\n        assert_eq!(dump.tuple_hashes, vec![]);\n    });\n}\n\n#[test]\nfn create_webrtc_server_without_specifying_port_succeeds() {\n    future::block_on(async move {\n        let (worker1, _worker2) = init().await;\n\n        let new_webrtc_server_count = Arc::new(AtomicUsize::new(0));\n\n        worker1\n            .on_new_webrtc_server({\n                let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count);\n\n                move |_webrtc_server| {\n                    new_webrtc_server_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        let webrtc_server = worker1\n            .create_webrtc_server({\n                let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let listen_infos = listen_infos.insert(ListenInfo {\n                    protocol: Protocol::Tcp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"1.2.3.4\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                });\n                let mut webrtc_server_options = WebRtcServerOptions::new(listen_infos);\n\n                webrtc_server_options.app_data = AppData::new(CustomAppData { foo: 123 });\n\n                webrtc_server_options\n            })\n            .await\n            .expect(\"Failed to create router\");\n\n        assert_eq!(new_webrtc_server_count.load(Ordering::SeqCst), 1);\n        assert!(!webrtc_server.closed());\n        assert_eq!(\n            webrtc_server.app_data().downcast_ref::<CustomAppData>(),\n            Some(&CustomAppData { foo: 123 }),\n        );\n\n        let worker_dump = worker1.dump().await.expect(\"Failed to dump worker\");\n\n        assert_eq!(worker_dump.router_ids, vec![]);\n        assert_eq!(worker_dump.webrtc_server_ids, vec![webrtc_server.id()]);\n\n        let dump = webrtc_server\n            .dump()\n            .await\n            .expect(\"Failed to dump WebRTC server\");\n\n        assert_eq!(dump.id, webrtc_server.id());\n\n        assert_eq!(dump.udp_sockets[0].ip, IpAddr::V4(Ipv4Addr::LOCALHOST));\n        assert_eq!(dump.tcp_servers[0].ip, IpAddr::V4(Ipv4Addr::LOCALHOST));\n\n        assert_eq!(dump.webrtc_transport_ids, HashedSet::default());\n        assert_eq!(dump.local_ice_username_fragments, vec![]);\n        assert_eq!(dump.tuple_hashes, vec![]);\n    });\n}\n\n#[test]\nfn unavailable_infos_fails() {\n    future::block_on(async move {\n        let (worker1, worker2) = init().await;\n\n        let new_webrtc_server_count = Arc::new(AtomicUsize::new(0));\n\n        let port1 = pick_unused_port().unwrap();\n        let port2 = pick_unused_port().unwrap();\n        worker1\n            .on_new_webrtc_server({\n                let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count);\n\n                move |_webrtc_server| {\n                    new_webrtc_server_count.fetch_add(1, Ordering::SeqCst);\n                }\n            })\n            .detach();\n\n        // Using an unavailable listen IP.\n        {\n            let create_result = worker1\n                .create_webrtc_server({\n                    let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(port1),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n                    let listen_infos = listen_infos.insert(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(port2),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n\n                    WebRtcServerOptions::new(listen_infos)\n                })\n                .await;\n\n            assert!(matches!(\n                create_result,\n                Err(CreateWebRtcServerError::Request(_))\n            ));\n        }\n\n        // Using the same UDP port twice.\n        {\n            let create_result = worker1\n                .create_webrtc_server({\n                    let listen_infos = WebRtcServerListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(port1),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n                    let listen_infos = listen_infos.insert(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),\n                        announced_address: Some(\"1.2.3.4\".to_string()),\n                        expose_internal_ip: false,\n                        port: Some(port1),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    });\n\n                    WebRtcServerOptions::new(listen_infos)\n                })\n                .await;\n\n            assert!(matches!(\n                create_result,\n                Err(CreateWebRtcServerError::Request(_))\n            ));\n        }\n\n        // Using the same UDP port in a second server.\n        {\n            let _webrtc_server = worker1\n                .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new(\n                    ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(port1),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    },\n                )))\n                .await\n                .expect(\"Failed to dump WebRTC server\");\n\n            let create_result = worker2\n                .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new(\n                    ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: Some(port1),\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    },\n                )))\n                .await;\n\n            assert!(matches!(\n                create_result,\n                Err(CreateWebRtcServerError::Request(_))\n            ));\n        }\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (worker1, _worker2) = init().await;\n\n        let port = pick_unused_port().unwrap();\n\n        let webrtc_server = worker1\n            .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new(\n                ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: None,\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                },\n            )))\n            .await\n            .expect(\"Failed to create WebRTC server\");\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        let _handler = webrtc_server.on_close(move || {\n            let _ = tx.send(());\n        });\n        drop(webrtc_server);\n\n        rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/webrtc_transport.rs",
    "content": "use futures_lite::future;\nuse hash_hasher::HashedSet;\nuse mediasoup::prelude::*;\nuse mediasoup::router::{Router, RouterOptions};\nuse mediasoup::transport::TransportTraceEventType;\nuse mediasoup::webrtc_transport::{\n    WebRtcTransportListenInfos, WebRtcTransportOptions, WebRtcTransportRemoteParameters,\n};\nuse mediasoup::worker::{RequestError, Worker, WorkerSettings};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::{\n    AppData, DtlsFingerprint, DtlsParameters, DtlsRole, DtlsState, IceCandidateType, IceRole,\n    IceState, ListenInfo, Protocol, SctpState,\n};\nuse mediasoup_types::rtp_parameters::{\n    MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters,\n};\nuse mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters};\nuse portpicker::pick_unused_port;\nuse std::convert::TryInto;\nuse std::env;\nuse std::net::{IpAddr, Ipv4Addr};\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::sync::atomic::{AtomicUsize, Ordering};\nuse std::sync::Arc;\n\nstruct CustomAppData {\n    foo: &'static str,\n}\n\nfn media_codecs() -> Vec<RtpCodecCapability> {\n    vec![\n        RtpCodecCapability::Audio {\n            mime_type: MimeTypeAudio::Opus,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(48000).unwrap(),\n            channels: NonZeroU8::new(2).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"useinbandfec\", 1_u32.into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::Vp8,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::default(),\n            rtcp_feedback: vec![],\n        },\n        RtpCodecCapability::Video {\n            mime_type: MimeTypeVideo::H264,\n            preferred_payload_type: None,\n            clock_rate: NonZeroU32::new(90000).unwrap(),\n            parameters: RtpCodecParametersParameters::from([\n                (\"level-asymmetry-allowed\", 1_u32.into()),\n                (\"packetization-mode\", 1_u32.into()),\n                (\"profile-level-id\", \"4d0032\".into()),\n                (\"foo\", \"bar\".into()),\n            ]),\n            rtcp_feedback: vec![],\n        },\n    ]\n}\n\nasync fn init() -> (Worker, Router) {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    let worker_manager = WorkerManager::new();\n\n    let worker = worker_manager\n        .create_worker(WorkerSettings::default())\n        .await\n        .expect(\"Failed to create worker\");\n\n    let router = worker\n        .create_router(RouterOptions::new(media_codecs()))\n        .await\n        .expect(\"Failed to create router\");\n\n    (worker, router)\n}\n\n#[test]\nfn create_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        {\n            let transport = router\n                .create_webrtc_transport(WebRtcTransportOptions::new(\n                    WebRtcTransportListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: Some(\"9.9.9.1\".to_string()),\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    }),\n                ))\n                .await\n                .expect(\"Failed to create WebRTC transport\");\n\n            let router_dump = router.dump().await.expect(\"Failed to dump router\");\n            assert_eq!(router_dump.transport_ids, {\n                let mut set = HashedSet::default();\n                set.insert(transport.id());\n                set\n            });\n        }\n\n        {\n            let new_transports_count = Arc::new(AtomicUsize::new(0));\n\n            router\n                .on_new_transport({\n                    let new_transports_count = Arc::clone(&new_transports_count);\n\n                    move |_transport| {\n                        new_transports_count.fetch_add(1, Ordering::SeqCst);\n                    }\n                })\n                .detach();\n\n            let transport1 = router\n                .create_webrtc_transport({\n                    let mut webrtc_transport_options = WebRtcTransportOptions::new(\n                        vec![\n                            ListenInfo {\n                                protocol: Protocol::Udp,\n                                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                                announced_address: Some(\"9.9.9.1\".to_string()),\n                                expose_internal_ip: true,\n                                port: None,\n                                port_range: None,\n                                flags: None,\n                                send_buffer_size: None,\n                                recv_buffer_size: None,\n                            },\n                            ListenInfo {\n                                protocol: Protocol::Udp,\n                                ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED),\n                                announced_address: Some(\"foo1.bar.org\".to_string()),\n                                expose_internal_ip: false,\n                                port: None,\n                                port_range: None,\n                                flags: None,\n                                send_buffer_size: None,\n                                recv_buffer_size: None,\n                            },\n                            ListenInfo {\n                                protocol: Protocol::Udp,\n                                ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                                announced_address: None,\n                                expose_internal_ip: false,\n                                port: None,\n                                port_range: None,\n                                flags: None,\n                                send_buffer_size: None,\n                                recv_buffer_size: None,\n                            },\n                        ]\n                        .try_into()\n                        .unwrap(),\n                    );\n                    webrtc_transport_options.enable_sctp = true;\n                    webrtc_transport_options.num_sctp_streams = NumSctpStreams {\n                        os: 2048,\n                        mis: 2048,\n                    };\n                    webrtc_transport_options.max_sctp_message_size = 1000000;\n                    webrtc_transport_options.app_data = AppData::new(CustomAppData { foo: \"bar\" });\n\n                    webrtc_transport_options\n                })\n                .await\n                .expect(\"Failed to create WebRTC transport\");\n\n            assert_eq!(new_transports_count.load(Ordering::SeqCst), 1);\n            assert_eq!(\n                transport1\n                    .app_data()\n                    .downcast_ref::<CustomAppData>()\n                    .unwrap()\n                    .foo,\n                \"bar\",\n            );\n            assert_eq!(transport1.ice_role(), IceRole::Controlled);\n            assert_eq!(transport1.ice_parameters().ice_lite, Some(true));\n            assert_eq!(\n                transport1.sctp_parameters(),\n                Some(SctpParameters {\n                    port: 5000,\n                    os: 2048,\n                    mis: 2048,\n                    max_message_size: 1000000,\n                }),\n            );\n            {\n                let ice_candidates = transport1.ice_candidates();\n                assert_eq!(ice_candidates.len(), 4);\n                assert_eq!(ice_candidates[0].address, \"9.9.9.1\");\n                assert_eq!(ice_candidates[0].protocol, Protocol::Udp);\n                assert_eq!(ice_candidates[0].r#type, IceCandidateType::Host);\n                assert_eq!(ice_candidates[0].tcp_type, None);\n                assert_eq!(ice_candidates[1].address, \"127.0.0.1\");\n                assert_eq!(ice_candidates[1].protocol, Protocol::Udp);\n                assert_eq!(ice_candidates[1].r#type, IceCandidateType::Host);\n                assert_eq!(ice_candidates[1].tcp_type, None);\n                assert_eq!(ice_candidates[2].address, \"foo1.bar.org\");\n                assert_eq!(ice_candidates[2].protocol, Protocol::Udp);\n                assert_eq!(ice_candidates[2].r#type, IceCandidateType::Host);\n                assert_eq!(ice_candidates[2].tcp_type, None);\n                assert_eq!(ice_candidates[3].address, \"127.0.0.1\");\n                assert_eq!(ice_candidates[3].protocol, Protocol::Udp);\n                assert_eq!(ice_candidates[3].r#type, IceCandidateType::Host);\n                assert_eq!(ice_candidates[3].tcp_type, None);\n                assert_eq!(ice_candidates[3].address, \"127.0.0.1\");\n                assert_eq!(ice_candidates[3].protocol, Protocol::Udp);\n                assert_eq!(ice_candidates[3].r#type, IceCandidateType::Host);\n                assert_eq!(ice_candidates[3].tcp_type, None);\n                assert!(ice_candidates[0].priority > ice_candidates[1].priority);\n                assert!(ice_candidates[1].priority > ice_candidates[2].priority);\n                assert!(ice_candidates[2].priority > ice_candidates[3].priority);\n            }\n\n            assert_eq!(transport1.ice_state(), IceState::New);\n            assert_eq!(transport1.ice_selected_tuple(), None);\n            assert_eq!(transport1.dtls_parameters().role, DtlsRole::Auto);\n            assert_eq!(transport1.dtls_state(), DtlsState::New);\n            assert_eq!(transport1.sctp_state(), Some(SctpState::New));\n\n            {\n                let transport_dump = transport1\n                    .dump()\n                    .await\n                    .expect(\"Failed to dump WebRTC transport\");\n\n                assert_eq!(transport_dump.id, transport1.id());\n                assert!(!transport_dump.direct);\n                assert_eq!(transport_dump.producer_ids, vec![]);\n                assert_eq!(transport_dump.consumer_ids, vec![]);\n                assert_eq!(transport_dump.ice_role, transport1.ice_role());\n                assert_eq!(&transport_dump.ice_parameters, transport1.ice_parameters());\n                assert_eq!(&transport_dump.ice_candidates, transport1.ice_candidates());\n                assert_eq!(transport_dump.ice_state, transport1.ice_state());\n                assert_eq!(\n                    transport_dump.ice_selected_tuple,\n                    transport1.ice_selected_tuple(),\n                );\n                assert_eq!(transport_dump.dtls_parameters, transport1.dtls_parameters());\n                assert_eq!(transport_dump.dtls_state, transport1.dtls_state());\n                assert_eq!(transport_dump.sctp_parameters, transport1.sctp_parameters());\n                assert_eq!(transport_dump.sctp_state, transport1.sctp_state());\n            }\n        }\n    });\n}\n\n#[test]\nfn create_with_fixed_port_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let port = pick_unused_port().unwrap();\n\n        let transport = router\n            .create_webrtc_transport({\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: Some(port),\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }))\n            })\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        assert_eq!(transport.ice_candidates().first().unwrap().port, port);\n    });\n}\n\n#[test]\nfn create_with_port_range_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n        let port_range = 11111..=11112;\n\n        let transport1 = router\n            .create_webrtc_transport({\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: Some(port_range.clone()),\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }))\n            })\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let port1 = transport1.ice_candidates().first().unwrap().port;\n        assert!(port1 >= *port_range.start() && port1 <= *port_range.end());\n\n        let transport2 = router\n            .create_webrtc_transport({\n                WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: Some(port_range.clone()),\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }))\n            })\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let port2 = transport2.ice_candidates().first().unwrap().port;\n        assert!(port2 >= *port_range.start() && port2 <= *port_range.end());\n\n        assert!(matches!(\n            router\n                .create_webrtc_transport({\n                    WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                        announced_address: Some(\"9.9.9.1\".to_string()),\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: Some(port_range.clone()),\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    }))\n                })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n    });\n}\n\n#[test]\nfn weak() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let weak_transport = transport.downgrade();\n\n        assert!(weak_transport.upgrade().is_some());\n\n        drop(transport);\n\n        assert!(weak_transport.upgrade().is_none());\n    });\n}\n\n#[test]\nfn create_non_bindable_ip() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        assert!(matches!(\n            router\n                .create_webrtc_transport(WebRtcTransportOptions::new(\n                    WebRtcTransportListenInfos::new(ListenInfo {\n                        protocol: Protocol::Udp,\n                        ip: \"8.8.8.8\".parse().unwrap(),\n                        announced_address: None,\n                        expose_internal_ip: false,\n                        port: None,\n                        port_range: None,\n                        flags: None,\n                        send_buffer_size: None,\n                        recv_buffer_size: None,\n                    },)\n                ))\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n    });\n}\n\n#[test]\nfn get_stats_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let stats = transport\n            .get_stats()\n            .await\n            .expect(\"Failed to get stats on WebRTC transport\");\n\n        assert_eq!(stats.len(), 1);\n        assert_eq!(stats[0].transport_id, transport.id());\n        assert_eq!(stats[0].ice_role, IceRole::Controlled);\n        assert_eq!(stats[0].ice_state, IceState::New);\n        assert_eq!(stats[0].dtls_state, DtlsState::New);\n        assert_eq!(stats[0].sctp_state, None);\n        assert_eq!(stats[0].bytes_received, 0);\n        assert_eq!(stats[0].recv_bitrate, 0);\n        assert_eq!(stats[0].bytes_sent, 0);\n        assert_eq!(stats[0].send_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_received, 0);\n        assert_eq!(stats[0].rtp_recv_bitrate, 0);\n        assert_eq!(stats[0].rtp_bytes_sent, 0);\n        assert_eq!(stats[0].rtp_send_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_received, 0);\n        assert_eq!(stats[0].rtx_recv_bitrate, 0);\n        assert_eq!(stats[0].rtx_bytes_sent, 0);\n        assert_eq!(stats[0].rtx_send_bitrate, 0);\n        assert_eq!(stats[0].probation_bytes_sent, 0);\n        assert_eq!(stats[0].probation_send_bitrate, 0);\n        assert_eq!(stats[0].ice_selected_tuple, None);\n        assert_eq!(stats[0].max_incoming_bitrate, None);\n        assert_eq!(stats[0].rtp_packet_loss_received, None);\n        assert_eq!(stats[0].rtp_packet_loss_sent, None);\n    });\n}\n\n#[test]\nfn connect_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let dtls_parameters = DtlsParameters {\n            role: DtlsRole::Client,\n            fingerprints: vec![DtlsFingerprint::Sha256 {\n                value: [\n                    0x82, 0x5A, 0x68, 0x3D, 0x36, 0xC3, 0x0A, 0xDE, 0xAF, 0xE7, 0x32, 0x43, 0xD2,\n                    0x88, 0x83, 0x57, 0xAC, 0x2D, 0x65, 0xE5, 0x80, 0xC4, 0xB6, 0xFB, 0xAF, 0x1A,\n                    0xA0, 0x21, 0x9F, 0x6D, 0x0C, 0xAD,\n                ],\n            }],\n        };\n\n        transport\n            .connect(WebRtcTransportRemoteParameters {\n                dtls_parameters: dtls_parameters.clone(),\n            })\n            .await\n            .expect(\"Failed to establish WebRTC transport connection\");\n\n        // Must fail if connected.\n        assert!(matches!(\n            transport\n                .connect(WebRtcTransportRemoteParameters { dtls_parameters })\n                .await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        assert_eq!(transport.dtls_parameters().role, DtlsRole::Server);\n    });\n}\n\n#[test]\nfn set_max_incoming_bitrate_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        transport\n            .set_max_incoming_bitrate(1000000)\n            .await\n            .expect(\"Failed to set max incoming bitrate on WebRTC transport\");\n\n        // Remove limit.\n        transport\n            .set_max_incoming_bitrate(0)\n            .await\n            .expect(\"Failed to remove limit in max incoming bitrate on WebRTC transport\");\n    });\n}\n\n#[test]\nfn set_max_outgoing_bitrate_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        transport\n            .set_max_outgoing_bitrate(2000000)\n            .await\n            .expect(\"Failed to set max outgoing bitrate on WebRTC transport\");\n\n        // Remove limit.\n        transport\n            .set_max_outgoing_bitrate(0)\n            .await\n            .expect(\"Failed to remove limit in max outgoing bitrate on WebRTC transport\");\n    });\n}\n\n#[test]\nfn set_min_outgoing_bitrate_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        transport\n            .set_min_outgoing_bitrate(100000)\n            .await\n            .expect(\"Failed to set min outgoing bitrate on WebRTC transport\");\n\n        // Remove limit.\n        transport\n            .set_min_outgoing_bitrate(0)\n            .await\n            .expect(\"Failed to remove limit in min outgoing bitrate on WebRTC transport\");\n    });\n}\n\n#[test]\nfn set_max_outgoing_bitrate_fails_if_value_is_lower_than_current_min_limit() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        transport\n            .set_min_outgoing_bitrate(3000000)\n            .await\n            .expect(\"Failed to set min outgoing bitrate on WebRTC transport\");\n\n        assert!(matches!(\n            transport.set_max_outgoing_bitrate(2000000).await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        // Remove limit.\n        transport\n            .set_min_outgoing_bitrate(0)\n            .await\n            .expect(\"Failed to remove limit in min outgoing bitrate on WebRTC transport\");\n    });\n}\n\n#[test]\nfn set_min_outgoing_bitrate_fails_if_value_is_higher_than_current_max_limit() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        transport\n            .set_max_outgoing_bitrate(2000000)\n            .await\n            .expect(\"Failed to set max outgoing bitrate on WebRTC transport\");\n\n        assert!(matches!(\n            transport.set_min_outgoing_bitrate(3000000).await,\n            Err(RequestError::Response { .. }),\n        ));\n\n        // Remove limit.\n        transport\n            .set_max_outgoing_bitrate(0)\n            .await\n            .expect(\"Failed to remove limit in max outgoing bitrate on WebRTC transport\");\n    });\n}\n\n#[test]\nfn restart_ice_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let previous_ice_username_fragment = transport.ice_parameters().username_fragment.clone();\n        let previous_ice_password = transport.ice_parameters().password.clone();\n\n        let ice_parameters = transport\n            .restart_ice()\n            .await\n            .expect(\"Failed to initiate ICE Restart on WebRTC transport\");\n\n        assert_ne!(\n            ice_parameters.username_fragment,\n            previous_ice_username_fragment\n        );\n        assert_ne!(ice_parameters.password, previous_ice_password);\n    });\n}\n\n#[test]\nfn enable_trace_event_succeeds() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        {\n            transport\n                .enable_trace_event(vec![TransportTraceEventType::Probation])\n                .await\n                .expect(\"Failed to enable trace event\");\n\n            let dump = transport\n                .dump()\n                .await\n                .expect(\"Failed to dump WebRTC transport\");\n\n            assert_eq!(\n                dump.trace_event_types,\n                vec![TransportTraceEventType::Probation]\n            );\n        }\n\n        {\n            transport\n                .enable_trace_event(vec![\n                    TransportTraceEventType::Probation,\n                    TransportTraceEventType::Bwe,\n                ])\n                .await\n                .expect(\"Failed to enable trace event\");\n\n            let dump = transport\n                .dump()\n                .await\n                .expect(\"Failed to dump WebRTC transport\");\n\n            assert_eq!(\n                dump.trace_event_types,\n                vec![\n                    TransportTraceEventType::Probation,\n                    TransportTraceEventType::Bwe,\n                ]\n            );\n        }\n\n        {\n            transport\n                .enable_trace_event(vec![])\n                .await\n                .expect(\"Failed to enable trace event\");\n\n            let dump = transport\n                .dump()\n                .await\n                .expect(\"Failed to dump WebRTC transport\");\n\n            assert_eq!(dump.trace_event_types, vec![]);\n        }\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let (_worker, router) = init().await;\n\n        let transport = router\n            .create_webrtc_transport(WebRtcTransportOptions::new(\n                WebRtcTransportListenInfos::new(ListenInfo {\n                    protocol: Protocol::Udp,\n                    ip: IpAddr::V4(Ipv4Addr::LOCALHOST),\n                    announced_address: Some(\"9.9.9.1\".to_string()),\n                    expose_internal_ip: false,\n                    port: None,\n                    port_range: None,\n                    flags: None,\n                    send_buffer_size: None,\n                    recv_buffer_size: None,\n                }),\n            ))\n            .await\n            .expect(\"Failed to create WebRTC transport\");\n\n        let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>();\n        let _handler = transport.on_close(Box::new(move || {\n            let _ = close_tx.send(());\n        }));\n\n        drop(transport);\n\n        close_rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/tests/integration/worker.rs",
    "content": "use futures_lite::future;\nuse mediasoup::worker::{\n    ChannelMessageHandlers, WorkerDtlsFiles, WorkerLogLevel, WorkerLogTag, WorkerSettings,\n    WorkerUpdateSettings,\n};\nuse mediasoup::worker_manager::WorkerManager;\nuse mediasoup_types::data_structures::AppData;\nuse std::{env, io};\n\nasync fn init() -> WorkerManager {\n    {\n        let mut builder = env_logger::builder();\n        if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() {\n            builder.filter_level(log::LevelFilter::Off);\n        }\n        let _ = builder.is_test(true).try_init();\n    }\n\n    WorkerManager::new()\n}\n\n#[test]\nfn create_worker_succeeds() {\n    future::block_on(async move {\n        let worker_manager = init().await;\n\n        let worker = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker with default settings\");\n\n        drop(worker);\n\n        #[derive(Debug, PartialEq)]\n        struct CustomAppData {\n            bar: u16,\n        }\n\n        let worker = worker_manager\n            .create_worker({\n                let mut settings = WorkerSettings::default();\n\n                settings.log_level = WorkerLogLevel::Debug;\n                settings.log_tags = vec![WorkerLogTag::Info];\n                settings.rtc_port_range = 0..=9999;\n                settings.dtls_files = Some(WorkerDtlsFiles {\n                    certificate: \"tests/integration/data/dtls-cert.pem\".into(),\n                    private_key: \"tests/integration/data/dtls-key.pem\".into(),\n                });\n                settings.libwebrtc_field_trials =\n                    Some(\"WebRTC-Bwe-AlrLimitedBackoff/Disabled/\".to_string());\n                settings.app_data = AppData::new(CustomAppData { bar: 456 });\n\n                settings\n            })\n            .await\n            .expect(\"Failed to create worker with custom settings\");\n\n        assert!(!worker.closed());\n        assert_eq!(\n            worker.app_data().downcast_ref::<CustomAppData>(),\n            Some(&CustomAppData { bar: 456 }),\n        );\n\n        drop(worker);\n    });\n}\n\n#[test]\nfn create_worker_wrong_settings() {\n    future::block_on(async move {\n        let worker_manager = init().await;\n\n        {\n            let worker_result = worker_manager\n                .create_worker({\n                    let mut settings = WorkerSettings::default();\n\n                    // Intentionally incorrect range\n                    #[allow(clippy::reversed_empty_ranges)]\n                    {\n                        settings.rtc_port_range = 1000..=999;\n                    }\n\n                    settings\n                })\n                .await;\n\n            assert!(matches!(worker_result, Err(io::Error { .. })));\n        }\n\n        {\n            let worker_result = worker_manager\n                .create_worker({\n                    let mut settings = WorkerSettings::default();\n\n                    settings.dtls_files = Some(WorkerDtlsFiles {\n                        certificate: \"/notfound/cert.pem\".into(),\n                        private_key: \"/notfound/priv.pem\".into(),\n                    });\n\n                    settings\n                })\n                .await;\n\n            assert!(matches!(worker_result, Err(io::Error { .. })));\n        }\n    });\n}\n\n#[test]\nfn update_settings_succeeds() {\n    future::block_on(async move {\n        let worker_manager = init().await;\n\n        let worker = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker with default settings\");\n\n        worker\n            .update_settings({\n                let mut settings = WorkerUpdateSettings::default();\n\n                settings.log_level = Some(WorkerLogLevel::Debug);\n                settings.log_tags = Some(vec![WorkerLogTag::Ice]);\n\n                settings\n            })\n            .await\n            .expect(\"Failed to update worker settings\");\n    });\n}\n\n#[test]\nfn dump_succeeds() {\n    future::block_on(async move {\n        let worker_manager = init().await;\n\n        let worker = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker with default settings\");\n\n        let dump = worker.dump().await.expect(\"Failed to dump worker\");\n\n        assert_eq!(dump.router_ids, vec![]);\n        assert_eq!(dump.webrtc_server_ids, vec![]);\n        assert_eq!(\n            dump.channel_message_handlers,\n            ChannelMessageHandlers {\n                channel_request_handlers: vec![],\n                channel_notification_handlers: vec![]\n            }\n        );\n    });\n}\n\n#[test]\nfn close_event() {\n    future::block_on(async move {\n        let worker_manager = init().await;\n\n        let worker = worker_manager\n            .create_worker(WorkerSettings::default())\n            .await\n            .expect(\"Failed to create worker with default settings\");\n\n        let (mut tx, rx) = async_oneshot::oneshot::<()>();\n        let _handler = worker.on_close(move || {\n            let _ = tx.send(());\n        });\n        drop(worker);\n\n        rx.await.expect(\"Failed to receive close event\");\n    });\n}\n"
  },
  {
    "path": "rust/types/CHANGELOG.md",
    "content": "# Changelog\n\n### NEXT\n\n### 0.3.0\n\n- `RtpHeaderExtensionUri`: Add `SsrcAudioLevel`, `AbsSendTime`, `TransportWideCcDraft01`, `DependencyDescriptor`, `AbsCaptureTime`, `PlayoutDelay` and `MediasoupPacketId` variants. Rename `AudioLevel` to `SsrcAudioLevel` (PR #1631).\n- `RtpParameters`: Add optional `msid` field (WebRTC MediaStream Identification, RFC 8830) (PR #1634).\n\n### 0.2.1\n\n- Initial release as a standalone crate extracted from `mediasoup` (PR #1572).\n"
  },
  {
    "path": "rust/types/Cargo.toml",
    "content": "[package]\nname = \"mediasoup-types\"\nversion = \"0.3.0\"\ndescription = \"Type definitions and shared data structures for the mediasoup crate\"\nauthors = [\n    \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n    \"José Luis Millán <jmillan@aliax.net>\",\n    \"Iñaki Baz Castillo <ibc@aliax.net>\"\n]\nedition = \"2021\"\nlicense = \"ISC\"\ndocumentation = \"https://docs.rs/mediasoup-types\"\nrepository = \"https://github.com/versatica/mediasoup\"\nreadme = \"README.md\"\n\n[dependencies]\nonce_cell = \"1.16.0\"\nserde_json = \"1.0.87\"\nthiserror = \"2.0.12\"\n\n[dependencies.serde]\nfeatures = [\"derive\"]\nversion = \"1.0.190\"\n\n[dependencies.regex]\ndefault-features = false\nfeatures = [\"std\", \"perf\"]\nversion = \"1.6.0\"\n"
  },
  {
    "path": "rust/types/README.md",
    "content": "# mediasoup-types v3\n\nType definitions and shared data structures for the [mediasoup](https://docs.rs/mediasoup) crate.\n\n## Overview\n\n`mediasoup-types` provides core types, enums, and data structures used throughout the mediasoup Rust crates. This crate is intended for use by both the main mediasoup implementation and any libraries, tools, or applications that interact with mediasoup in Rust.\n\n## Usage\n\nAdd `mediasoup-types` to your `Cargo.toml`:\n\n```toml\n[dependencies]\nmediasoup-types = \"X.Y.Z\"\n```\n\nImport and use the types in your Rust code:\n\n```rust\nuse mediasoup_types::{RtpCodecParameters, RtpCapabilities, MediaKind};\n```\n\n## Documentation\n\n- [API Documentation](https://docs.rs/mediasoup-types)\n- [mediasoup Rust repository](https://github.com/versatica/mediasoup)\n\n## Sponsor\n\nYou can support mediasoup by [sponsoring](https://mediasoup.org/sponsor) it. Thanks!\n\n## License\n\n[ISC](./LICENSE)\n"
  },
  {
    "path": "rust/types/src/data_structures/tests.rs",
    "content": "use super::*;\n\n#[test]\nfn dtls_fingerprint() {\n    {\n        let dtls_fingerprints = &[\n            r#\"{\"algorithm\":\"sha-1\",\"value\":\"0D:88:5B:EF:B9:86:F9:66:67:75:7A:C1:7A:78:34:E4:88:DC:44:67\"}\"#,\n            r#\"{\"algorithm\":\"sha-224\",\"value\":\"6E:0C:C7:23:DF:36:E1:C7:46:AB:D7:B1:CE:DD:97:C3:C1:17:25:D6:26:0A:8A:B4:50:F1:3E:BC\"}\"#,\n            r#\"{\"algorithm\":\"sha-256\",\"value\":\"7A:27:46:F0:7B:09:28:F0:10:E2:EC:84:60:B5:87:9A:D9:C8:8B:F3:6C:C5:5D:C3:F3:BA:2C:5B:4F:8A:3A:E3\"}\"#,\n            r#\"{\"algorithm\":\"sha-384\",\"value\":\"D0:B7:F7:3C:71:9F:F4:A1:48:E1:9B:13:25:59:A4:7D:06:BF:E1:1B:DC:0B:8A:8E:45:09:01:22:7E:81:68:EC:DD:B8:DD:CA:1F:F3:F2:E8:15:A5:3C:23:CF:F7:B6:38\"}\"#,\n            r#\"{\"algorithm\":\"sha-512\",\"value\":\"36:8B:9B:CA:2B:01:2B:33:FD:06:95:F2:CC:28:56:69:5B:DD:38:5E:88:32:9A:72:F7:B1:5D:87:9E:64:97:0B:66:A1:C7:6C:BE:4D:CD:83:90:04:AE:20:6C:6D:5F:F0:BD:4C:D9:DD:6E:8A:19:C1:C9:F6:C2:46:C8:08:94:39\"}\"#,\n        ];\n\n        for dtls_fingerprint_str in dtls_fingerprints {\n            let dtls_fingerprint =\n                serde_json::from_str::<DtlsFingerprint>(dtls_fingerprint_str).unwrap();\n            assert_eq!(\n                dtls_fingerprint_str,\n                &serde_json::to_string(&dtls_fingerprint).unwrap()\n            );\n        }\n    }\n\n    {\n        let bad_dtls_fingerprints = &[\n            r#\"{\"algorithm\":\"sha-1\",\"value\":\"0D:88:5B:EF:B9:86:F9:66:67::44:67\"}\"#,\n            r#\"{\"algorithm\":\"sha-200\",\"value\":\"6E:0C:C7:23:DF:36:E1:C7:46:AB:D7:B1:CE:DD:97:C3:C1:17:25:D6:26:0A:8A:B4:50:F1:3E:BC\"}\"#,\n        ];\n\n        for dtls_fingerprint_str in bad_dtls_fingerprints {\n            assert!(serde_json::from_str::<DtlsFingerprint>(dtls_fingerprint_str).is_err());\n        }\n    }\n}\n"
  },
  {
    "path": "rust/types/src/data_structures.rs",
    "content": "//! Miscellaneous data structures.\n\n#[cfg(test)]\nmod tests;\n\nuse serde::de::{MapAccess, Visitor};\nuse serde::ser::SerializeStruct;\nuse serde::{de, Deserialize, Deserializer, Serialize, Serializer};\nuse std::any::Any;\nuse std::borrow::Cow;\nuse std::fmt;\nuse std::net::IpAddr;\nuse std::ops::{Deref, DerefMut, RangeInclusive};\nuse std::sync::Arc;\n\n/// Container for arbitrary data attached to mediasoup entities.\n#[derive(Debug, Clone)]\npub struct AppData(Arc<dyn Any + Send + Sync>);\n\nimpl Default for AppData {\n    fn default() -> Self {\n        Self::new(())\n    }\n}\n\nimpl Deref for AppData {\n    type Target = Arc<dyn Any + Send + Sync>;\n\n    fn deref(&self) -> &Self::Target {\n        &self.0\n    }\n}\n\nimpl DerefMut for AppData {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.0\n    }\n}\n\nimpl AppData {\n    /// Construct app data from almost anything\n    pub fn new<T: Any + Send + Sync>(app_data: T) -> Self {\n        Self(Arc::new(app_data))\n    }\n}\n\n/// Listening protocol, IP and port for [`WebRtcServer`](https://docs.rs/mediasoup/latest/mediasoup/webrtc_server/struct.WebRtcServer.html) to listen on.\n///\n/// # Notes on usage\n/// If you use \"0.0.0.0\" or \"::\" as ip value, then you need to also provide\n/// `announced_address`.\n#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct ListenInfo {\n    /// Network protocol.\n    pub protocol: Protocol,\n    /// Listening IPv4 or IPv6.\n    pub ip: IpAddr,\n    /// Announced IPv4, IPv6 or hostname (useful when running mediasoup behind\n    /// NAT with private IP).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub announced_address: Option<String>,\n    /// In transports with ICE candidates, this field determines whether to also\n    /// expose an ICE candidate with the IP of the |ip| field when\n    /// |announcedAddress| is given.\n    pub expose_internal_ip: bool,\n    /// Listening port.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub port: Option<u16>,\n    /// Listening port range. If given then |port| will be ignored.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub port_range: Option<RangeInclusive<u16>>,\n    /// Socket flags.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub flags: Option<SocketFlags>,\n    /// Send buffer size (bytes).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub send_buffer_size: Option<u32>,\n    /// Recv buffer size (bytes).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub recv_buffer_size: Option<u32>,\n}\n\n/// UDP/TCP socket flags.\n#[derive(\n    Default, Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\npub struct SocketFlags {\n    /// Disable dual-stack support so only IPv6 is used (only if ip is IPv6).\n    /// Defaults to false.\n    pub ipv6_only: bool,\n    /// Make different transports bind to the same ip and port (only for UDP).\n    /// Useful for multicast scenarios with plain transport. Use with caution.\n    /// Defaults to false.\n    pub udp_reuse_port: bool,\n}\n\n/// ICE role.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum IceRole {\n    /// The transport is the controlled agent.\n    Controlled,\n    /// The transport is the controlling agent.\n    Controlling,\n}\n\n/// ICE parameters.\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct IceParameters {\n    /// ICE username fragment.\n    pub username_fragment: String,\n    /// ICE password.\n    pub password: String,\n    /// ICE Lite.\n    pub ice_lite: Option<bool>,\n}\n\n/// ICE candidate type.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum IceCandidateType {\n    /// The candidate is a host candidate, whose IP address as specified in the\n    /// [`IceCandidate::address`] property is in fact the true address of the remote peer.\n    Host,\n    /// The candidate is a server reflexive candidate; the [`IceCandidate::address`] indicates an\n    /// intermediary address assigned by the STUN server to represent the candidate's peer\n    /// anonymously.\n    Srflx,\n    /// The candidate is a peer reflexive candidate; the [`IceCandidate::address`] is an intermediary\n    /// address assigned by the STUN server to represent the candidate's peer anonymously.\n    Prflx,\n    /// The candidate is a relay candidate, obtained from a TURN server. The relay candidate's IP\n    /// address is an address the [TURN](https://developer.mozilla.org/en-US/docs/Glossary/TURN)\n    /// server uses to forward the media between the two peers.\n    Relay,\n}\n\n/// ICE candidate TCP type (always `Passive`).\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum IceCandidateTcpType {\n    /// Passive.\n    Passive,\n}\n\n/// Transport protocol.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum Protocol {\n    /// TCP.\n    Tcp,\n    /// UDP.\n    Udp,\n}\n\n/// ICE candidate\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct IceCandidate {\n    /// Unique identifier that allows ICE to correlate candidates that appear on multiple\n    /// transports.\n    pub foundation: String,\n    /// The assigned priority of the candidate.\n    pub priority: u32,\n    /// The IP address or hostname of the candidate.\n    pub address: String,\n    /// The protocol of the candidate.\n    pub protocol: Protocol,\n    /// The port for the candidate.\n    pub port: u16,\n    /// The type of candidate (always `Host`).\n    pub r#type: IceCandidateType,\n    /// The type of TCP candidate (always `Passive`).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub tcp_type: Option<IceCandidateTcpType>,\n}\n\n/// ICE state.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum IceState {\n    /// No ICE Binding Requests have been received yet.\n    New,\n    /// Valid ICE Binding Request have been received, but none with USE-CANDIDATE attribute.\n    /// Outgoing media is allowed.\n    Connected,\n    /// ICE Binding Request with USE_CANDIDATE attribute has been received. Media in both directions\n    /// is now allowed.\n    Completed,\n    /// ICE was `Connected` or `Completed` but it has suddenly failed (this can just happen if the\n    /// selected tuple has `Tcp` protocol).\n    Disconnected,\n}\n\n/// Tuple of local IP/port/protocol + optional remote IP/port.\n///\n/// # Notes on usage\n/// Both `remote_ip` and `remote_port` are unset until the media address of the remote endpoint is\n/// known, which happens after calling `transport.connect()` in `PlainTransport` and\n/// `PipeTransport`, or via dynamic detection as it happens in `WebRtcTransport` (in which the\n/// remote media address is detected by ICE means), or in `PlainTransport` (when using `comedia`\n/// mode).\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum TransportTuple {\n    /// Transport tuple with remote endpoint info.\n    #[serde(rename_all = \"camelCase\")]\n    WithRemote {\n        /// Local IP address or hostname.\n        local_address: String,\n        /// Local port.\n        local_port: u16,\n        /// Remote IP address.\n        remote_ip: IpAddr,\n        /// Remote port.\n        remote_port: u16,\n        /// Protocol\n        protocol: Protocol,\n    },\n    /// Transport tuple without remote endpoint info.\n    #[serde(rename_all = \"camelCase\")]\n    LocalOnly {\n        /// Local IP address or hostname.\n        local_address: String,\n        /// Local port.\n        local_port: u16,\n        /// Protocol\n        protocol: Protocol,\n    },\n}\n\nimpl TransportTuple {\n    /// Local IP address or hostname.\n    pub fn local_address(&self) -> &String {\n        let (Self::WithRemote { local_address, .. } | Self::LocalOnly { local_address, .. }) = self;\n        local_address\n    }\n\n    /// Local port.\n    pub fn local_port(&self) -> u16 {\n        let (Self::WithRemote { local_port, .. } | Self::LocalOnly { local_port, .. }) = self;\n        *local_port\n    }\n\n    /// Protocol.\n    pub fn protocol(&self) -> Protocol {\n        let (Self::WithRemote { protocol, .. } | Self::LocalOnly { protocol, .. }) = self;\n        *protocol\n    }\n\n    /// Remote IP address.\n    pub fn remote_ip(&self) -> Option<IpAddr> {\n        if let TransportTuple::WithRemote { remote_ip, .. } = self {\n            Some(*remote_ip)\n        } else {\n            None\n        }\n    }\n\n    /// Remote port.\n    pub fn remote_port(&self) -> Option<u16> {\n        if let TransportTuple::WithRemote { remote_port, .. } = self {\n            Some(*remote_port)\n        } else {\n            None\n        }\n    }\n}\n\n/// DTLS state.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum DtlsState {\n    /// DTLS procedures not yet initiated.\n    New,\n    /// DTLS connecting.\n    Connecting,\n    /// DTLS successfully connected (SRTP keys already extracted).\n    Connected,\n    /// DTLS connection failed.\n    Failed,\n    /// DTLS state when the `transport` has been closed.\n    Closed,\n}\n\n/// SCTP state.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub enum SctpState {\n    /// SCTP procedures not yet initiated.\n    New,\n    /// SCTP connecting.\n    Connecting,\n    /// SCTP successfully connected.\n    Connected,\n    /// SCTP connection failed.\n    Failed,\n    /// SCTP state when the transport has been closed.\n    Closed,\n}\n\n/// DTLS role.\n#[derive(\n    Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Default,\n)]\n#[serde(rename_all = \"camelCase\")]\npub enum DtlsRole {\n    /// The DTLS role is determined based on the resolved ICE role (the `Controlled` role acts as\n    /// DTLS client, the `Controlling` role acts as DTLS server).\n    /// Since mediasoup is a ICE Lite implementation it always behaves as ICE `Controlled`.\n    #[default]\n    Auto,\n    /// DTLS client role.\n    Client,\n    /// DTLS server role.\n    Server,\n}\n\n/// The hash function algorithm (as defined in the \"Hash function Textual Names\" registry initially\n/// specified in [RFC 4572](https://tools.ietf.org/html/rfc4572#section-8) Section 8) and its\n/// corresponding certificate fingerprint value.\n#[derive(Copy, Clone, PartialOrd, Eq, PartialEq)]\npub enum DtlsFingerprint {\n    /// sha-1\n    Sha1 {\n        /// Certificate fingerprint value.\n        value: [u8; 20],\n    },\n    /// sha-224\n    Sha224 {\n        /// Certificate fingerprint value.\n        value: [u8; 28],\n    },\n    /// sha-256\n    Sha256 {\n        /// Certificate fingerprint value.\n        value: [u8; 32],\n    },\n    /// sha-384\n    Sha384 {\n        /// Certificate fingerprint value.\n        value: [u8; 48],\n    },\n    /// sha-512\n    Sha512 {\n        /// Certificate fingerprint value.\n        value: [u8; 64],\n    },\n}\n\nimpl fmt::Debug for DtlsFingerprint {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let name = match self {\n            DtlsFingerprint::Sha1 { .. } => \"Sha1\",\n            DtlsFingerprint::Sha224 { .. } => \"Sha224\",\n            DtlsFingerprint::Sha256 { .. } => \"Sha256\",\n            DtlsFingerprint::Sha384 { .. } => \"Sha384\",\n            DtlsFingerprint::Sha512 { .. } => \"Sha512\",\n        };\n        f.debug_struct(name)\n            .field(\"value\", &self.value_string())\n            .finish()\n    }\n}\n\nimpl Serialize for DtlsFingerprint {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut rtcp_feedback = serializer.serialize_struct(\"DtlsFingerprint\", 2)?;\n        rtcp_feedback.serialize_field(\"algorithm\", self.algorithm_str())?;\n        rtcp_feedback.serialize_field(\"value\", &self.value_string())?;\n        rtcp_feedback.end()\n    }\n}\n\nimpl<'de> Deserialize<'de> for DtlsFingerprint {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        #[derive(Deserialize)]\n        #[serde(field_identifier, rename_all = \"lowercase\")]\n        enum Field {\n            Algorithm,\n            Value,\n        }\n\n        struct DtlsFingerprintVisitor;\n\n        impl<'de> Visitor<'de> for DtlsFingerprintVisitor {\n            type Value = DtlsFingerprint;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n                formatter.write_str(\n                    r#\"DTLS fingerprint algorithm and value like {\"algorithm\": \"sha-256\", \"value\": \"1B:EA:BF:33:B8:11:26:6D:91:AD:1B:A0:16:FD:5D:60:59:33:F7:46:A3:BA:99:2A:1D:04:99:A6:F2:C6:2D:43\"}\"#,\n                )\n            }\n\n            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>\n            where\n                V: MapAccess<'de>,\n            {\n                fn parse_as_bytes(input: &str, output: &mut [u8]) -> Result<(), String> {\n                    for (i, v) in output.iter_mut().enumerate() {\n                        *v = u8::from_str_radix(&input[i * 3..(i * 3) + 2], 16).map_err(\n                            |error| {\n                                format!(\n                                    \"Failed to parse value {input} as series of hex bytes: {error}\"\n                                )\n                            },\n                        )?;\n                    }\n\n                    Ok(())\n                }\n\n                let mut algorithm = None::<Cow<'_, str>>;\n                let mut value = None::<Cow<'_, str>>;\n                while let Some(key) = map.next_key()? {\n                    match key {\n                        Field::Algorithm => {\n                            if algorithm.is_some() {\n                                return Err(de::Error::duplicate_field(\"algorithm\"));\n                            }\n                            algorithm = Some(map.next_value()?);\n                        }\n                        Field::Value => {\n                            if value.is_some() {\n                                return Err(de::Error::duplicate_field(\"value\"));\n                            }\n                            value = map.next_value()?;\n                        }\n                    }\n                }\n                let algorithm = algorithm.ok_or_else(|| de::Error::missing_field(\"algorithm\"))?;\n                let value = value.ok_or_else(|| de::Error::missing_field(\"value\"))?;\n\n                match algorithm.as_ref() {\n                    \"sha-1\" => {\n                        if value.len() == (20 * 3 - 1) {\n                            let mut value_result = [0_u8; 20];\n                            parse_as_bytes(value.as_ref(), &mut value_result)\n                                .map_err(de::Error::custom)?;\n\n                            Ok(DtlsFingerprint::Sha1 {\n                                value: value_result,\n                            })\n                        } else {\n                            Err(de::Error::custom(\n                                \"Value doesn't have correct length for SHA-1\",\n                            ))\n                        }\n                    }\n                    \"sha-224\" => {\n                        if value.len() == (28 * 3 - 1) {\n                            let mut value_result = [0_u8; 28];\n                            parse_as_bytes(value.as_ref(), &mut value_result)\n                                .map_err(de::Error::custom)?;\n\n                            Ok(DtlsFingerprint::Sha224 {\n                                value: value_result,\n                            })\n                        } else {\n                            Err(de::Error::custom(\n                                \"Value doesn't have correct length for SHA-224\",\n                            ))\n                        }\n                    }\n                    \"sha-256\" => {\n                        if value.len() == (32 * 3 - 1) {\n                            let mut value_result = [0_u8; 32];\n                            parse_as_bytes(value.as_ref(), &mut value_result)\n                                .map_err(de::Error::custom)?;\n\n                            Ok(DtlsFingerprint::Sha256 {\n                                value: value_result,\n                            })\n                        } else {\n                            Err(de::Error::custom(\n                                \"Value doesn't have correct length for SHA-256\",\n                            ))\n                        }\n                    }\n                    \"sha-384\" => {\n                        if value.len() == (48 * 3 - 1) {\n                            let mut value_result = [0_u8; 48];\n                            parse_as_bytes(value.as_ref(), &mut value_result)\n                                .map_err(de::Error::custom)?;\n\n                            Ok(DtlsFingerprint::Sha384 {\n                                value: value_result,\n                            })\n                        } else {\n                            Err(de::Error::custom(\n                                \"Value doesn't have correct length for SHA-384\",\n                            ))\n                        }\n                    }\n                    \"sha-512\" => {\n                        if value.len() == (64 * 3 - 1) {\n                            let mut value_result = [0_u8; 64];\n                            parse_as_bytes(value.as_ref(), &mut value_result)\n                                .map_err(de::Error::custom)?;\n\n                            Ok(DtlsFingerprint::Sha512 {\n                                value: value_result,\n                            })\n                        } else {\n                            Err(de::Error::custom(\n                                \"Value doesn't have correct length for SHA-512\",\n                            ))\n                        }\n                    }\n                    algorithm => Err(de::Error::unknown_variant(\n                        algorithm,\n                        &[\"sha-1\", \"sha-224\", \"sha-256\", \"sha-384\", \"sha-512\"],\n                    )),\n                }\n            }\n        }\n\n        const FIELDS: &[&str] = &[\"algorithm\", \"value\"];\n        deserializer.deserialize_struct(\"DtlsFingerprint\", FIELDS, DtlsFingerprintVisitor)\n    }\n}\n\nimpl DtlsFingerprint {\n    pub fn value_string(&self) -> String {\n        match self {\n            DtlsFingerprint::Sha1 { value } => {\n                format!(\n                    \"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\",\n                    value[0],\n                    value[1],\n                    value[2],\n                    value[3],\n                    value[4],\n                    value[5],\n                    value[6],\n                    value[7],\n                    value[8],\n                    value[9],\n                    value[10],\n                    value[11],\n                    value[12],\n                    value[13],\n                    value[14],\n                    value[15],\n                    value[16],\n                    value[17],\n                    value[18],\n                    value[19],\n                )\n            }\n            DtlsFingerprint::Sha224 { value } => {\n                format!(\n                    \"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\",\n                    value[0],\n                    value[1],\n                    value[2],\n                    value[3],\n                    value[4],\n                    value[5],\n                    value[6],\n                    value[7],\n                    value[8],\n                    value[9],\n                    value[10],\n                    value[11],\n                    value[12],\n                    value[13],\n                    value[14],\n                    value[15],\n                    value[16],\n                    value[17],\n                    value[18],\n                    value[19],\n                    value[20],\n                    value[21],\n                    value[22],\n                    value[23],\n                    value[24],\n                    value[25],\n                    value[26],\n                    value[27],\n                )\n            }\n            DtlsFingerprint::Sha256 { value } => {\n                format!(\n                    \"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}\",\n                    value[0],\n                    value[1],\n                    value[2],\n                    value[3],\n                    value[4],\n                    value[5],\n                    value[6],\n                    value[7],\n                    value[8],\n                    value[9],\n                    value[10],\n                    value[11],\n                    value[12],\n                    value[13],\n                    value[14],\n                    value[15],\n                    value[16],\n                    value[17],\n                    value[18],\n                    value[19],\n                    value[20],\n                    value[21],\n                    value[22],\n                    value[23],\n                    value[24],\n                    value[25],\n                    value[26],\n                    value[27],\n                    value[28],\n                    value[29],\n                    value[30],\n                    value[31],\n                )\n            }\n            DtlsFingerprint::Sha384 { value } => {\n                format!(\n                    \"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\",\n                    value[0],\n                    value[1],\n                    value[2],\n                    value[3],\n                    value[4],\n                    value[5],\n                    value[6],\n                    value[7],\n                    value[8],\n                    value[9],\n                    value[10],\n                    value[11],\n                    value[12],\n                    value[13],\n                    value[14],\n                    value[15],\n                    value[16],\n                    value[17],\n                    value[18],\n                    value[19],\n                    value[20],\n                    value[21],\n                    value[22],\n                    value[23],\n                    value[24],\n                    value[25],\n                    value[26],\n                    value[27],\n                    value[28],\n                    value[29],\n                    value[30],\n                    value[31],\n                    value[32],\n                    value[33],\n                    value[34],\n                    value[35],\n                    value[36],\n                    value[37],\n                    value[38],\n                    value[39],\n                    value[40],\n                    value[41],\n                    value[42],\n                    value[43],\n                    value[44],\n                    value[45],\n                    value[46],\n                    value[47],\n                )\n            }\n            DtlsFingerprint::Sha512 { value } => {\n                format!(\n                    \"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\\\n                    {:02X}:{:02X}:{:02X}:{:02X}\",\n                    value[0],\n                    value[1],\n                    value[2],\n                    value[3],\n                    value[4],\n                    value[5],\n                    value[6],\n                    value[7],\n                    value[8],\n                    value[9],\n                    value[10],\n                    value[11],\n                    value[12],\n                    value[13],\n                    value[14],\n                    value[15],\n                    value[16],\n                    value[17],\n                    value[18],\n                    value[19],\n                    value[20],\n                    value[21],\n                    value[22],\n                    value[23],\n                    value[24],\n                    value[25],\n                    value[26],\n                    value[27],\n                    value[28],\n                    value[29],\n                    value[30],\n                    value[31],\n                    value[32],\n                    value[33],\n                    value[34],\n                    value[35],\n                    value[36],\n                    value[37],\n                    value[38],\n                    value[39],\n                    value[40],\n                    value[41],\n                    value[42],\n                    value[43],\n                    value[44],\n                    value[45],\n                    value[46],\n                    value[47],\n                    value[48],\n                    value[49],\n                    value[50],\n                    value[51],\n                    value[52],\n                    value[53],\n                    value[54],\n                    value[55],\n                    value[56],\n                    value[57],\n                    value[58],\n                    value[59],\n                    value[60],\n                    value[61],\n                    value[62],\n                    value[63],\n                )\n            }\n        }\n    }\n\n    fn algorithm_str(&self) -> &'static str {\n        match self {\n            DtlsFingerprint::Sha1 { .. } => \"sha-1\",\n            DtlsFingerprint::Sha224 { .. } => \"sha-224\",\n            DtlsFingerprint::Sha256 { .. } => \"sha-256\",\n            DtlsFingerprint::Sha384 { .. } => \"sha-384\",\n            DtlsFingerprint::Sha512 { .. } => \"sha-512\",\n        }\n    }\n}\n\n/// DTLS parameters.\n#[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]\npub struct DtlsParameters {\n    /// DTLS role.\n    pub role: DtlsRole,\n    /// DTLS fingerprints.\n    pub fingerprints: Vec<DtlsFingerprint>,\n}\n\n/// Trace event direction\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum TraceEventDirection {\n    /// In\n    In,\n    /// Out\n    Out,\n}\n\n/// Container used for sending/receiving messages using `DirectTransport` data producers and data\n/// consumers.\n#[derive(Debug, Clone)]\npub enum WebRtcMessage<'a> {\n    /// String\n    String(Cow<'a, [u8]>),\n    /// Binary\n    Binary(Cow<'a, [u8]>),\n    /// EmptyString\n    EmptyString,\n    /// EmptyBinary\n    EmptyBinary,\n}\n\nimpl<'a> WebRtcMessage<'a> {\n    // +------------------------------------+-----------+\n    // | Value                              | SCTP PPID |\n    // +------------------------------------+-----------+\n    // | WebRTC String                      | 51        |\n    // | WebRTC Binary Partial (Deprecated) | 52        |\n    // | WebRTC Binary                      | 53        |\n    // | WebRTC String Partial (Deprecated) | 54        |\n    // | WebRTC String Empty                | 56        |\n    // | WebRTC Binary Empty                | 57        |\n    // +------------------------------------+-----------+\n\n    pub fn new(ppid: u32, payload: Cow<'a, [u8]>) -> Result<Self, u32> {\n        match ppid {\n            51 => Ok(WebRtcMessage::String(payload)),\n            53 => Ok(WebRtcMessage::Binary(payload)),\n            56 => Ok(WebRtcMessage::EmptyString),\n            57 => Ok(WebRtcMessage::EmptyBinary),\n            ppid => Err(ppid),\n        }\n    }\n\n    pub fn into_ppid_and_payload(self) -> (u32, Cow<'a, [u8]>) {\n        match self {\n            WebRtcMessage::String(binary) => (51_u32, binary),\n            WebRtcMessage::Binary(binary) => (53_u32, binary),\n            WebRtcMessage::EmptyString => (56_u32, Cow::from(vec![0_u8])),\n            WebRtcMessage::EmptyBinary => (57_u32, Cow::from(vec![0_u8])),\n        }\n    }\n\n    /// Convert to owned message\n    pub fn into_owned(self) -> OwnedWebRtcMessage {\n        match self {\n            WebRtcMessage::String(binary) => OwnedWebRtcMessage::String(binary.into_owned()),\n            WebRtcMessage::Binary(binary) => OwnedWebRtcMessage::Binary(binary.into_owned()),\n            WebRtcMessage::EmptyString => OwnedWebRtcMessage::EmptyString,\n            WebRtcMessage::EmptyBinary => OwnedWebRtcMessage::EmptyBinary,\n        }\n    }\n}\n\n/// Similar to WebRtcMessage but represents\n/// messages that have ownership over the data\n#[derive(Debug, Clone)]\npub enum OwnedWebRtcMessage {\n    /// String\n    String(Vec<u8>),\n    /// Binary\n    Binary(Vec<u8>),\n    /// EmptyString\n    EmptyString,\n    /// EmptyBinary\n    EmptyBinary,\n}\n\n/// RTP packet info in trace event.\n#[derive(Debug, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpPacketTraceInfo {\n    /// RTP payload type.\n    pub payload_type: u8,\n    /// Sequence number.\n    pub sequence_number: u16,\n    /// Timestamp.\n    pub timestamp: u32,\n    /// Whether packet has marker or not.\n    pub marker: bool,\n    /// RTP stream SSRC.\n    pub ssrc: u32,\n    /// Whether packet contains a key frame.\n    pub is_key_frame: bool,\n    /// Packet size.\n    pub size: u64,\n    /// Payload size.\n    pub payload_size: u64,\n    /// The spatial layer index (from 0 to N).\n    pub spatial_layer: u8,\n    /// The temporal layer index (from 0 to N).\n    pub temporal_layer: u8,\n    /// The MID RTP extension value as defined in the BUNDLE specification\n    pub mid: Option<String>,\n    /// RTP stream RID value.\n    pub rid: Option<String>,\n    /// RTP stream RRID value.\n    pub rrid: Option<String>,\n    /// Transport-wide sequence number.\n    pub wide_sequence_number: Option<u16>,\n    /// Whether this is an RTX packet.\n    #[serde(default)]\n    pub is_rtx: bool,\n}\n\n/// SSRC info in trace event.\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\npub struct SsrcTraceInfo {\n    /// RTP stream SSRC.\n    pub ssrc: u32,\n}\n\n/// Bandwidth estimation type.\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\npub enum BweType {\n    /// Transport-wide Congestion Control.\n    #[serde(rename = \"transport-cc\")]\n    TransportCc,\n    /// Receiver Estimated Maximum Bitrate.\n    #[serde(rename = \"remb\")]\n    Remb,\n}\n\n/// BWE info in trace event.\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct BweTraceInfo {\n    /// Bandwidth estimation type.\n    pub r#type: BweType,\n    /// Desired bitrate\n    pub desired_bitrate: u32,\n    /// Effective desired bitrate.\n    pub effective_desired_bitrate: u32,\n    /// Min bitrate.\n    pub min_bitrate: u32,\n    /// Max bitrate.\n    pub max_bitrate: u32,\n    /// Start bitrate.\n    pub start_bitrate: u32,\n    /// Max padding bitrate.\n    pub max_padding_bitrate: u32,\n    /// Available bitrate.\n    pub available_bitrate: u32,\n}\n\n/// RTCP Sender Report info in trace event.\n#[derive(Debug, Copy, Clone, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SrTraceInfo {\n    /// Stream SSRC\n    pub ssrc: u32,\n    /// NTP : most significant word\n    pub ntp_sec: u32,\n    /// NTP : least significant word\n    pub ntp_frac: u32,\n    /// RTP timestamp\n    pub rtp_ts: u32,\n    /// Sender packet count\n    pub packet_count: u32,\n    /// Sender octet count\n    pub octet_count: u32,\n}\n"
  },
  {
    "path": "rust/types/src/lib.rs",
    "content": "#![warn(rust_2018_idioms, missing_debug_implementations)]\n//! MediaSoup Rust Types\n//!\n//! This library provides Rust types for interacting with [mediasoup](https://docs.rs/mediasoup) crate.\n\npub mod data_structures;\npub mod rtp_parameters;\npub mod scalability_modes;\npub mod sctp_parameters;\npub mod srtp_parameters;\n"
  },
  {
    "path": "rust/types/src/rtp_parameters/tests.rs",
    "content": "use super::*;\n\n#[test]\nfn rtcp_feedback_serde() {\n    {\n        let nack_pli_str = r#\"{\"type\":\"nack\",\"parameter\":\"pli\"}\"#;\n\n        assert_eq!(\n            serde_json::from_str::<RtcpFeedback>(nack_pli_str).unwrap(),\n            RtcpFeedback::NackPli\n        );\n\n        let result = serde_json::to_string(&RtcpFeedback::NackPli).unwrap();\n        assert_eq!(result.as_str(), nack_pli_str);\n    }\n    {\n        let transport_cc_str = r#\"{\"type\":\"transport-cc\",\"parameter\":\"\"}\"#;\n\n        assert_eq!(\n            serde_json::from_str::<RtcpFeedback>(transport_cc_str).unwrap(),\n            RtcpFeedback::TransportCc\n        );\n\n        let result = serde_json::to_string(&RtcpFeedback::TransportCc).unwrap();\n        assert_eq!(result.as_str(), transport_cc_str);\n    }\n    {\n        let nack_bar_str = r#\"{\"type\":\"nack\",\"parameter\":\"bar\"}\"#;\n\n        assert_eq!(\n            serde_json::from_str::<RtcpFeedback>(nack_bar_str).unwrap(),\n            RtcpFeedback::Unsupported\n        );\n    }\n}\n"
  },
  {
    "path": "rust/types/src/rtp_parameters.rs",
    "content": "//! Collection of RTP-related data structures that are used to specify codec parameters and\n//! capabilities of various endpoints.\n\n#[cfg(test)]\nmod tests;\n\nuse crate::scalability_modes::ScalabilityMode;\nuse serde::de::{MapAccess, Visitor};\nuse serde::ser::SerializeStruct;\nuse serde::{de, Deserialize, Deserializer, Serialize, Serializer};\nuse std::borrow::Cow;\nuse std::collections::BTreeMap;\nuse std::fmt;\nuse std::iter::FromIterator;\nuse std::num::{NonZeroU32, NonZeroU8};\nuse std::str::FromStr;\nuse thiserror::Error;\n\n/// Codec specific parameters. Some parameters (such as `packetization-mode` and `profile-level-id`\n/// in H264 or `profile-id` in VP9) are critical for codec matching.\n#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\npub struct RtpCodecParametersParameters(\n    BTreeMap<Cow<'static, str>, RtpCodecParametersParametersValue>,\n);\n\nimpl RtpCodecParametersParameters {\n    /// Insert another parameter into collection.\n    pub fn insert<K, V>(&mut self, key: K, value: V) -> &mut Self\n    where\n        K: Into<Cow<'static, str>>,\n        V: Into<RtpCodecParametersParametersValue>,\n    {\n        self.0.insert(key.into(), value.into());\n        self\n    }\n\n    /// Iterate over parameters in collection.\n    pub fn iter(\n        &self,\n    ) -> std::collections::btree_map::Iter<'_, Cow<'static, str>, RtpCodecParametersParametersValue>\n    {\n        self.0.iter()\n    }\n\n    /// Get specific parameter from collection.\n    #[must_use]\n    pub fn get(&self, key: &str) -> Option<&RtpCodecParametersParametersValue> {\n        self.0.get(key)\n    }\n}\n\nimpl<K, const N: usize> From<[(K, RtpCodecParametersParametersValue); N]>\n    for RtpCodecParametersParameters\nwhere\n    K: Into<Cow<'static, str>>,\n{\n    fn from(array: [(K, RtpCodecParametersParametersValue); N]) -> Self {\n        IntoIterator::into_iter(array).collect()\n    }\n}\n\nimpl IntoIterator for RtpCodecParametersParameters {\n    type Item = (Cow<'static, str>, RtpCodecParametersParametersValue);\n    type IntoIter =\n        std::collections::btree_map::IntoIter<Cow<'static, str>, RtpCodecParametersParametersValue>;\n\n    fn into_iter(\n        self,\n    ) -> std::collections::btree_map::IntoIter<Cow<'static, str>, RtpCodecParametersParametersValue>\n    {\n        self.0.into_iter()\n    }\n}\n\nimpl<K> Extend<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters\nwhere\n    K: Into<Cow<'static, str>>,\n{\n    fn extend<T: IntoIterator<Item = (K, RtpCodecParametersParametersValue)>>(&mut self, iter: T) {\n        iter.into_iter().for_each(|(k, v)| {\n            self.insert(k, v);\n        });\n    }\n}\n\nimpl<K> FromIterator<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters\nwhere\n    K: Into<Cow<'static, str>>,\n{\n    fn from_iter<T: IntoIterator<Item = (K, RtpCodecParametersParametersValue)>>(iter: T) -> Self {\n        Self(iter.into_iter().map(|(k, v)| (k.into(), v)).collect())\n    }\n}\n\n/// Provides information on the capabilities of a codec within the RTP capabilities. The list of\n/// media codecs supported by mediasoup and their settings is defined in the\n/// `supported_rtp_capabilities.rs` file.\n///\n/// Exactly one [`RtpCodecCapabilityFinalized`] will be present for each supported combination of\n/// parameters that requires a distinct value of `preferred_payload_type`. For example:\n///\n/// - Multiple H264 codecs, each with their own distinct `packetization-mode` and `profile-level-id`\n///   values.\n/// - Multiple VP9 codecs, each with their own distinct `profile-id` value.\n///\n/// This is similar to [`RtpCodecCapability`], but with `preferred_payload_type` field being\n/// required.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(tag = \"kind\", rename_all = \"lowercase\")]\npub enum RtpCodecCapabilityFinalized {\n    /// Audio codec\n    #[serde(rename_all = \"camelCase\")]\n    Audio {\n        /// The codec MIME media type/subtype (e.g. 'audio/opus').\n        mime_type: MimeTypeAudio,\n        /// The preferred RTP payload type.\n        preferred_payload_type: u8,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// The number of channels supported (e.g. two for stereo). Just for audio.\n        /// Default 1.\n        channels: NonZeroU8,\n        /// Codec specific parameters. Some parameters (such as `packetization-mode` and\n        /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching.\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n    /// Video codec\n    #[serde(rename_all = \"camelCase\")]\n    Video {\n        /// The codec MIME media type/subtype (e.g. 'video/VP8').\n        mime_type: MimeTypeVideo,\n        /// The preferred RTP payload type.\n        preferred_payload_type: u8,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// Codec specific parameters. Some parameters (such as `packetization-mode` and\n        /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching.\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n}\n\nimpl RtpCodecCapabilityFinalized {\n    pub fn is_rtx(&self) -> bool {\n        match self {\n            Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx,\n            Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx,\n        }\n    }\n\n    pub fn clock_rate(&self) -> NonZeroU32 {\n        let (Self::Audio { clock_rate, .. } | Self::Video { clock_rate, .. }) = self;\n        *clock_rate\n    }\n\n    pub fn parameters(&self) -> &RtpCodecParametersParameters {\n        let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self;\n        parameters\n    }\n\n    pub fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters {\n        let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self;\n        parameters\n    }\n\n    pub fn preferred_payload_type(&self) -> u8 {\n        match self {\n            Self::Audio {\n                preferred_payload_type,\n                ..\n            }\n            | Self::Video {\n                preferred_payload_type,\n                ..\n            } => *preferred_payload_type,\n        }\n    }\n}\n\n/// The RTP capabilities define what mediasoup or an endpoint can receive at media level.\n#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpCapabilitiesFinalized {\n    /// Supported media and RTX codecs.\n    pub codecs: Vec<RtpCodecCapabilityFinalized>,\n    /// Supported RTP header extensions.\n    pub header_extensions: Vec<RtpHeaderExtension>,\n}\n\n/// Media kind\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum MediaKind {\n    /// Audio\n    Audio,\n    /// Video\n    Video,\n}\n\n/// Error that caused [`MimeType`] parsing error.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum ParseMimeTypeError {\n    /// Invalid MIME type input string\n    #[error(\"Invalid MIME type input string\")]\n    InvalidInput,\n    /// Unknown MIME type\n    #[error(\"Unknown MIME type\")]\n    UnknownMimeType,\n}\n\n/// Known Audio or Video MIME type.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum MimeType {\n    /// Audio\n    Audio(MimeTypeAudio),\n    /// Video\n    Video(MimeTypeVideo),\n}\n\nimpl FromStr for MimeType {\n    type Err = ParseMimeTypeError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        if s.starts_with(\"audio/\") {\n            MimeTypeAudio::from_str(s).map(Self::Audio)\n        } else if s.starts_with(\"video/\") {\n            MimeTypeVideo::from_str(s).map(Self::Video)\n        } else {\n            Err(ParseMimeTypeError::InvalidInput)\n        }\n    }\n}\n\nimpl MimeType {\n    /// String representation of MIME type.\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Self::Audio(mime_type) => mime_type.as_str(),\n            Self::Video(mime_type) => mime_type.as_str(),\n        }\n    }\n}\n\n/// Known Audio MIME types.\n#[allow(non_camel_case_types)]\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]\npub enum MimeTypeAudio {\n    /// Opus\n    #[serde(rename = \"audio/opus\")]\n    Opus,\n    /// Multi-channel Opus (Surround sound in Chromium)\n    #[serde(rename = \"audio/multiopus\")]\n    MultiChannelOpus,\n    /// PCMU\n    #[serde(rename = \"audio/PCMU\")]\n    Pcmu,\n    /// PCMA\n    #[serde(rename = \"audio/PCMA\")]\n    Pcma,\n    /// ISAC\n    #[serde(rename = \"audio/ISAC\")]\n    Isac,\n    /// G722\n    #[serde(rename = \"audio/G722\")]\n    G722,\n    /// iLBC\n    #[serde(rename = \"audio/iLBC\")]\n    Ilbc,\n    /// SILK\n    #[serde(rename = \"audio/SILK\")]\n    Silk,\n    /// CN\n    #[serde(rename = \"audio/CN\")]\n    Cn,\n    /// TelephoneEvent\n    #[serde(rename = \"audio/telephone-event\")]\n    TelephoneEvent,\n    /// RTX\n    #[serde(rename = \"audio/rtx\")]\n    Rtx,\n    /// RED\n    #[serde(rename = \"audio/red\")]\n    Red,\n}\n\nimpl FromStr for MimeTypeAudio {\n    type Err = ParseMimeTypeError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.to_ascii_lowercase().as_str() {\n            \"audio/opus\" => Ok(Self::Opus),\n            \"audio/multiopus\" => Ok(Self::MultiChannelOpus),\n            \"audio/pcmu\" => Ok(Self::Pcmu),\n            \"audio/pcma\" => Ok(Self::Pcma),\n            \"audio/isac\" => Ok(Self::Isac),\n            \"audio/g722\" => Ok(Self::G722),\n            \"audio/ilbc\" => Ok(Self::Ilbc),\n            \"audio/silk\" => Ok(Self::Silk),\n            \"audio/cn\" => Ok(Self::Cn),\n            \"audio/telephone-event\" => Ok(Self::TelephoneEvent),\n            \"audio/rtx\" => Ok(Self::Rtx),\n            \"audio/red\" => Ok(Self::Red),\n            s => Err(if s.starts_with(\"audio/\") {\n                ParseMimeTypeError::UnknownMimeType\n            } else {\n                ParseMimeTypeError::InvalidInput\n            }),\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for MimeTypeAudio {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let s = String::deserialize(deserializer)?;\n        MimeTypeAudio::from_str(&s).map_err(serde::de::Error::custom)\n    }\n}\n\nimpl MimeTypeAudio {\n    /// String representation of MIME type.\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Self::Opus => \"audio/opus\",\n            Self::MultiChannelOpus => \"audio/multiopus\",\n            Self::Pcmu => \"audio/PCMU\",\n            Self::Pcma => \"audio/PCMA\",\n            Self::Isac => \"audio/ISAC\",\n            Self::G722 => \"audio/G722\",\n            Self::Ilbc => \"audio/iLBC\",\n            Self::Silk => \"audio/SILK\",\n            Self::Cn => \"audio/CN\",\n            Self::TelephoneEvent => \"audio/telephone-event\",\n            Self::Rtx => \"audio/rtx\",\n            Self::Red => \"audio/red\",\n        }\n    }\n}\n\n/// Known Video MIME types.\n#[allow(non_camel_case_types)]\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]\npub enum MimeTypeVideo {\n    /// VP8\n    #[serde(rename = \"video/VP8\")]\n    Vp8,\n    /// VP9\n    #[serde(rename = \"video/VP9\")]\n    Vp9,\n    /// H264\n    #[serde(rename = \"video/H264\")]\n    H264,\n    /// AV1\n    #[serde(rename = \"video/AV1\")]\n    AV1,\n    /// RTX\n    #[serde(rename = \"video/rtx\")]\n    Rtx,\n    /// RED\n    #[serde(rename = \"video/red\")]\n    Red,\n    /// ULPFEC\n    #[serde(rename = \"video/ulpfec\")]\n    Ulpfec,\n}\n\nimpl FromStr for MimeTypeVideo {\n    type Err = ParseMimeTypeError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s.to_ascii_lowercase().as_str() {\n            \"video/vp8\" => Ok(Self::Vp8),\n            \"video/vp9\" => Ok(Self::Vp9),\n            \"video/h264\" => Ok(Self::H264),\n            \"video/av1\" => Ok(Self::AV1),\n            \"video/rtx\" => Ok(Self::Rtx),\n            \"video/red\" => Ok(Self::Red),\n            \"video/ulpfec\" => Ok(Self::Ulpfec),\n            s => Err(if s.starts_with(\"video/\") {\n                ParseMimeTypeError::UnknownMimeType\n            } else {\n                ParseMimeTypeError::InvalidInput\n            }),\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for MimeTypeVideo {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let s = String::deserialize(deserializer)?;\n        MimeTypeVideo::from_str(&s).map_err(serde::de::Error::custom)\n    }\n}\n\nimpl MimeTypeVideo {\n    /// String representation of MIME type.\n    pub fn as_str(&self) -> &'static str {\n        match self {\n            Self::Vp8 => \"video/VP8\",\n            Self::Vp9 => \"video/VP9\",\n            Self::H264 => \"video/H264\",\n            Self::AV1 => \"video/AV1\",\n            Self::Rtx => \"video/rtx\",\n            Self::Red => \"video/red\",\n            Self::Ulpfec => \"video/ulpfec\",\n        }\n    }\n}\n\n/// Provides information on the capabilities of a codec within the RTP capabilities. The list of\n/// media codecs supported by mediasoup and their settings is defined in the\n/// `supported_rtp_capabilities.rs` file.\n///\n/// Exactly one [`RtpCodecCapability`] will be present for each supported combination of parameters\n/// that requires a distinct value of `preferred_payload_type`. For example:\n///\n/// - Multiple H264 codecs, each with their own distinct `packetization-mode` and `profile-level-id`\n///   values.\n/// - Multiple VP9 codecs, each with their own distinct `profile-id` value.\n///\n/// [`RtpCodecCapability`] entries in the `media_codecs` vector of\n/// [`RouterOptions`](https://docs.rs/mediasoup/latest/mediasoup/router/struct.RouterOptions.html)\n/// (if unset, mediasoup will choose a random one). If given, make sure it's in the 96-127 range.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(tag = \"kind\", rename_all = \"lowercase\")]\npub enum RtpCodecCapability {\n    /// Audio codec capability\n    #[serde(rename_all = \"camelCase\")]\n    Audio {\n        /// The codec MIME media type/subtype (e.g. 'audio/opus').\n        mime_type: MimeTypeAudio,\n        /// The preferred RTP payload type.\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        preferred_payload_type: Option<u8>,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// The number of channels supported (e.g. two for stereo). Just for audio.\n        /// Default 1.\n        channels: NonZeroU8,\n        /// Codec specific parameters. Some parameters (such as `packetization-mode` and\n        /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching.\n        #[serde(default)]\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        #[serde(default)]\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n    /// Video codec capability\n    #[serde(rename_all = \"camelCase\")]\n    Video {\n        /// The codec MIME media type/subtype (e.g. 'video/VP8').\n        mime_type: MimeTypeVideo,\n        /// The preferred RTP payload type.\n        #[serde(skip_serializing_if = \"Option::is_none\")]\n        preferred_payload_type: Option<u8>,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// Codec specific parameters. Some parameters (such as `packetization-mode` and\n        /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching.\n        #[serde(default)]\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        #[serde(default)]\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n}\n\nimpl RtpCodecCapability {\n    pub fn mime_type(&self) -> MimeType {\n        match self {\n            Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type),\n            Self::Video { mime_type, .. } => MimeType::Video(*mime_type),\n        }\n    }\n\n    pub fn parameters(&self) -> &RtpCodecParametersParameters {\n        let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self;\n        parameters\n    }\n\n    pub fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters {\n        let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self;\n        parameters\n    }\n\n    pub fn preferred_payload_type(&self) -> Option<u8> {\n        match self {\n            Self::Audio {\n                preferred_payload_type,\n                ..\n            }\n            | Self::Video {\n                preferred_payload_type,\n                ..\n            } => *preferred_payload_type,\n        }\n    }\n\n    pub fn rtcp_feedback(&self) -> &Vec<RtcpFeedback> {\n        let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self;\n        rtcp_feedback\n    }\n}\n\n/// The RTP capabilities define what mediasoup or an endpoint can receive at media level.\n#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpCapabilities {\n    /// Supported media and RTX codecs.\n    pub codecs: Vec<RtpCodecCapability>,\n    /// Supported RTP header extensions.\n    pub header_extensions: Vec<RtpHeaderExtension>,\n}\n\n/// Direction of RTP header extension.\n#[derive(\n    Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Default,\n)]\n#[serde(rename_all = \"lowercase\")]\npub enum RtpHeaderExtensionDirection {\n    /// SendRecv\n    #[default]\n    SendRecv,\n    /// SendOnly\n    SendOnly,\n    /// RecvOnly\n    RecvOnly,\n    /// Inactive\n    Inactive,\n}\n\n/// Error that caused [`RtpHeaderExtensionUri`] parsing error.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum RtpHeaderExtensionUriParseError {\n    /// Unsupported\n    #[error(\"Unsupported\")]\n    Unsupported,\n}\n\n/// URI for supported RTP header extension\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\npub enum RtpHeaderExtensionUri {\n    /// urn:ietf:params:rtp-hdrext:sdes:mid\n    #[serde(rename = \"urn:ietf:params:rtp-hdrext:sdes:mid\")]\n    Mid,\n    /// urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\n    #[serde(rename = \"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\")]\n    RtpStreamId,\n    /// urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\n    #[serde(rename = \"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\")]\n    RepairRtpStreamId,\n    /// <http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time>\n    #[serde(rename = \"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\")]\n    AbsSendTime,\n    /// <http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01>\n    #[serde(rename = \"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\")]\n    TransportWideCcDraft01,\n    /// urn:ietf:params:rtp-hdrext:ssrc-audio-level\n    #[serde(rename = \"urn:ietf:params:rtp-hdrext:ssrc-audio-level\")]\n    SsrcAudioLevel,\n    /// <https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension>\n    #[serde(\n        rename = \"https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension\"\n    )]\n    DependencyDescriptor,\n    /// urn:3gpp:video-orientation\n    #[serde(rename = \"urn:3gpp:video-orientation\")]\n    VideoOrientation,\n    /// urn:ietf:params:rtp-hdrext:toffset\n    #[serde(rename = \"urn:ietf:params:rtp-hdrext:toffset\")]\n    TimeOffset,\n    /// <http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time>\n    #[serde(rename = \"http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time\")]\n    AbsCaptureTime,\n    /// <http://www.webrtc.org/experiments/rtp-hdrext/playout-delay>\n    #[serde(rename = \"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\")]\n    PlayoutDelay,\n    /// urn:mediasoup:params:rtp-hdrext:packet-id\n    #[serde(rename = \"urn:mediasoup:params:rtp-hdrext:packet-id\")]\n    MediasoupPacketId,\n\n    #[doc(hidden)]\n    #[serde(other, rename = \"unsupported\")]\n    Unsupported,\n}\n\nimpl FromStr for RtpHeaderExtensionUri {\n    type Err = RtpHeaderExtensionUriParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"urn:ietf:params:rtp-hdrext:sdes:mid\" => Ok(Self::Mid),\n            \"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\" => Ok(Self::RtpStreamId),\n            \"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\" => Ok(Self::RepairRtpStreamId),\n            \"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\" => Ok(Self::AbsSendTime),\n            \"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\" => {\n                Ok(Self::TransportWideCcDraft01)\n            }\n            \"urn:ietf:params:rtp-hdrext:ssrc-audio-level\" => Ok(Self::SsrcAudioLevel),\n            \"https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension\" => Ok(Self::DependencyDescriptor),\n            \"urn:3gpp:video-orientation\" => Ok(Self::VideoOrientation),\n            \"urn:ietf:params:rtp-hdrext:toffset\" => Ok(Self::TimeOffset),\n            \"http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time\" => {\n                Ok(Self::AbsCaptureTime)\n            }\n            \"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\" => Ok(Self::PlayoutDelay),\n            \"urn:mediasoup:params:rtp-hdrext:packet-id\" => Ok(Self::MediasoupPacketId),\n            _ => Err(RtpHeaderExtensionUriParseError::Unsupported),\n        }\n    }\n}\n\nimpl RtpHeaderExtensionUri {\n    /// RTP header extension as a string\n    #[must_use]\n    pub fn as_str(self) -> &'static str {\n        match self {\n            RtpHeaderExtensionUri::Mid => \"urn:ietf:params:rtp-hdrext:sdes:mid\",\n            RtpHeaderExtensionUri::RtpStreamId => \"urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\",\n            RtpHeaderExtensionUri::RepairRtpStreamId => {\n                \"urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\"\n            }\n            RtpHeaderExtensionUri::AbsSendTime => {\n                \"http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\"\n            }\n            RtpHeaderExtensionUri::TransportWideCcDraft01 => {\n                \"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\"\n            }\n            RtpHeaderExtensionUri::SsrcAudioLevel => \"urn:ietf:params:rtp-hdrext:ssrc-audio-level\",\n            RtpHeaderExtensionUri::DependencyDescriptor => \"https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension\",\n            RtpHeaderExtensionUri::VideoOrientation => \"urn:3gpp:video-orientation\",\n            RtpHeaderExtensionUri::TimeOffset => \"urn:ietf:params:rtp-hdrext:toffset\",\n            RtpHeaderExtensionUri::AbsCaptureTime => {\n                \"http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time\"\n            }\n            RtpHeaderExtensionUri::PlayoutDelay => {\n                \"http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\"\n            }\n            RtpHeaderExtensionUri::MediasoupPacketId => {\n                \"urn:mediasoup:params:rtp-hdrext:packet-id\"\n            }\n            RtpHeaderExtensionUri::Unsupported => \"unsupported\",\n        }\n    }\n}\n\n/// Provides information relating to supported header extensions. The list of RTP header extensions\n/// supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file.\n///\n/// mediasoup does not currently support encrypted RTP header extensions. The direction field is\n/// just present in mediasoup RTP capabilities (retrieved via\n/// `mediasoup::router::Router::rtp_capabilities()` or\n/// `mediasoup::supported_rtp_capabilities::get_supported_rtp_capabilities()`. It's ignored if\n/// present in endpoints' RTP capabilities.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpHeaderExtension {\n    /// Media kind.\n    pub kind: MediaKind,\n    /// The URI of the RTP header extension, as defined in RFC 5285.\n    pub uri: RtpHeaderExtensionUri,\n    /// The preferred numeric identifier that goes in the RTP packet. Must be unique.\n    pub preferred_id: u16,\n    /// If true, it is preferred that the value in the header be encrypted as per RFC 6904.\n    /// Default false.\n    pub preferred_encrypt: bool,\n    /// If `SendRecv`, mediasoup supports sending and receiving this RTP extension. `SendOnly` means\n    /// that mediasoup can send (but not receive) it. `RecvOnly` means that mediasoup can receive\n    /// (but not send) it.\n    pub direction: RtpHeaderExtensionDirection,\n}\n\n/// The RTP send parameters describe a media stream received by mediasoup from\n/// an endpoint through its corresponding mediasoup Producer. These parameters\n/// may include a mid value that the mediasoup transport will use to match\n/// received RTP packets based on their MID RTP extension value.\n///\n/// mediasoup allows RTP send parameters with a single encoding and with multiple\n/// encodings (simulcast). In the latter case, each entry in the encodings array\n/// must include a ssrc field or a rid field (the RID RTP extension value). Check\n/// the Simulcast and SVC sections for more information.\n///\n/// The RTP receive parameters describe a media stream as sent by mediasoup to\n/// an endpoint through its corresponding mediasoup Consumer. The mid value is\n/// unset (mediasoup does not include the MID RTP extension into RTP packets\n/// being sent to endpoints).\n///\n/// There is a single entry in the encodings array (even if the corresponding\n/// producer uses simulcast). The consumer sends a single and continuous RTP\n/// stream to the endpoint and spatial/temporal layer selection is possible via\n/// consumer.setPreferredLayers().\n///\n/// As an exception, previous bullet is not true when consuming a stream over a\n/// [`PipeTransport`](https://docs.rs/mediasoup/latest/mediasoup/pipe_transport/struct.PipeTransport.html)\n/// associated producer are forwarded verbatim through the consumer.\n///\n/// The RTP receive parameters will always have their ssrc values randomly\n/// generated for all of its  encodings (and optional rtx: { ssrc: XXXX } if the\n/// endpoint supports RTX), regardless of the original RTP send parameters in\n/// the associated producer. This applies even if the producer's encodings have\n/// rid set.\n#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpParameters {\n    /// The MID RTP extension value as defined in the BUNDLE specification.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub mid: Option<String>,\n    /// Media and RTX codecs in use.\n    pub codecs: Vec<RtpCodecParameters>,\n    /// RTP header extensions in use.\n    pub header_extensions: Vec<RtpHeaderExtensionParameters>,\n    /// Transmitted RTP streams and their settings.\n    pub encodings: Vec<RtpEncodingParameters>,\n    /// Parameters used for RTCP.\n    pub rtcp: RtcpParameters,\n    /// MSID (WebRTC MediaStream Identification) as defined in\n    /// <https://datatracker.ietf.org/doc/html/rfc8830>\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub msid: Option<String>,\n}\n\n/// Single value used in RTP codec parameters.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(untagged)]\npub enum RtpCodecParametersParametersValue {\n    /// String value\n    String(Cow<'static, str>),\n    /// Numerical value\n    Number(u32),\n}\n\nimpl From<Cow<'static, str>> for RtpCodecParametersParametersValue {\n    fn from(s: Cow<'static, str>) -> Self {\n        Self::String(s)\n    }\n}\n\nimpl From<String> for RtpCodecParametersParametersValue {\n    fn from(s: String) -> Self {\n        Self::String(s.into())\n    }\n}\n\nimpl From<&'static str> for RtpCodecParametersParametersValue {\n    fn from(s: &'static str) -> Self {\n        Self::String(s.into())\n    }\n}\n\nimpl From<u8> for RtpCodecParametersParametersValue {\n    fn from(n: u8) -> Self {\n        Self::Number(u32::from(n))\n    }\n}\n\nimpl From<u16> for RtpCodecParametersParametersValue {\n    fn from(n: u16) -> Self {\n        Self::Number(u32::from(n))\n    }\n}\n\nimpl From<u32> for RtpCodecParametersParametersValue {\n    fn from(n: u32) -> Self {\n        Self::Number(n)\n    }\n}\n\n/// Provides information on codec settings within the RTP parameters. The list\n/// of media codecs supported by mediasoup and their settings is defined in the\n/// `supported_rtp_capabilities.rs` file.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(untagged, rename_all = \"lowercase\")]\npub enum RtpCodecParameters {\n    /// Audio codec\n    #[serde(rename_all = \"camelCase\")]\n    Audio {\n        /// The codec MIME media type/subtype (e.g. `audio/opus`).\n        mime_type: MimeTypeAudio,\n        /// The value that goes in the RTP Payload Type Field. Must be unique.\n        payload_type: u8,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// The number of channels supported (e.g. two for stereo).\n        /// Default 1.\n        channels: NonZeroU8,\n        /// Codec-specific parameters available for signaling. Some parameters (such as\n        /// `packetization-mode` and `profile-level-id` in H264 or `profile-id` in VP9) are critical for\n        /// codec matching.\n        #[serde(default)]\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        #[serde(default)]\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n    /// Video codec\n    #[serde(rename_all = \"camelCase\")]\n    Video {\n        /// The codec MIME media type/subtype (e.g. `video/VP8`).\n        mime_type: MimeTypeVideo,\n        /// The value that goes in the RTP Payload Type Field. Must be unique.\n        payload_type: u8,\n        /// Codec clock rate expressed in Hertz.\n        clock_rate: NonZeroU32,\n        /// Codec-specific parameters available for signaling. Some parameters (such as\n        /// `packetization-mode` and `profile-level-id` in H264 or `profile-id` in VP9) are critical for\n        /// codec matching.\n        #[serde(default)]\n        parameters: RtpCodecParametersParameters,\n        /// Transport layer and codec-specific feedback messages for this codec.\n        #[serde(default)]\n        rtcp_feedback: Vec<RtcpFeedback>,\n    },\n}\n\nimpl RtpCodecParameters {\n    pub fn is_rtx(&self) -> bool {\n        match self {\n            Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx,\n            Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx,\n        }\n    }\n\n    pub fn mime_type(&self) -> MimeType {\n        match self {\n            Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type),\n            Self::Video { mime_type, .. } => MimeType::Video(*mime_type),\n        }\n    }\n\n    pub fn payload_type(&self) -> u8 {\n        let (Self::Audio { payload_type, .. } | Self::Video { payload_type, .. }) = self;\n        *payload_type\n    }\n\n    pub fn clock_rate(&self) -> NonZeroU32 {\n        let (Self::Audio { clock_rate, .. } | Self::Video { clock_rate, .. }) = self;\n        *clock_rate\n    }\n\n    pub fn parameters(&self) -> &RtpCodecParametersParameters {\n        let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self;\n        parameters\n    }\n\n    pub fn rtcp_feedback(&self) -> &[RtcpFeedback] {\n        let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self;\n        rtcp_feedback\n    }\n\n    pub fn rtcp_feedback_mut(&mut self) -> &mut Vec<RtcpFeedback> {\n        let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self;\n        rtcp_feedback\n    }\n}\n\n/// Provides information on RTCP feedback messages for a specific codec. Those messages can be\n/// transport layer feedback messages or codec-specific feedback messages. The list of RTCP\n/// feedbacks supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]\npub enum RtcpFeedback {\n    /// NACK\n    Nack,\n    /// NACK PLI\n    NackPli,\n    /// CCM FIR\n    CcmFir,\n    /// goog-remb\n    GoogRemb,\n    /// transport-cc\n    TransportCc,\n    #[doc(hidden)]\n    Unsupported,\n}\n\nimpl Serialize for RtcpFeedback {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        let mut rtcp_feedback = serializer.serialize_struct(\"RtcpFeedback\", 2)?;\n        let (r#type, parameter) = self.as_type_parameter();\n        rtcp_feedback.serialize_field(\"type\", r#type)?;\n        rtcp_feedback.serialize_field(\"parameter\", parameter)?;\n        rtcp_feedback.end()\n    }\n}\n\nimpl<'de> Deserialize<'de> for RtcpFeedback {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        #[derive(Deserialize)]\n        #[serde(field_identifier, rename_all = \"lowercase\")]\n        enum Field {\n            Type,\n            Parameter,\n        }\n\n        struct RtcpFeedbackVisitor;\n\n        impl<'de> Visitor<'de> for RtcpFeedbackVisitor {\n            type Value = RtcpFeedback;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n                formatter.write_str(\n                    r#\"RTCP feedback type and parameter like {\"type\": \"nack\", \"parameter\": \"\"}\"#,\n                )\n            }\n\n            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>\n            where\n                V: MapAccess<'de>,\n            {\n                let mut r#type = None::<Cow<'_, str>>;\n                let mut parameter = Cow::Borrowed(\"\");\n                while let Some(key) = map.next_key()? {\n                    match key {\n                        Field::Type => {\n                            if r#type.is_some() {\n                                return Err(de::Error::duplicate_field(\"type\"));\n                            }\n                            r#type = Some(map.next_value()?);\n                        }\n                        Field::Parameter => {\n                            if !parameter.is_empty() {\n                                return Err(de::Error::duplicate_field(\"parameter\"));\n                            }\n                            parameter = map.next_value()?;\n                        }\n                    }\n                }\n                let r#type = r#type.ok_or_else(|| de::Error::missing_field(\"type\"))?;\n\n                Ok(\n                    RtcpFeedback::from_type_parameter(r#type.as_ref(), parameter.as_ref())\n                        .unwrap_or(RtcpFeedback::Unsupported),\n                )\n            }\n        }\n\n        const FIELDS: &[&str] = &[\"type\", \"parameter\"];\n        deserializer.deserialize_struct(\"RtcpFeedback\", FIELDS, RtcpFeedbackVisitor)\n    }\n}\n\n/// Error of failure to create [`RtcpFeedback`] from type and parameter.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum RtcpFeedbackFromTypeParameterError {\n    /// Unsupported\n    #[error(\"Unsupported\")]\n    Unsupported,\n}\n\nimpl RtcpFeedback {\n    pub fn from_type_parameter(\n        r#type: &str,\n        parameter: &str,\n    ) -> Result<Self, RtcpFeedbackFromTypeParameterError> {\n        match (r#type, parameter) {\n            (\"nack\", \"\") => Ok(RtcpFeedback::Nack),\n            (\"nack\", \"pli\") => Ok(RtcpFeedback::NackPli),\n            (\"ccm\", \"fir\") => Ok(RtcpFeedback::CcmFir),\n            (\"goog-remb\", \"\") => Ok(RtcpFeedback::GoogRemb),\n            (\"transport-cc\", \"\") => Ok(RtcpFeedback::TransportCc),\n            (\"unknown\", \"\") => Ok(RtcpFeedback::Unsupported),\n            _ => Err(RtcpFeedbackFromTypeParameterError::Unsupported),\n        }\n    }\n\n    pub fn as_type_parameter(&self) -> (&'static str, &'static str) {\n        match self {\n            RtcpFeedback::Nack => (\"nack\", \"\"),\n            RtcpFeedback::NackPli => (\"nack\", \"pli\"),\n            RtcpFeedback::CcmFir => (\"ccm\", \"fir\"),\n            RtcpFeedback::GoogRemb => (\"goog-remb\", \"\"),\n            RtcpFeedback::TransportCc => (\"transport-cc\", \"\"),\n            RtcpFeedback::Unsupported => (\"unknown\", \"\"),\n        }\n    }\n}\n\n/// RTX stream information. It must contain a numeric ssrc field indicating the RTX SSRC.\n#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\npub struct RtpEncodingParametersRtx {\n    /// The media SSRC.\n    pub ssrc: u32,\n}\n\n/// Provides information relating to an encoding, which represents a media RTP\n/// stream and its associated RTX stream (if any).\n#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtpEncodingParameters {\n    /// The media SSRC.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub ssrc: Option<u32>,\n    /// The RID RTP extension value. Must be unique.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rid: Option<String>,\n    /// Codec payload type this encoding affects. If unset, first media codec is chosen.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub codec_payload_type: Option<u8>,\n    /// RTX stream information. It must contain a numeric ssrc field indicating the RTX SSRC.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub rtx: Option<RtpEncodingParametersRtx>,\n    /// It indicates whether discontinuous RTP transmission will be used. Useful for audio (if the\n    /// codec supports it) and for video screen sharing (when static content is being transmitted,\n    /// this option disables the RTP inactivity checks in mediasoup).\n    /// Default false.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub dtx: Option<bool>,\n    /// Number of spatial and temporal layers in the RTP stream.\n    #[serde(default, skip_serializing_if = \"ScalabilityMode::is_none\")]\n    pub scalability_mode: ScalabilityMode,\n    /// Maximum number of bits per second to allow a track encoded with this encoding to use.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub max_bitrate: Option<u32>,\n}\n\n/// Defines a RTP header extension within the RTP parameters. The list of RTP\n/// header extensions supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file.\n///\n/// mediasoup does not currently support encrypted RTP header extensions and no\n/// parameters are currently considered.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\npub struct RtpHeaderExtensionParameters {\n    /// The URI of the RTP header extension, as defined in RFC 5285.\n    pub uri: RtpHeaderExtensionUri,\n    /// The numeric identifier that goes in the RTP packet. Must be unique.\n    pub id: u16,\n    /// If true, the value in the header is encrypted as per RFC 6904.\n    /// Default false.\n    pub encrypt: bool,\n    // This field is not used by mediasoup currently\n    // /// Configuration parameters for the header extension.\n    // pub parameters: RtpCodecParametersParameters,\n}\n\n/// Provides information on RTCP settings within the RTP parameters.\n///\n/// If no cname is given in a producer's RTP parameters, the mediasoup transport will choose a\n/// random one that will be used into RTCP SDES messages sent to all its associated consumers.\n///\n/// mediasoup assumes `reduced_size` to always be true.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct RtcpParameters {\n    /// The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub cname: Option<String>,\n    /// Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP\n    /// as specified in RFC 3550 (if false). Default true.\n    pub reduced_size: bool,\n}\n\nimpl Default for RtcpParameters {\n    fn default() -> Self {\n        Self {\n            cname: None,\n            reduced_size: true,\n        }\n    }\n}\n"
  },
  {
    "path": "rust/types/src/scalability_modes/tests.rs",
    "content": "use super::*;\n\n#[test]\nfn parse_scalability_modes() {\n    let scalability_mode: ScalabilityMode = \"L1T3\".parse().unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(1).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    let scalability_mode: ScalabilityMode = \"L3T2_KEY\".parse().unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(2).unwrap()\n    );\n    assert!(scalability_mode.ksvc());\n\n    let scalability_mode: ScalabilityMode = \"S2T3\".parse().unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(2).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    assert_eq!(\n        \"foo\".parse::<ScalabilityMode>(),\n        Err(ParseScalabilityModeError::InvalidInput),\n    );\n\n    assert_eq!(\n        \"ull\".parse::<ScalabilityMode>(),\n        Err(ParseScalabilityModeError::InvalidInput),\n    );\n\n    assert_eq!(\n        \"S0T3\".parse::<ScalabilityMode>(),\n        Err(ParseScalabilityModeError::InvalidInput),\n    );\n\n    assert_eq!(\n        \"S1T0\".parse::<ScalabilityMode>(),\n        Err(ParseScalabilityModeError::InvalidInput),\n    );\n\n    let scalability_mode: ScalabilityMode = \"L20T3\".parse().unwrap();\n    assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. }));\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(20).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    assert_eq!(\n        \"S200T3\".parse::<ScalabilityMode>(),\n        Err(ParseScalabilityModeError::InvalidInput),\n    );\n\n    let scalability_mode: ScalabilityMode = \"L4T7_KEY_SHIFT\".parse().unwrap();\n    assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. }));\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(4).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(7).unwrap()\n    );\n    assert!(scalability_mode.ksvc());\n}\n\n#[test]\nfn parse_json_scalability_modes() {\n    let scalability_mode: ScalabilityMode = serde_json::from_str(\"\\\"L1T3\\\"\").unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(1).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    let scalability_mode: ScalabilityMode = serde_json::from_str(\"\\\"L3T2_KEY\\\"\").unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(2).unwrap()\n    );\n    assert!(scalability_mode.ksvc());\n\n    let scalability_mode: ScalabilityMode = serde_json::from_str(\"\\\"S2T3\\\"\").unwrap();\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(2).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    assert!(serde_json::from_str::<ScalabilityMode>(\"\\\"foo\\\"\").is_err());\n\n    assert!(serde_json::from_str::<ScalabilityMode>(\"\\\"ull\\\"\").is_err());\n\n    assert!(serde_json::from_str::<ScalabilityMode>(\"\\\"S0T3\\\"\").is_err());\n\n    assert!(serde_json::from_str::<ScalabilityMode>(\"\\\"S1T0\\\"\").is_err());\n\n    let scalability_mode: ScalabilityMode = \"L20T3\".parse().unwrap();\n    assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. }));\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(20).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(3).unwrap()\n    );\n    assert!(!scalability_mode.ksvc());\n\n    assert!(serde_json::from_str::<ScalabilityMode>(\"\\\"S200T3\\\"\").is_err());\n\n    let scalability_mode: ScalabilityMode = serde_json::from_str(\"\\\"L4T7_KEY_SHIFT\\\"\").unwrap();\n    assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. }));\n    assert_eq!(\n        scalability_mode.spatial_layers(),\n        NonZeroU8::new(4).unwrap()\n    );\n    assert_eq!(\n        scalability_mode.temporal_layers(),\n        NonZeroU8::new(7).unwrap()\n    );\n    assert!(scalability_mode.ksvc());\n}\n"
  },
  {
    "path": "rust/types/src/scalability_modes.rs",
    "content": "//! Scalability mode.\n\n#[cfg(test)]\nmod tests;\n\nuse once_cell::sync::OnceCell;\nuse regex::Regex;\nuse serde::{de, Deserialize, Deserializer, Serialize, Serializer};\nuse std::fmt;\nuse std::num::NonZeroU8;\nuse std::str::FromStr;\nuse thiserror::Error;\n\n/// Scalability mode.\n///\n/// Most modes match [webrtc-svc](https://w3c.github.io/webrtc-svc/), but custom ones are also\n/// supported by mediasoup.\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]\npub enum ScalabilityMode {\n    /// No scalability used, there is just one spatial and one temporal layer.\n    #[default]\n    None,\n    /// L1T2.\n    L1T2,\n    /// L1T2h.\n    L1T2h,\n    /// L1T3.\n    L1T3,\n    /// L1T3h.\n    L1T3h,\n    /// L2T1.\n    L2T1,\n    /// L2T1h.\n    L2T1h,\n    /// L2T1_KEY.\n    L2T1Key,\n    /// L2T2.\n    L2T2,\n    /// L2T2h.\n    L2T2h,\n    /// L2T2_KEY.\n    L2T2Key,\n    /// L2T2_KEY_SHIFT.\n    L2T2KeyShift,\n    /// L2T3.\n    L2T3,\n    /// L2T3h.\n    L2T3h,\n    /// L2T3_KEY.\n    L2T3Key,\n    /// L2T3_KEY_SHIFT.\n    L2T3KeyShift,\n    /// L3T1.\n    L3T1,\n    /// L3T1h.\n    L3T1h,\n    /// L3T1_KEY.\n    L3T1Key,\n    /// L3T2.\n    L3T2,\n    /// L3T2h.\n    L3T2h,\n    /// L3T2_KEY.\n    L3T2Key,\n    /// L3T2_KEY_SHIFT.\n    L3T2KeyShift,\n    /// L3T3.\n    L3T3,\n    /// L3T3h.\n    L3T3h,\n    /// L3T3_KEY.\n    L3T3Key,\n    /// L3T3_KEY_SHIFT.\n    L3T3KeyShift,\n    /// S2T1.\n    S2T1,\n    /// S2T1h.\n    S2T1h,\n    /// S2T2.\n    S2T2,\n    /// S2T2h.\n    S2T2h,\n    /// S2T3.\n    S2T3,\n    /// S2T3h.\n    S2T3h,\n    /// S3T1.\n    S3T1,\n    /// S3T1h.\n    S3T1h,\n    /// S3T2.\n    S3T2,\n    /// S3T2h.\n    S3T2h,\n    /// S3T3.\n    S3T3,\n    /// S3T3h.\n    S3T3h,\n    /// Custom scalability mode not defined in [webrtc-svc](https://w3c.github.io/webrtc-svc/).\n    Custom {\n        /// Scalability mode as string\n        scalability_mode: String,\n        /// Number of spatial layers.\n        spatial_layers: NonZeroU8,\n        /// Number of temporal layers.\n        temporal_layers: NonZeroU8,\n        /// K-SVC mode.\n        ksvc: bool,\n    },\n}\n\n/// Error that caused [`ScalabilityMode`] parsing error.\n#[derive(Debug, Error, Eq, PartialEq)]\npub enum ParseScalabilityModeError {\n    /// Invalid input string\n    #[error(\"Invalid Scalability Mode input string\")]\n    InvalidInput,\n}\n\nimpl FromStr for ScalabilityMode {\n    type Err = ParseScalabilityModeError;\n\n    fn from_str(scalability_mode: &str) -> Result<Self, Self::Err> {\n        Ok(match scalability_mode {\n            \"S1T1\" => Self::None,\n            \"L1T2\" => Self::L1T2,\n            \"L1T2h\" => Self::L1T2h,\n            \"L1T3\" => Self::L1T3,\n            \"L1T3h\" => Self::L1T3h,\n            \"L2T1\" => Self::L2T1,\n            \"L2T1h\" => Self::L2T1h,\n            \"L2T1_KEY\" => Self::L2T1Key,\n            \"L2T2\" => Self::L2T2,\n            \"L2T2h\" => Self::L2T2h,\n            \"L2T2_KEY\" => Self::L2T2Key,\n            \"L2T2_KEY_SHIFT\" => Self::L2T2KeyShift,\n            \"L2T3\" => Self::L2T3,\n            \"L2T3h\" => Self::L2T3h,\n            \"L2T3_KEY\" => Self::L2T3Key,\n            \"L2T3_KEY_SHIFT\" => Self::L2T3KeyShift,\n            \"L3T1\" => Self::L3T1,\n            \"L3T1h\" => Self::L3T1h,\n            \"L3T1_KEY\" => Self::L3T1Key,\n            \"L3T2\" => Self::L3T2,\n            \"L3T2h\" => Self::L3T2h,\n            \"L3T2_KEY\" => Self::L3T2Key,\n            \"L3T2_KEY_SHIFT\" => Self::L3T2KeyShift,\n            \"L3T3\" => Self::L3T3,\n            \"L3T3h\" => Self::L3T3h,\n            \"L3T3_KEY\" => Self::L3T3Key,\n            \"L3T3_KEY_SHIFT\" => Self::L3T3KeyShift,\n            \"S2T1\" => Self::S2T1,\n            \"S2T1h\" => Self::S2T1h,\n            \"S2T2\" => Self::S2T2,\n            \"S2T2h\" => Self::S2T2h,\n            \"S2T3\" => Self::S2T3,\n            \"S2T3h\" => Self::S2T3h,\n            \"S3T1\" => Self::S3T1,\n            \"S3T1h\" => Self::S3T1h,\n            \"S3T2\" => Self::S3T2,\n            \"S3T2h\" => Self::S3T2h,\n            \"S3T3\" => Self::S3T3,\n            \"S3T3h\" => Self::S3T3h,\n            scalability_mode => {\n                static SCALABILITY_MODE_REGEX: OnceCell<Regex> = OnceCell::new();\n\n                SCALABILITY_MODE_REGEX\n                    .get_or_init(|| Regex::new(r\"^[LS]([1-9][0-9]?)T([1-9][0-9]?)(_KEY)?\").unwrap())\n                    .captures(scalability_mode)\n                    .map(|captures| Self::Custom {\n                        scalability_mode: scalability_mode.to_string(),\n                        spatial_layers: captures.get(1).unwrap().as_str().parse().unwrap(),\n                        temporal_layers: captures.get(2).unwrap().as_str().parse().unwrap(),\n                        ksvc: captures.get(3).is_some(),\n                    })\n                    .ok_or(ParseScalabilityModeError::InvalidInput)?\n            }\n        })\n    }\n}\n\nimpl std::fmt::Display for ScalabilityMode {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.as_str())\n    }\n}\n\nimpl ScalabilityMode {\n    /// Returns true if there is no scalability.\n    pub fn is_none(&self) -> bool {\n        matches!(self, Self::None)\n    }\n\n    /// Number of spatial layers.\n    pub fn spatial_layers(&self) -> NonZeroU8 {\n        match self {\n            Self::None | Self::L1T2 | Self::L1T2h | Self::L1T3 | Self::L1T3h => {\n                NonZeroU8::new(1).unwrap()\n            }\n            Self::L2T1\n            | Self::L2T1h\n            | Self::L2T1Key\n            | Self::L2T2\n            | Self::L2T2h\n            | Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L2T3\n            | Self::L2T3h\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::S2T1\n            | Self::S2T1h\n            | Self::S2T2\n            | Self::S2T2h\n            | Self::S2T3\n            | Self::S2T3h => NonZeroU8::new(2).unwrap(),\n            Self::L3T1\n            | Self::L3T1h\n            | Self::L3T1Key\n            | Self::L3T2\n            | Self::L3T2h\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::L3T3\n            | Self::L3T3h\n            | Self::L3T3Key\n            | Self::L3T3KeyShift\n            | Self::S3T1\n            | Self::S3T1h\n            | Self::S3T2\n            | Self::S3T2h\n            | Self::S3T3\n            | Self::S3T3h => NonZeroU8::new(3).unwrap(),\n            Self::Custom { spatial_layers, .. } => *spatial_layers,\n        }\n    }\n\n    /// Number of temporal layers.\n    pub fn temporal_layers(&self) -> NonZeroU8 {\n        match self {\n            Self::None\n            | Self::L2T1\n            | Self::L2T1h\n            | Self::L2T1Key\n            | Self::L3T1\n            | Self::L3T1h\n            | Self::L3T1Key\n            | Self::S2T1\n            | Self::S2T1h\n            | Self::S3T1\n            | Self::S3T1h => NonZeroU8::new(1).unwrap(),\n            Self::L1T2\n            | Self::L1T2h\n            | Self::L2T2\n            | Self::L2T2h\n            | Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L3T2\n            | Self::L3T2h\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::S2T2\n            | Self::S2T2h\n            | Self::S3T2\n            | Self::S3T2h => NonZeroU8::new(2).unwrap(),\n            Self::L1T3\n            | Self::L1T3h\n            | Self::L2T3\n            | Self::L2T3h\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::L3T3\n            | Self::L3T3h\n            | Self::L3T3Key\n            | Self::L3T3KeyShift\n            | Self::S2T3\n            | Self::S2T3h\n            | Self::S3T3\n            | Self::S3T3h => NonZeroU8::new(3).unwrap(),\n            Self::Custom {\n                temporal_layers, ..\n            } => *temporal_layers,\n        }\n    }\n\n    /// K-SVC mode.\n    pub fn ksvc(&self) -> bool {\n        match self {\n            Self::L2T2Key\n            | Self::L2T2KeyShift\n            | Self::L2T3Key\n            | Self::L2T3KeyShift\n            | Self::L3T1Key\n            | Self::L3T2Key\n            | Self::L3T2KeyShift\n            | Self::L3T3Key\n            | Self::L3T3KeyShift => true,\n            Self::Custom { ksvc, .. } => *ksvc,\n            _ => false,\n        }\n    }\n\n    /// String representation of scalability mode.\n    pub fn as_str(&self) -> &str {\n        match self {\n            Self::None => \"S1T1\",\n            Self::L1T2 => \"L1T2\",\n            Self::L1T2h => \"L1T2h\",\n            Self::L1T3 => \"L1T3\",\n            Self::L1T3h => \"L1T3h\",\n            Self::L2T1 => \"L2T1\",\n            Self::L2T1h => \"L2T1h\",\n            Self::L2T1Key => \"L2T1_KEY\",\n            Self::L2T2 => \"L2T2\",\n            Self::L2T2h => \"L2T2h\",\n            Self::L2T2Key => \"L2T2_KEY\",\n            Self::L2T2KeyShift => \"L2T2_KEY_SHIFT\",\n            Self::L2T3 => \"L2T3\",\n            Self::L2T3h => \"L2T3h\",\n            Self::L2T3Key => \"L2T3_KEY\",\n            Self::L2T3KeyShift => \"L2T3_KEY_SHIFT\",\n            Self::L3T1 => \"L3T1\",\n            Self::L3T1h => \"L3T1h\",\n            Self::L3T1Key => \"L3T1_KEY\",\n            Self::L3T2 => \"L3T2\",\n            Self::L3T2h => \"L3T2h\",\n            Self::L3T2Key => \"L3T2_KEY\",\n            Self::L3T2KeyShift => \"L3T2_KEY_SHIFT\",\n            Self::L3T3 => \"L3T3\",\n            Self::L3T3h => \"L3T3h\",\n            Self::L3T3Key => \"L3T3_KEY\",\n            Self::L3T3KeyShift => \"L3T3_KEY_SHIFT\",\n            Self::S2T1 => \"S2T1\",\n            Self::S2T1h => \"S2T1h\",\n            Self::S2T2 => \"S2T2\",\n            Self::S2T2h => \"S2T2h\",\n            Self::S2T3 => \"S2T3\",\n            Self::S2T3h => \"S2T3h\",\n            Self::S3T1 => \"S3T1\",\n            Self::S3T1h => \"S3T1h\",\n            Self::S3T2 => \"S3T2\",\n            Self::S3T2h => \"S3T2h\",\n            Self::S3T3 => \"S3T3\",\n            Self::S3T3h => \"S3T3h\",\n            Self::Custom {\n                scalability_mode, ..\n            } => scalability_mode,\n        }\n    }\n}\n\nimpl<'de> Deserialize<'de> for ScalabilityMode {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        struct ScalabilityModeVisitor;\n\n        impl<'de> de::Visitor<'de> for ScalabilityModeVisitor {\n            type Value = ScalabilityMode;\n\n            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {\n                formatter.write_str(r#\"Scalability mode string like \"S1T3\"\"#)\n            }\n\n            #[inline]\n            fn visit_none<E>(self) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                Ok(ScalabilityMode::None)\n            }\n\n            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>\n            where\n                E: de::Error,\n            {\n                v.parse().map_err(de::Error::custom)\n            }\n        }\n\n        deserializer.deserialize_str(ScalabilityModeVisitor)\n    }\n}\n\nimpl Serialize for ScalabilityMode {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        serializer.serialize_str(self.as_str())\n    }\n}\n"
  },
  {
    "path": "rust/types/src/sctp_parameters.rs",
    "content": "//! Collection of SCTP-related data structures that are used to specify SCTP association parameters.\n\nuse serde::{Deserialize, Serialize};\n\n/// Number of SCTP streams.\n///\n/// Both OS and MIS are part of the SCTP INIT+ACK handshake. OS refers to the initial number of\n/// outgoing SCTP streams that the server side transport creates (to be used by\n/// [DataConsumer](https://docs.rs/mediasoup/latest/mediasoup/data_consumer/enum.DataConsumer.html)s),\n/// while MIS refers to the maximum number of incoming SCTP streams that the server side transport\n/// can receive (to be used by [DataProducer](https://docs.rs/mediasoup/latest/mediasoup/data_producer/enum.DataProducer.html)s).\n/// So, if the server side transport will just re used to create data producers (but no data consumers),\n/// OS can be low (~1). However, if data consumers are desired on the server side transport, OS must\n/// have a proper value and such a proper value depends on whether the remote endpoint supports\n/// `SCTP_ADD_STREAMS` extension or not.\n///\n/// libwebrtc (Chrome, Safari, etc) does not enable `SCTP_ADD_STREAMS` so, if data consumers are\n/// required,  OS should be 1024 (the maximum number of DataChannels that libwebrtc enables).\n///\n/// Firefox does enable `SCTP_ADD_STREAMS` so, if data consumers are required, OS can be lower (16\n/// for instance). The mediasoup transport will allocate and announce more outgoing SCTP streams\n/// when needed.\n///\n/// mediasoup-client provides specific per browser/version OS and MIS values via the\n/// device.sctpCapabilities getter.\n#[derive(Debug, Serialize, Copy, Clone)]\npub struct NumSctpStreams {\n    /// Initially requested number of outgoing SCTP streams.\n    #[serde(rename = \"OS\")]\n    pub os: u16,\n    /// Maximum number of incoming SCTP streams.\n    #[serde(rename = \"MIS\")]\n    pub mis: u16,\n}\n\nimpl Default for NumSctpStreams {\n    fn default() -> Self {\n        Self {\n            os: 1024,\n            mis: 1024,\n        }\n    }\n}\n\n/// Parameters of the SCTP association.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SctpParameters {\n    /// Must always equal 5000.\n    pub port: u16,\n    /// Initially requested number of outgoing SCTP streams.\n    #[serde(rename = \"OS\")]\n    pub os: u16,\n    /// Maximum number of incoming SCTP streams.\n    #[serde(rename = \"MIS\")]\n    pub mis: u16,\n    /// Maximum allowed size for SCTP messages.\n    pub max_message_size: u32,\n}\n\n/// SCTP stream parameters describe the reliability of a certain SCTP stream.\n///\n/// If ordered is true then `max_packet_life_time` and `max_retransmits` must be `false`.\n/// If ordered if false, only one of `max_packet_life_time` or max_retransmits can be `true`.\n#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SctpStreamParameters {\n    /// SCTP stream id.\n    pub stream_id: u16,\n    /// Whether data messages must be received in order. If `true` the messages will be sent\n    /// reliably.\n    /// Default true.\n    pub ordered: bool,\n    /// When `ordered` is `false` indicates the time (in milliseconds) after which a SCTP packet\n    /// will stop being retransmitted.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub max_packet_life_time: Option<u16>,\n    /// When `ordered` is `false` indicates the maximum number of times a packet will be\n    /// retransmitted.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub max_retransmits: Option<u16>,\n}\n\nimpl SctpStreamParameters {\n    /// SCTP stream id.\n    #[must_use]\n    pub fn stream_id(&self) -> u16 {\n        self.stream_id\n    }\n\n    /// Whether data messages must be received in order. If `true` the messages will be sent\n    /// reliably.\n    #[must_use]\n    pub fn ordered(&self) -> bool {\n        self.ordered\n    }\n\n    /// When `ordered` is `false` indicates the time (in milliseconds) after which a SCTP packet\n    /// will stop being retransmitted.\n    #[must_use]\n    pub fn max_packet_life_time(&self) -> Option<u16> {\n        self.max_packet_life_time\n    }\n\n    /// When `ordered` is `false` indicates the maximum number of times a packet will be\n    /// retransmitted.\n    #[must_use]\n    pub fn max_retransmits(&self) -> Option<u16> {\n        self.max_retransmits\n    }\n}\n\nimpl SctpStreamParameters {\n    /// Messages will be sent reliably in order.\n    #[must_use]\n    pub fn new_ordered(stream_id: u16) -> Self {\n        Self {\n            stream_id,\n            ordered: true,\n            max_packet_life_time: None,\n            max_retransmits: None,\n        }\n    }\n\n    /// Messages will be sent unreliably with time (in milliseconds) after which a SCTP packet will\n    /// stop being retransmitted.\n    #[must_use]\n    pub fn new_unordered_with_life_time(stream_id: u16, max_packet_life_time: u16) -> Self {\n        Self {\n            stream_id,\n            ordered: false,\n            max_packet_life_time: Some(max_packet_life_time),\n            max_retransmits: None,\n        }\n    }\n\n    /// Messages will be sent unreliably with a limited number of retransmission attempts.\n    #[must_use]\n    pub fn new_unordered_with_retransmits(stream_id: u16, max_retransmits: u16) -> Self {\n        Self {\n            stream_id,\n            ordered: false,\n            max_packet_life_time: None,\n            max_retransmits: Some(max_retransmits),\n        }\n    }\n}\n"
  },
  {
    "path": "rust/types/src/srtp_parameters.rs",
    "content": "//! Collection of SRTP-related data structures that are used to specify SRTP encryption/decryption\n//! parameters.\n\nuse serde::{Deserialize, Serialize};\n\n/// SRTP parameters.\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]\n#[serde(rename_all = \"camelCase\")]\npub struct SrtpParameters {\n    /// Encryption and authentication transforms to be used.\n    pub crypto_suite: SrtpCryptoSuite,\n    /// SRTP keying material (master key and salt) in Base64.\n    pub key_base64: String,\n}\n\n/// SRTP crypto suite.\n#[derive(\n    Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Default,\n)]\npub enum SrtpCryptoSuite {\n    /// AEAD_AES_256_GCM\n    #[serde(rename = \"AEAD_AES_256_GCM\")]\n    AeadAes256Gcm,\n    /// AEAD_AES_128_GCM\n    #[serde(rename = \"AEAD_AES_128_GCM\")]\n    AeadAes128Gcm,\n    /// AES_CM_128_HMAC_SHA1_80\n    #[serde(rename = \"AES_CM_128_HMAC_SHA1_80\")]\n    #[default]\n    AesCm128HmacSha180,\n    /// AES_CM_128_HMAC_SHA1_32\n    #[serde(rename = \"AES_CM_128_HMAC_SHA1_32\")]\n    AesCm128HmacSha132,\n}\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"1.94.1\"\ncomponents = [\"rustfmt\", \"clippy\"]\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compileOnSave\": true,\n\t\"compilerOptions\": {\n\t\t\"rootDir\": \"node/src\",\n\t\t\"outDir\": \"node/lib\",\n\t\t\"target\": \"ES2024\",\n\t\t\"lib\": [\"ES2024\"],\n\t\t\"types\": [\"node\", \"jest\"],\n\t\t\"module\": \"NodeNext\",\n\t\t\"moduleResolution\": \"NodeNext\",\n\t\t\"declaration\": true,\n\t\t\"declarationMap\": true,\n\t\t\"declarationDir\": \"node/lib\",\n\t\t\"isolatedModules\": true,\n\t\t\"allowUnreachableCode\": false,\n\t\t\"allowUnusedLabels\": false,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noPropertyAccessFromIndexSignature\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\t\t\"strict\": true\n\t},\n\t\"include\": [\"node/src\"]\n}\n"
  },
  {
    "path": "worker/.clang-format",
    "content": "Language: Cpp\nAccessModifierOffset: -2\nAlignAfterOpenBracket: AlwaysBreak\nAlignArrayOfStructures: Left\nAlignConsecutiveAssignments: Consecutive\nAlignConsecutiveDeclarations: None\nAlignOperands: true\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: Never\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: None\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLambdasOnASingleLine: None\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: Yes\nBinPackArguments: false\nBinPackParameters: false\n# NOTE: This doesn't do anything because it requires Cpp11BracedListStyle: true,\n# which we don't want.\nBreakAfterOpenBracketBracedList: true\nBraceWrapping:\n  AfterClass: true\n  AfterControlStatement: Always\n  AfterEnum: true\n  AfterFunction: true\n  AfterNamespace: true\n  AfterStruct: true\n  AfterUnion: true\n  AfterCaseLabel: true\n  AfterExternBlock: true\n  BeforeCatch: true\n  BeforeElse: true\n  IndentBraces: false\nBreakAfterReturnType: Automatic\nBreakBeforeBraces: Allman\nBreakBeforeBinaryOperators: None\nBreakBeforeInheritanceComma: false\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nBreakStringLiterals: false\nColumnLimit: 100\nQualifierAlignment: Left\nCommentPragmas: 'NOLINT'\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 2\nContinuationIndentWidth: 2\nCpp11BracedListStyle: false\nDerivePointerAlignment: false\nDisableFormat: false\nExperimentalAutoDetectBinPacking: true\nFixNamespaceComments: true\nIncludeCategories:\n  - Regex: '\"common.hpp\"'\n    Priority: 1\n  - Regex: '^\"(Channel|PayloadChannel|FBS|RTC|Utils|handles)/'\n    Priority: 3\n  - Regex: '\"*\"'\n    Priority: 2\n  - Regex: '^<(flatbuffers|uv|openssl|srtp|usrsctp|libwebrtc)(.|/)'\n    Priority: 4\n  - Regex: '<*>'\n    Priority: 5\nIncludeIsMainRegex: '$'\nIndentCaseLabels: true\nIndentWidth: 2\nIndentWrappedFunctionNames: false\nInsertBraces: true\nKeepEmptyLinesAtTheStartOfBlocks: false\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: All\nPenaltyBreakBeforeFirstCallParameter: 5\nPenaltyBreakComment: 100\nPenaltyBreakFirstLessLess: 200\nPenaltyBreakString: 20\nPenaltyExcessCharacter: 10\nPenaltyReturnTypeOnItsOwnLine: 1000\nPointerAlignment: Left\nReflowComments: Always\nSortIncludes: CaseSensitive\nSpaceAfterCStyleCast: false\nSpaceAfterTemplateKeyword: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles: false\nSpacesInCStyleCastParentheses: false\nSpacesInContainerLiterals: true\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard: c++11\nTabWidth: 2\nUseTab: ForIndentation\n"
  },
  {
    "path": "worker/.clang-tidy",
    "content": "---\nChecks: \"*,\\\n  -altera*,\\\n  -android*,\\\n  -boost-use-to-string,\\\n  -boost-use-ranges,\\\n  -bugprone-branch-clone,\\\n  -bugprone-easily-swappable-parameters,\\\n  -bugprone-implicit-widening-of-multiplication-result,\\\n  -bugprone-lambda-function-name,\\\n  -bugprone-macro-parentheses,\\\n  -bugprone-narrowing-conversions,\\\n  -bugprone-reserved-identifier,\\\n  -cert-*,\\\n  -clang-analyzer-optin.osx.*,\\\n  -clang-analyzer-osx.*,\\\n  -concurrency-mt-unsafe,\\\n  -cppcoreguidelines-avoid-c-arrays,\\\n  -cppcoreguidelines-avoid-do-while,\\\n  -cppcoreguidelines-avoid-goto,\\\n  -cppcoreguidelines-avoid-non-const-global-variables,\\\n  -cppcoreguidelines-avoid-magic-numbers,\\\n  -cppcoreguidelines-init-variables,\\\n  -cppcoreguidelines-narrowing-conversions,\\\n  -cppcoreguidelines-no-malloc,\\\n  -cppcoreguidelines-non-private-member-variables-in-classes,\\\n  -cppcoreguidelines-owning-memory,\\\n  -cppcoreguidelines-prefer-member-initializer,\\\n  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\\\n  -cppcoreguidelines-pro-bounds-constant-array-index,\\\n  -cppcoreguidelines-pro-bounds-pointer-arithmetic,\\\n  -cppcoreguidelines-pro-type-const-cast,\\\n  -cppcoreguidelines-pro-type-reinterpret-cast,\\\n  -cppcoreguidelines-pro-type-static-cast-downcast,\\\n  -cppcoreguidelines-pro-type-union-access,\\\n  -cppcoreguidelines-pro-type-vararg,\\\n  -cppcoreguidelines-special-member-functions,\\\n  -hicpp-exception-baseclass,\\\n  -hicpp-no-malloc,\\\n  -hicpp-function-size,\\\n  -fuchsia-default-arguments-calls,\\\n  -fuchsia-default-arguments-declarations,\\\n  -fuchsia-overloaded-operator,\\\n  -fuchsia-statically-constructed-objects,\\\n  -google-build-using-namespace,\\\n  -google-default-arguments,\\\n  -google-readability-*,\\\n  -google-runtime-int,\\\n  -google-upgrade-googletest-case,\\\n  -hicpp-avoid-c-arrays,\\\n  -hicpp-avoid-goto,\\\n  -hicpp-braces-around-statements,\\\n  -hicpp-no-array-decay,\\\n  -hicpp-signed-bitwise,\\\n  -hicpp-special-member-functions,\\\n  -hicpp-uppercase-literal-suffix,\\\n  -hicpp-vararg,\\\n  -llvm-include-order,\\\n  -llvm-header-guard,\\\n  -llvm-else-after-return,\\\n  -llvm-prefer-static-over-anonymous-namespace,\\\n  -llvmlibc-*,\\\n  -misc-confusable-identifiers,\\\n  -misc-include-cleaner,\\\n  -misc-non-private-member-variables-in-classes,\\\n  -misc-use-anonymous-namespace,\\\n  -modernize-avoid-c-arrays,\\\n  -modernize-concat-nested-namespaces,\\\n  -modernize-make-unique,\\\n  -modernize-pass-by-value,\\\n  -modernize-use-nodiscard, \\\n  -modernize-use-trailing-return-type,\\\n  -performance-avoid-endl,\\\n  -performance-no-int-to-ptr,\\\n  -portability-template-virtual-member-function,\\\n  -readability-convert-member-functions-to-static,\\\n  -readability-else-after-return,\\\n  -readability-function-cognitive-complexity,\\\n  -readability-function-size,\\\n  -readability-identifier-length,\\\n  -readability-implicit-bool-conversion,\\\n  -readability-magic-numbers,\\\n  -readability-redundant-access-specifiers, \\\n  -readability-simplify-boolean-expr,\\\n  -readability-uppercase-literal-suffix,\\\n  \"\nHeaderFilterRegex: '.*/worker/(include|test/include|fuzzer/include)/.*'\nFormatStyle: 'file'\nUser: mediasoup\nCheckOptions:\n  - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader\n    value: ''\n  - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle\n    value: '0'\n  - key: cppcoreguidelines-macro-usage.AllowedRegexp\n    value: ^MS_*\n  - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries\n    value: '1'\n  - key: modernize-loop-convert.MaxCopySize\n    value: '16'\n  - key: modernize-loop-convert.MinConfidence\n    value: reasonable\n  - key: modernize-loop-convert.NamingStyle\n    value: CamelCase\n  - key: modernize-replace-auto-ptr.IncludeStyle\n    value: llvm\n  - key: modernize-use-nullptr.NullMacros\n    value: 'NULL'\n  - key: readability-braces-around-statements.ShortStatementLines\n    value: '3'\n  - key: readability-identifier-naming.AbstractClassCase\n    value: CamelCase\n  - key: readability-identifier-naming.AbstractClassPrefix\n    value: ''\n  - key: readability-identifier-naming.AbstractClassSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassCase\n    value: CamelCase\n  - key: readability-identifier-naming.ClassConstantCase\n    value: CamelCase\n  - key: readability-identifier-naming.ClassConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassMemberCase\n    value: camelBack\n  - key: readability-identifier-naming.ClassMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassMethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.ClassMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ClassPrefix\n    value: ''\n  - key: readability-identifier-naming.ClassSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantCase\n    value: UPPER_CASE\n  - key: readability-identifier-naming.ConstantMemberCase\n    value: camelBack\n  - key: readability-identifier-naming.ConstantMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantParameterCase\n    value: camelBack\n  - key: readability-identifier-naming.ConstantParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprFunctionCase\n    value: camelBack\n  - key: readability-identifier-naming.ConstexprFunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprFunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprMethodCase\n    value: camelBack\n  - key: readability-identifier-naming.ConstexprMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ConstexprVariableCase\n    value: CamelCase\n  - key: readability-identifier-naming.ConstexprVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.ConstexprVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.EnumCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.EnumConstantCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.EnumConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.EnumConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.EnumPrefix\n    value: ''\n  - key: readability-identifier-naming.EnumSuffix\n    value: ''\n  - key: readability-identifier-naming.FunctionCase\n    value: camelBack\n  - key: readability-identifier-naming.FunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.FunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalConstantCase\n    value: CamelCase\n  - key: readability-identifier-naming.GlobalConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalFunctionCase\n    value: CamelCase\n  - key: readability-identifier-naming.GlobalFunctionPrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalFunctionSuffix\n    value: ''\n  - key: readability-identifier-naming.GlobalVariableCase\n    value: CamelCase\n  - key: readability-identifier-naming.GlobalVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.GlobalVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.IgnoreFailedSplit\n    value: '0'\n  - key: readability-identifier-naming.InlineNamespaceCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.InlineNamespacePrefix\n    value: ''\n  - key: readability-identifier-naming.InlineNamespaceSuffix\n    value: ''\n  - key: readability-identifier-naming.LocalConstantCase\n    value: camelBack\n  - key: readability-identifier-naming.LocalConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.LocalConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.LocalVariableCase\n    value: camelBack\n  - key: readability-identifier-naming.LocalVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.LocalVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.MemberCase\n    value: camelBack\n  - key: readability-identifier-naming.MemberPrefix\n    value: ''\n  - key: readability-identifier-naming.MemberSuffix\n    value: ''\n  - key: readability-identifier-naming.MethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.MethodPrefix\n    value: ''\n  - key: readability-identifier-naming.MethodSuffix\n    value: ''\n  - key: readability-identifier-naming.NamespaceCase\n    value: aNy_CasE\n  - key: readability-identifier-naming.NamespacePrefix\n    value: ''\n  - key: readability-identifier-naming.NamespaceSuffix\n    value: ''\n  - key: readability-identifier-naming.ParameterCase\n    value: camelBack\n  - key: readability-identifier-naming.ParameterPackCase\n    value: camelBack\n  - key: readability-identifier-naming.ParameterPackPrefix\n    value: ''\n  - key: readability-identifier-naming.ParameterPackSuffix\n    value: ''\n  - key: readability-identifier-naming.ParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.PrivateMemberCase\n    value: camelBack\n  - key: readability-identifier-naming.PrivateMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.PrivateMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.PrivateMethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.PrivateMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.PrivateMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMemberCase\n    value: camelBack\n  - key: readability-identifier-naming.ProtectedMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.ProtectedMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.ProtectedMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.PublicMemberCase\n    value: camelBack\n  - key: readability-identifier-naming.PublicMemberPrefix\n    value: ''\n  - key: readability-identifier-naming.PublicMemberSuffix\n    value: ''\n  - key: readability-identifier-naming.PublicMethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.PublicMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.PublicMethodSuffix\n    value: ''\n  - key: readability-identifier-naming.StaticConstantCase\n    value: CamelCase\n  - key: readability-identifier-naming.StaticConstantPrefix\n    value: ''\n  - key: readability-identifier-naming.StaticConstantSuffix\n    value: ''\n  - key: readability-identifier-naming.StaticVariableCase\n    value: camelBack\n  - key: readability-identifier-naming.StaticVariablePrefix\n    value: ''\n  - key: readability-identifier-naming.StaticVariableSuffix\n    value: ''\n  - key: readability-identifier-naming.StructCase\n    value: CamelCase\n  - key: readability-identifier-naming.StructPrefix\n    value: ''\n  - key: readability-identifier-naming.StructSuffix\n    value: ''\n  - key: readability-identifier-naming.TemplateParameterCase\n    value: CamelCase\n  - key: readability-identifier-naming.TemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TemplateTemplateParameterCase\n    value: CamelCase\n  - key: readability-identifier-naming.TemplateTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TemplateTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TypeTemplateParameterCase\n    value: CamelCase\n  - key: readability-identifier-naming.TypeTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.TypeTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.TypedefCase\n    value: CamelCase\n  - key: readability-identifier-naming.TypedefPrefix\n    value: ''\n  - key: readability-identifier-naming.TypedefSuffix\n    value: ''\n  - key: readability-identifier-naming.UnionCase\n    value: CamelCase\n  - key: readability-identifier-naming.UnionPrefix\n    value: ''\n  - key: readability-identifier-naming.UnionSuffix\n    value: ''\n  - key: readability-identifier-naming.ValueTemplateParameterCase\n    value: CamelCase\n  - key: readability-identifier-naming.ValueTemplateParameterPrefix\n    value: ''\n  - key: readability-identifier-naming.ValueTemplateParameterSuffix\n    value: ''\n  - key: readability-identifier-naming.VariableCase\n    value: camelBack\n  - key: readability-identifier-naming.VariablePrefix\n    value: ''\n  - key: readability-identifier-naming.VariableSuffix\n    value: ''\n  - key: readability-identifier-naming.VirtualMethodCase\n    value: CamelCase\n  - key: readability-identifier-naming.VirtualMethodPrefix\n    value: ''\n  - key: readability-identifier-naming.VirtualMethodSuffix\n    value: ''\n  - key: readability-simplify-boolean-expr.ChainedConditionalAssignment\n    value: '1'\n  - key: readability-simplify-boolean-expr.ChainedConditionalReturn\n    value: '1'\n"
  },
  {
    "path": "worker/.clangd",
    "content": "CompileFlags:\n  CompilationDatabase: \"out/Release/build\"\n\nCompletionOptions:\n  HeaderInsertion: Never\n"
  },
  {
    "path": "worker/Cargo.toml",
    "content": "[package]\nname = \"mediasoup-sys\"\nversion = \"0.11.0\"\ndescription = \"FFI bindings to C++ libmediasoup-worker\"\nauthors = [\n    \"Nazar Mokrynskyi <nazar@mokrynskyi.com>\",\n    \"José Luis Millán <jmillan@aliax.net>\",\n    \"Iñaki Baz Castillo <ibc@aliax.net>\"\n]\nedition = \"2021\"\nlicense = \"ISC\"\ndocumentation = \"https://docs.rs/mediasoup-sys\"\nrepository = \"https://github.com/versatica/mediasoup\"\ninclude = [\n    \"/deps/libwebrtc\",\n    \"/fbs\",\n    \"/fuzzer/include\",\n    \"/fuzzer/src\",\n    \"/include\",\n    \"/scripts\",\n    \"!/scripts/node_modules\",\n    \"/src\",\n    \"/subprojects/*.wrap\",\n    \"/test/include\",\n    \"/test/src\",\n    \"/build.rs\",\n    \"/Cargo.toml\",\n    \"/meson.build\",\n    \"/meson_options.txt\",\n    \"/tasks.py\"\n]\n\n[package.metadata.docs.rs]\ndefault-target = \"x86_64-unknown-linux-gnu\"\ntargets = []\n\n[build-dependencies]\nplanus-codegen = \"0.4.0\"\nplanus-translation = \"0.4.0\"\n\n[dependencies]\nplanus = \"0.4.0\"\n\n[dependencies.serde]\nfeatures = [\"derive\"]\nversion = \"1.0.190\"\n"
  },
  {
    "path": "worker/Dockerfile",
    "content": "FROM ubuntu:26.04\n\n# Install dependencies.\nRUN set -x \\\n\t&& apt-get update \\\n\t&& apt-get install --yes \\\n\t\tclang-21 make pkg-config bash-completion wget curl git screen python3-pip python3-yaml \\\n\t\tzlib1g-dev libgss-dev libssl-dev libxml2-dev gdb procps file clang-format-22 clang-tidy-21\n\n# Install node 24.\nRUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \\\n\t&& apt-get install --yes nodejs\n\n# Enable core dumps.\nRUN set -x \\\n\t&& echo \"mkdir -p /tmp/cores && chmod 777 /tmp/cores && echo \\\"/tmp/cores/core.%e.sig%s.%p\\\" > /proc/sys/kernel/core_pattern && ulimit -c unlimited\" >> ~/.bashrc\n\n# Make CC and CXX point to clang/clang++ installed above.\nENV LANG=\"C.UTF-8\"\nENV CC=\"clang-21\"\nENV CXX=\"clang++-21\"\n\nENV MEDIASOUP_LOCAL_DEV=\"true\"\nENV KEEP_BUILD_ARTIFACTS=\"1\"\n\nWORKDIR \"/foo bar/mediasoup\"\n\nCMD [\"bash\"]\n"
  },
  {
    "path": "worker/Dockerfile.386",
    "content": "#\n# This container installs a 32-bits Linux Debian.\n#\n\nFROM debian:bookworm\n\n# Install dependencies.\nRUN set -x \\\n\t&& apt-get update \\\n\t&& apt-get install --yes \\\n\t\tmake pkg-config bash-completion wget curl git screen python3-pip python3-yaml \\\n\t\tzlib1g-dev libgss-dev libssl-dev libxml2-dev gdb nodejs npm\n\n# Enable core dumps.\nRUN set -x \\\n\t&& echo \"mkdir -p /tmp/cores && chmod 777 /tmp/cores && echo \\\"/tmp/cores/core.%e.sig%s.%p\\\" > /proc/sys/kernel/core_pattern && ulimit -c unlimited\" >> ~/.bashrc\n\nENV LANG=\"C.UTF-8\"\n\nENV MEDIASOUP_LOCAL_DEV=\"true\"\nENV KEEP_BUILD_ARTIFACTS=\"1\"\n\nWORKDIR \"/foo bar/mediasoup\"\n\nCMD [\"bash\"]\n"
  },
  {
    "path": "worker/Dockerfile.alpine",
    "content": "FROM alpine\n\n# Install dependencies.\nRUN set -x \\\n\t&& apk add gcc g++ make nodejs-current npm python3 py3-pip linux-headers\n\n# Make CC and CXX point to gcc/g++.\nENV LANG=\"C.UTF-8\"\nENV CC=\"gcc\"\nENV CXX=\"g++\"\n\nENV MEDIASOUP_LOCAL_DEV=\"true\"\nENV KEEP_BUILD_ARTIFACTS=\"1\"\n\nWORKDIR \"/foo bar/mediasoup\"\n\nCMD [\"ash\"]\n"
  },
  {
    "path": "worker/Makefile",
    "content": "#\n# make tasks for mediasoup-worker.\n#\n# NOTE: This Makefile is a proxy to pip invoke commands (see tasks.py).\n#\n\nPYTHON ?= $(shell command -v python3 2> /dev/null || echo python)\nPIP_INVOKE_DIR = $(shell pwd)/pip_invoke\n\n# Instruct Python where to look for invoke module.\nifeq ($(OS),Windows_NT)\n\texport PYTHONPATH := $(PIP_INVOKE_DIR);${PYTHONPATH}\nelse\n\texport PYTHONPATH := $(PIP_INVOKE_DIR):${PYTHONPATH}\nendif\n\n.PHONY:\t\\\n\tdefault \\\n\tinvoke \\\n\tmeson-ninja \\\n\tsetup \\\n\tclean \\\n\tclean-build \\\n\tclean-pip \\\n\tclean-subprojects \\\n\tclean-all \\\n\tupdate-wrap-file \\\n\tmediasoup-worker \\\n\tlibmediasoup-worker \\\n\tflatc \\\n\txcode \\\n\tlint \\\n\tformat \\\n\ttidy \\\n\ttidy-fix \\\n\ttest \\\n\ttest-asan-address \\\n\ttest-asan-undefined \\\n\tfuzzer \\\n\tfuzzer-run-all \\\n\tdocker \\\n\tdocker-run \\\n\tdocker-alpine \\\n\tdocker-alpine-run \\\n\tdocker-386 \\\n\tdocker-386-run\n\ndefault: mediasoup-worker\n\ninvoke:\nifeq ($(wildcard $(PIP_INVOKE_DIR)),)\n\t# Install pip invoke into custom location, so we don't depend on system-wide\n\t# installation.\n\t\"$(PYTHON)\" -m pip install --upgrade --no-user --target \"$(PIP_INVOKE_DIR)\" invoke\nendif\n\nmeson-ninja: invoke\n\t\"$(PYTHON)\" -m invoke meson-ninja\n\nsetup: invoke\n\t\"$(PYTHON)\" -m invoke setup\n\nclean: invoke\n\t\"$(PYTHON)\" -m invoke clean\n\nclean-build: invoke\n\t\"$(PYTHON)\" -m invoke clean-build\n\nclean-pip: invoke\n\t\"$(PYTHON)\" -m invoke clean-pip\n\nclean-subprojects: invoke\n\t\"$(PYTHON)\" -m invoke clean-subprojects\n\nclean-all: invoke\n\t\"$(PYTHON)\" -m invoke clean-all\n\n# It requires the SUBPROJECT environment variable.\nupdate-wrap-file: invoke\n\t\"$(PYTHON)\" -m invoke subprojects $(SUBPROJECT)\n\nmediasoup-worker: invoke\n\t\"$(PYTHON)\" -m invoke mediasoup-worker\n\nlibmediasoup-worker: invoke\n\t\"$(PYTHON)\" -m invoke libmediasoup-worker\n\nflatc: invoke\n\t\"$(PYTHON)\" -m invoke flatc\n\nxcode: invoke\n\t\"$(PYTHON)\" -m invoke xcode\n\nlint: invoke\n\t\"$(PYTHON)\" -m invoke lint\n\nformat: invoke\n\t\"$(PYTHON)\" -m invoke format\n\ntidy: invoke\n\t\"$(PYTHON)\" -m invoke tidy\n\ntidy-fix: invoke\n\t\"$(PYTHON)\" -m invoke tidy-fix\n\ntest: invoke\n\t\"$(PYTHON)\" -m invoke test\n\ntest-asan-address: invoke\n\t\"$(PYTHON)\" -m invoke test-asan-address\n\ntest-asan-undefined: invoke\n\t\"$(PYTHON)\" -m invoke test-asan-undefined\n\nfuzzer: invoke\n\t\"$(PYTHON)\" -m invoke fuzzer\n\nfuzzer-run-all: invoke\n\t\"$(PYTHON)\" -m invoke fuzzer-run-all\n\ndocker: invoke\n\t\"$(PYTHON)\" -m invoke docker\n\ndocker-run: invoke\n\t\"$(PYTHON)\" -m invoke docker-run\n\ndocker-alpine: invoke\n\t\"$(PYTHON)\" -m invoke docker-alpine\n\ndocker-alpine-run: invoke\n\t\"$(PYTHON)\" -m invoke docker-alpine-run\n\ndocker-386: invoke\n\t\"$(PYTHON)\" -m invoke docker-386\n\ndocker-386-run: invoke\n\t\"$(PYTHON)\" -m invoke docker-386-run\n"
  },
  {
    "path": "worker/build.rs",
    "content": "use std::process::Command;\nuse std::{env, fs};\n\nfn main() {\n    // On Windows Rust always links against release version of MSVC runtime, thus requires\n    // Release build here\n    let build_type = if cfg!(all(debug_assertions, not(windows))) {\n        \"Debug\"\n    } else {\n        \"Release\"\n    };\n\n    let out_dir = env::var(\"OUT_DIR\").unwrap();\n\n    // Compile Rust flatbuffers\n    let flatbuffers_declarations = planus_translation::translate_files(\n        &fs::read_dir(\"fbs\")\n            .expect(\"Failed to read `fbs` directory\")\n            .filter_map(|maybe_entry| {\n                maybe_entry\n                    .map(|entry| {\n                        let path = entry.path();\n                        if path.extension() == Some(\"fbs\".as_ref()) {\n                            Some(path)\n                        } else {\n                            None\n                        }\n                    })\n                    .transpose()\n            })\n            .collect::<Result<Vec<_>, _>>()\n            .expect(\"Failed to collect flatbuffers files\"),\n    )\n    .expect(\"Failed to translate flatbuffers files\");\n\n    fs::write(\n        format!(\"{out_dir}/fbs.rs\"),\n        planus_codegen::generate_rust(&flatbuffers_declarations)\n            .expect(\"Failed to generate Rust code from flatbuffers\"),\n    )\n    .expect(\"Failed to write generated Rust flatbuffers into fbs.rs\");\n    if env::var(\"DOCS_RS\").is_ok() {\n        // Skip everything when building docs on docs.rs\n        return;\n    }\n\n    // Force forward slashes on Windows too so that is plays well with our tasks.py\n    let mediasoup_out_dir = format!(\"{}/out\", out_dir.replace('\\\\', \"/\"));\n\n    // Add C++ std lib\n    #[cfg(target_os = \"linux\")]\n    {\n        let path = Command::new(env::var(\"CXX\").unwrap_or_else(|_| \"c++\".to_string()))\n            .arg(\"--print-file-name=libstdc++.a\")\n            .output()\n            .expect(\"Failed to start\")\n            .stdout;\n        println!(\n            \"cargo:rustc-link-search=native={}\",\n            String::from_utf8_lossy(&path)\n                .trim()\n                .strip_suffix(\"libstdc++.a\")\n                .expect(\"Failed to strip suffix\"),\n        );\n        println!(\"cargo:rustc-link-lib=static=stdc++\");\n    }\n    #[cfg(any(\n        target_os = \"freebsd\",\n        target_os = \"dragonfly\",\n        target_os = \"openbsd\",\n        target_os = \"netbsd\"\n    ))]\n    {\n        let path = Command::new(env::var(\"CXX\").unwrap_or_else(|_| \"c++\".to_string()))\n            .arg(\"--print-file-name=libc++.a\")\n            .output()\n            .expect(\"Failed to start\")\n            .stdout;\n        println!(\n            \"cargo:rustc-link-search=native={}\",\n            String::from_utf8_lossy(&path)\n                .trim()\n                .strip_suffix(\"libc++.a\")\n                .expect(\"Failed to strip suffix\"),\n        );\n        println!(\"cargo:rustc-link-lib=static=c++\");\n    }\n    #[cfg(target_os = \"macos\")]\n    {\n        let path = Command::new(\"xcrun\")\n            .arg(\"--show-sdk-path\")\n            .output()\n            .expect(\"Failed to start\")\n            .stdout;\n\n        let libpath = format!(\n            \"{}/usr/lib\",\n            String::from_utf8(path)\n                .expect(\"Failed to decode path\")\n                .trim()\n        );\n        println!(\"cargo:rustc-link-search={libpath}\");\n        println!(\"cargo:rustc-link-lib=dylib=c++\");\n        println!(\"cargo:rustc-link-lib=dylib=c++abi\");\n    }\n\n    // Install Python invoke package in custom folder\n    let pip_invoke_dir = format!(\"{out_dir}/pip_invoke\");\n    let python = env::var(\"PYTHON\").unwrap_or(\"python3\".to_string());\n    let mut pythonpath = if let Ok(original_pythonpath) = env::var(\"PYTHONPATH\") {\n        format!(\"{pip_invoke_dir}:{original_pythonpath}\")\n    } else {\n        pip_invoke_dir.clone()\n    };\n\n    // Force \";\" in PYTHONPATH on Windows\n    if cfg!(target_os = \"windows\") {\n        pythonpath = pythonpath.replace(':', \";\");\n    }\n\n    if !Command::new(&python)\n        .arg(\"-m\")\n        .arg(\"pip\")\n        .arg(\"install\")\n        .arg(\"--upgrade\")\n        .arg(\"--target\")\n        .arg(pip_invoke_dir)\n        .arg(\"invoke\")\n        .spawn()\n        .expect(\"Failed to start\")\n        .wait()\n        .expect(\"Wasn't running\")\n        .success()\n    {\n        panic!(\"Failed to install Python invoke package\")\n    }\n\n    // Build\n    if !Command::new(&python)\n        .arg(\"-m\")\n        .arg(\"invoke\")\n        .arg(\"libmediasoup-worker\")\n        .env(\"PYTHONPATH\", &pythonpath)\n        .env(\"MEDIASOUP_OUT_DIR\", &mediasoup_out_dir)\n        .env(\"MEDIASOUP_BUILDTYPE\", build_type)\n        // Force forward slashes on Windows too, otherwise Meson thinks path is not absolute 🤷\n        .env(\"MEDIASOUP_INSTALL_DIR\", out_dir.replace('\\\\', \"/\"))\n        .spawn()\n        .expect(\"Failed to start\")\n        .wait()\n        .expect(\"Wasn't running\")\n        .success()\n    {\n        panic!(\"Failed to build libmediasoup-worker\")\n    }\n\n    #[cfg(target_os = \"windows\")]\n    {\n        let dot_a = format!(\"{out_dir}/libmediasoup-worker.a\");\n        let dot_lib = format!(\"{out_dir}/mediasoup-worker.lib\");\n\n        // Meson builds `libmediasoup-worker.a` on Windows instead of `*.lib` file under MinGW\n        if std::path::Path::new(&dot_a).exists() {\n            std::fs::copy(&dot_a, &dot_lib).unwrap_or_else(|error| {\n                panic!(\"Failed to copy static library from {dot_a} to {dot_lib}: {error}\");\n            });\n        }\n\n        // These are required by libuv on Windows\n        println!(\"cargo:rustc-link-lib=psapi\");\n        println!(\"cargo:rustc-link-lib=user32\");\n        println!(\"cargo:rustc-link-lib=advapi32\");\n        println!(\"cargo:rustc-link-lib=iphlpapi\");\n        println!(\"cargo:rustc-link-lib=userenv\");\n        println!(\"cargo:rustc-link-lib=ws2_32\");\n        println!(\"cargo:rustc-link-lib=dbghelp\");\n        println!(\"cargo:rustc-link-lib=ole32\");\n        println!(\"cargo:rustc-link-lib=uuid\");\n        println!(\"cargo:rustc-link-lib=shell32\");\n\n        // These are required by OpenSSL on Windows\n        println!(\"cargo:rustc-link-lib=ws2_32\");\n        println!(\"cargo:rustc-link-lib=gdi32\");\n        println!(\"cargo:rustc-link-lib=advapi32\");\n        println!(\"cargo:rustc-link-lib=crypt32\");\n        println!(\"cargo:rustc-link-lib=user32\");\n    }\n\n    // Remove subprojects/.wraplock created by Meson's directory locking\n    // mechanism. It lives in the source tree and would cause `cargo package`\n    // verification to fail with \"Source directory was modified by build.rs\".\n    let wraplock = std::path::Path::new(\"subprojects/.wraplock\");\n    if wraplock.exists() {\n        fs::remove_file(wraplock).expect(\"Failed to remove subprojects/.wraplock\");\n    }\n\n    if env::var(\"KEEP_BUILD_ARTIFACTS\") != Ok(\"1\".to_string()) {\n        // Clean\n        if !Command::new(python)\n            .arg(\"-m\")\n            .arg(\"invoke\")\n            .arg(\"clean-all\")\n            .env(\"PYTHONPATH\", &pythonpath)\n            .env(\"MEDIASOUP_OUT_DIR\", &mediasoup_out_dir)\n            .spawn()\n            .expect(\"Failed to start\")\n            .wait()\n            .expect(\"Wasn't running\")\n            .success()\n        {\n            panic!(\"Failed to clean libmediasoup-worker\")\n        }\n    }\n\n    println!(\"cargo:rustc-link-lib=static=mediasoup-worker\");\n    println!(\"cargo:rustc-link-search=native={out_dir}\");\n}\n"
  },
  {
    "path": "worker/deps/libwebrtc/LICENSE",
    "content": "Copyright (c) 2011, The WebRTC project authors. All rights reserved.\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n  * Redistributions of source code must retain the above copyright\n    notice, this list of conditions and the following disclaimer.\n  * Redistributions in binary form must reproduce the above copyright\n    notice, this list of conditions and the following disclaimer in\n    the documentation and/or other materials provided with the\n    distribution.\n  * Neither the name of Google nor the names of its contributors may\n    be used to endorse or promote products derived from this software\n    without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "worker/deps/libwebrtc/PATENTS",
    "content": "Additional IP Rights Grant (Patents)\n\"This implementation\" means the copyrightable works distributed by\nGoogle as part of the WebRTC code package.\nGoogle hereby grants to you a perpetual, worldwide, non-exclusive,\nno-charge, irrevocable (except as stated in this section) patent\nlicense to make, have made, use, offer to sell, sell, import,\ntransfer, and otherwise run, modify and propagate the contents of this\nimplementation of the WebRTC code package, where such license applies\nonly to those patent claims, both currently owned by Google and\nacquired in the future, licensable by Google that are necessarily\ninfringed by this implementation of the WebRTC code package. This\ngrant does not include claims that would be infringed only as a\nconsequence of further modification of this implementation. If you or\nyour agent or exclusive licensee institute or order or agree to the\ninstitution of patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that this\nimplementation of the WebRTC code package or any code incorporated\nwithin this implementation of the WebRTC code package constitutes\ndirect or contributory patent infringement, or inducement of patent\ninfringement, then any patent rights granted to you under this License\nfor this implementation of the WebRTC code package shall terminate as\nof the date such litigation is filed.\n"
  },
  {
    "path": "worker/deps/libwebrtc/README.md",
    "content": "# README\n\nThis folder contains a modified/adapted subset of the libwebrtc library, which is used by mediasoup for transport congestion purposes.\n\n* libwebrtc branch: m77\n* libwebrtc commit: 2bac7da1349c75e5cf89612ab9619a1920d5d974\n\nThe file `libwebrtc/mediasoup_helpers.h` includes some utilities to plug mediasoup classes into libwebrtc.\n\nThe file `worker/deps/libwebrtc/deps/abseil-cpp/abseil-cpp/absl/synchronization/internal/graphcycles.cc` has `#include <limits>` added to it to fix CI builds with Clang.\n\nThe file `meson.build` is written for using with Meson build system.\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/bitrate_constraints.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_BITRATE_CONSTRAINTS_H_\n#define API_BITRATE_CONSTRAINTS_H_\n\n#include <algorithm>\n\nnamespace webrtc {\n// TODO(srte): BitrateConstraints and BitrateSettings should be merged.\n// Both represent the same kind data, but are using different default\n// initializer and representation of unset values.\nstruct BitrateConstraints {\n  int min_bitrate_bps = 0;\n  int start_bitrate_bps = kDefaultStartBitrateBps;\n  int max_bitrate_bps = -1;\n\n private:\n  static constexpr int kDefaultStartBitrateBps = 300000;\n};\n\n// Like std::min, but considers non-positive values to be unset.\ntemplate <typename T>\nstatic T MinPositive(T a, T b) {\n  if (a <= 0) {\n    return b;\n  }\n  if (b <= 0) {\n    return a;\n  }\n  return std::min(a, b);\n}\n}  // namespace webrtc\n#endif  // API_BITRATE_CONSTRAINTS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/network_state_predictor.cc",
    "content": "\n#include \"api/network_state_predictor.h\"\n\nnamespace webrtc {\n\n// MS_NOTE: added function.\nstd::string BandwidthUsage2String(BandwidthUsage bandwidthUsage) {\n\tswitch (bandwidthUsage) {\n\t\tcase BandwidthUsage::kBwNormal:\n\t\t\treturn \"normal\";\n\t\tcase BandwidthUsage::kBwUnderusing:\n\t\t\treturn \"underusing\";\n\t\tcase BandwidthUsage::kBwOverusing:\n\t\t\treturn \"overusing\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/network_state_predictor.h",
    "content": "/*\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_NETWORK_STATE_PREDICTOR_H_\n#define API_NETWORK_STATE_PREDICTOR_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n#include <cstdint>\n\nnamespace webrtc {\n\nenum class BandwidthUsage {\n  kBwNormal = 0,\n  kBwUnderusing = 1,\n  kBwOverusing = 2,\n  kLast\n};\n\n// MS_NOTE: added function.\nstd::string BandwidthUsage2String(BandwidthUsage bandwidthUsage);\n\n// TODO(yinwa): work in progress. API in class NetworkStatePredictor should not\n// be used by other users until this comment is removed.\n\n// NetworkStatePredictor predict network state based on current network metrics.\n// Usage:\n// Setup by calling Initialize.\n// For each update, call Update. Update returns network state\n// prediction.\nclass NetworkStatePredictor {\n public:\n  virtual ~NetworkStatePredictor() {}\n\n  // Returns current network state prediction.\n  // Inputs:  send_time_ms - packet send time.\n  //          arrival_time_ms - packet arrival time.\n  //          network_state - computed network state.\n  virtual BandwidthUsage Update(int64_t send_time_ms,\n                                int64_t arrival_time_ms,\n                                BandwidthUsage network_state) = 0;\n};\n\nclass NetworkStatePredictorFactoryInterface {\n public:\n  virtual std::unique_ptr<NetworkStatePredictor>\n  CreateNetworkStatePredictor() = 0;\n  virtual ~NetworkStatePredictorFactoryInterface() = default;\n};\n\n}  // namespace webrtc\n\n#endif  // API_NETWORK_STATE_PREDICTOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/bitrate_settings.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/transport/bitrate_settings.h\"\n\nnamespace webrtc {\n\nBitrateSettings::BitrateSettings() = default;\nBitrateSettings::~BitrateSettings() = default;\nBitrateSettings::BitrateSettings(const BitrateSettings&) = default;\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/bitrate_settings.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_TRANSPORT_BITRATE_SETTINGS_H_\n#define API_TRANSPORT_BITRATE_SETTINGS_H_\n\n#include <absl/types/optional.h>\n\nnamespace webrtc {\n\n// Configuration of send bitrate. The |start_bitrate_bps| value is\n// used for multiple purposes, both as a prior in the bandwidth\n// estimator, and for initial configuration of the encoder. We may\n// want to create separate apis for those, and use a smaller struct\n// with only the min and max constraints.\nstruct BitrateSettings {\n  BitrateSettings();\n  ~BitrateSettings();\n  BitrateSettings(const BitrateSettings&);\n  // 0 <= min <= start <= max should hold for set parameters.\n  absl::optional<int> min_bitrate_bps;\n  absl::optional<int> start_bitrate_bps;\n  absl::optional<int> max_bitrate_bps;\n};\n\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_BITRATE_SETTINGS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/field_trial_based_config.cc",
    "content": "/*\n *  Copyright 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/transport/field_trial_based_config.h\"\n#include \"system_wrappers/source/field_trial.h\"\n\nnamespace webrtc {\nstd::string FieldTrialBasedConfig::Lookup(absl::string_view key) const {\n  return webrtc::field_trial::FindFullName(std::string(key));\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/field_trial_based_config.h",
    "content": "/*\n *  Copyright 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_\n#define API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n\n#include <absl/strings/string_view.h>\n#include <string>\n\nnamespace webrtc {\n// Implementation using the field trial API fo the key value lookup.\nclass FieldTrialBasedConfig : public WebRtcKeyValueConfig {\n public:\n  std::string Lookup(absl::string_view key) const override;\n};\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/goog_cc_factory.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/transport/goog_cc_factory.h\"\n#include \"modules/congestion_controller/goog_cc/goog_cc_network_control.h\"\n\n#include <absl/memory/memory.h>\n#include <utility>\n\nnamespace webrtc {\nGoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory(\n    NetworkStatePredictorFactoryInterface* network_state_predictor_factory) {\n  factory_config_.network_state_predictor_factory =\n      network_state_predictor_factory;\n}\n\nGoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory(\n    GoogCcFactoryConfig config)\n    : factory_config_(std::move(config)) {}\n\nstd::unique_ptr<NetworkControllerInterface>\nGoogCcNetworkControllerFactory::Create(NetworkControllerConfig config) {\n  GoogCcConfig goog_cc_config;\n  goog_cc_config.feedback_only = factory_config_.feedback_only;\n  if (factory_config_.network_state_estimator_factory) {\n    // RTC_DCHECK(config.key_value_config);\n    goog_cc_config.network_state_estimator =\n        factory_config_.network_state_estimator_factory->Create(\n            config.key_value_config);\n  }\n  if (factory_config_.network_state_predictor_factory) {\n    goog_cc_config.network_state_predictor =\n        factory_config_.network_state_predictor_factory\n            ->CreateNetworkStatePredictor();\n  }\n  return absl::make_unique<GoogCcNetworkController>(config,\n                                                    std::move(goog_cc_config));\n}\n\nTimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const {\n  const int64_t kUpdateIntervalMs = 25;\n  return TimeDelta::ms(kUpdateIntervalMs);\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/goog_cc_factory.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_TRANSPORT_GOOG_CC_FACTORY_H_\n#define API_TRANSPORT_GOOG_CC_FACTORY_H_\n\n#include \"api/network_state_predictor.h\"\n#include \"api/transport/network_control.h\"\n\n#include <memory>\n\nnamespace webrtc {\n\nstruct GoogCcFactoryConfig {\n  std::unique_ptr<NetworkStateEstimatorFactory>\n      network_state_estimator_factory = nullptr;\n  NetworkStatePredictorFactoryInterface* network_state_predictor_factory =\n      nullptr;\n  bool feedback_only = false;\n};\n\nclass GoogCcNetworkControllerFactory\n    : public NetworkControllerFactoryInterface {\n public:\n  GoogCcNetworkControllerFactory() = default;\n  explicit GoogCcNetworkControllerFactory(\n      NetworkStatePredictorFactoryInterface* network_state_predictor_factory);\n\n  explicit GoogCcNetworkControllerFactory(GoogCcFactoryConfig config);\n  std::unique_ptr<NetworkControllerInterface> Create(\n      NetworkControllerConfig config) override;\n  TimeDelta GetProcessInterval() const override;\n\n protected:\n  GoogCcFactoryConfig factory_config_;\n};\n\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_GOOG_CC_FACTORY_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/network_control.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_TRANSPORT_NETWORK_CONTROL_H_\n#define API_TRANSPORT_NETWORK_CONTROL_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n\n#include <stdint.h>\n#include <memory>\n\nnamespace webrtc {\n// TODO(srte): Remove this forward declaration when this is in api.\nclass RtcEventLog;\n\nclass TargetTransferRateObserver {\n public:\n  virtual ~TargetTransferRateObserver() = default;\n  // Called to indicate target transfer rate as well as giving information about\n  // the current estimate of network parameters.\n  virtual void OnTargetTransferRate(TargetTransferRate) = 0;\n  // Called to provide updates to the expected target rate in case it changes\n  // before the first call to OnTargetTransferRate.\n  virtual void OnStartRateUpdate(DataRate) {}\n};\n\n// Configuration sent to factory create function. The parameters here are\n// optional to use for a network controller implementation.\nstruct NetworkControllerConfig {\n  // The initial constraints to start with, these can be changed at any later\n  // time by calls to OnTargetRateConstraints. Note that the starting rate\n  // has to be set initially to provide a starting state for the network\n  // controller, even though the field is marked as optional.\n  TargetRateConstraints constraints;\n  // Initial stream specific configuration, these are changed at any later time\n  // by calls to OnStreamsConfig.\n  StreamsConfig stream_based_config;\n\n  // Optional override of configuration of WebRTC internals. Using nullptr here\n  // indicates that the field trial API will be used.\n  const WebRtcKeyValueConfig* key_value_config = nullptr;\n  // Optional override of event log.\n  RtcEventLog* event_log = nullptr;\n};\n\n// NetworkControllerInterface is implemented by network controllers. A network\n// controller is a class that uses information about network state and traffic\n// to estimate network parameters such as round trip time and bandwidth. Network\n// controllers does not guarantee thread safety, the interface must be used in a\n// non-concurrent fashion.\nclass NetworkControllerInterface {\n public:\n  virtual ~NetworkControllerInterface() = default;\n\n  // Called when network availabilty changes.\n  virtual NetworkControlUpdate OnNetworkAvailability(NetworkAvailability) = 0;\n  // Called when the receiving or sending endpoint changes address.\n  virtual NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange) = 0;\n  // Called periodically with a periodicy as specified by\n  // NetworkControllerFactoryInterface::GetProcessInterval.\n  virtual NetworkControlUpdate OnProcessInterval(ProcessInterval) = 0;\n  // Called when remotely calculated bitrate is received.\n  virtual NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport) = 0;\n  // Called round trip time has been calculated by protocol specific mechanisms.\n  virtual NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0;\n  // Called when a packet is sent on the network.\n  virtual NetworkControlUpdate OnSentPacket(SentPacket) = 0;\n  // Called when the stream specific configuration has been updated.\n  virtual NetworkControlUpdate OnStreamsConfig(StreamsConfig) = 0;\n  // Called when target transfer rate constraints has been changed.\n  virtual NetworkControlUpdate OnTargetRateConstraints(\n      TargetRateConstraints) = 0;\n  // Called when a protocol specific calculation of packet loss has been made.\n  virtual NetworkControlUpdate OnTransportLossReport(TransportLossReport) = 0;\n  // Called with per packet feedback regarding receive time.\n  virtual NetworkControlUpdate OnTransportPacketsFeedback(\n      TransportPacketsFeedback) = 0;\n  // Called with network state estimate updates.\n  virtual NetworkControlUpdate OnNetworkStateEstimate(NetworkStateEstimate) = 0;\n};\n\n// NetworkControllerFactoryInterface is an interface for creating a network\n// controller.\nclass NetworkControllerFactoryInterface {\n public:\n  virtual ~NetworkControllerFactoryInterface() = default;\n\n  // Used to create a new network controller, requires an observer to be\n  // provided to handle callbacks.\n  virtual std::unique_ptr<NetworkControllerInterface> Create(\n      NetworkControllerConfig config) = 0;\n  // Returns the interval by which the network controller expects\n  // OnProcessInterval calls.\n  virtual TimeDelta GetProcessInterval() const = 0;\n};\n\n// Under development, subject to change without notice.\nclass NetworkStateEstimator {\n public:\n  // Gets the current best estimate according to the estimator.\n  virtual absl::optional<NetworkStateEstimate> GetCurrentEstimate() = 0;\n  // Called with per packet feedback regarding receive time.\n  virtual void OnTransportPacketsFeedback(const TransportPacketsFeedback&) = 0;\n  // Called when the receiving or sending endpoint changes address.\n  virtual void OnRouteChange(const NetworkRouteChange&) = 0;\n  virtual ~NetworkStateEstimator() = default;\n};\nclass NetworkStateEstimatorFactory {\n public:\n  virtual std::unique_ptr<NetworkStateEstimator> Create(\n      const WebRtcKeyValueConfig* key_value_config) = 0;\n  virtual ~NetworkStateEstimatorFactory() = default;\n};\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_NETWORK_CONTROL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/network_types.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/transport/network_types.h\"\n\n#include <algorithm>\n\nnamespace webrtc {\nStreamsConfig::StreamsConfig() = default;\nStreamsConfig::StreamsConfig(const StreamsConfig&) = default;\nStreamsConfig::~StreamsConfig() = default;\n\nTargetRateConstraints::TargetRateConstraints() = default;\nTargetRateConstraints::TargetRateConstraints(const TargetRateConstraints&) =\n    default;\nTargetRateConstraints::~TargetRateConstraints() = default;\n\nNetworkRouteChange::NetworkRouteChange() = default;\nNetworkRouteChange::NetworkRouteChange(const NetworkRouteChange&) = default;\nNetworkRouteChange::~NetworkRouteChange() = default;\n\nPacketResult::PacketResult() = default;\nPacketResult::PacketResult(const PacketResult& other) = default;\nPacketResult::~PacketResult() = default;\n\nbool PacketResult::ReceiveTimeOrder::operator()(const PacketResult& lhs,\n                                                const PacketResult& rhs) {\n  if (lhs.receive_time != rhs.receive_time)\n    return lhs.receive_time < rhs.receive_time;\n  if (lhs.sent_packet.send_time != rhs.sent_packet.send_time)\n    return lhs.sent_packet.send_time < rhs.sent_packet.send_time;\n  return lhs.sent_packet.sequence_number < rhs.sent_packet.sequence_number;\n}\n\nTransportPacketsFeedback::TransportPacketsFeedback() = default;\nTransportPacketsFeedback::TransportPacketsFeedback(\n    const TransportPacketsFeedback& other) = default;\nTransportPacketsFeedback::~TransportPacketsFeedback() = default;\n\nstd::vector<PacketResult> TransportPacketsFeedback::ReceivedWithSendInfo()\n    const {\n  std::vector<PacketResult> res;\n  for (const PacketResult& fb : packet_feedbacks) {\n    if (fb.receive_time.IsFinite()) {\n      res.push_back(fb);\n    }\n  }\n  return res;\n}\n\nstd::vector<PacketResult> TransportPacketsFeedback::LostWithSendInfo() const {\n  std::vector<PacketResult> res;\n  for (const PacketResult& fb : packet_feedbacks) {\n    if (fb.receive_time.IsPlusInfinity()) {\n      res.push_back(fb);\n    }\n  }\n  return res;\n}\n\nstd::vector<PacketResult> TransportPacketsFeedback::PacketsWithFeedback()\n    const {\n  return packet_feedbacks;\n}\n\nstd::vector<PacketResult> TransportPacketsFeedback::SortedByReceiveTime()\n    const {\n  std::vector<PacketResult> res;\n  for (const PacketResult& fb : packet_feedbacks) {\n    if (fb.receive_time.IsFinite()) {\n      res.push_back(fb);\n    }\n  }\n  std::sort(res.begin(), res.end(), PacketResult::ReceiveTimeOrder());\n  return res;\n}\n\nNetworkControlUpdate::NetworkControlUpdate() = default;\nNetworkControlUpdate::NetworkControlUpdate(const NetworkControlUpdate&) =\n    default;\nNetworkControlUpdate::~NetworkControlUpdate() = default;\n\nPacedPacketInfo::PacedPacketInfo() = default;\n\nPacedPacketInfo::PacedPacketInfo(int probe_cluster_id,\n                                 int probe_cluster_min_probes,\n                                 int probe_cluster_min_bytes)\n    : probe_cluster_id(probe_cluster_id),\n      probe_cluster_min_probes(probe_cluster_min_probes),\n      probe_cluster_min_bytes(probe_cluster_min_bytes) {}\n\nbool PacedPacketInfo::operator==(const PacedPacketInfo& rhs) const {\n  return send_bitrate_bps == rhs.send_bitrate_bps &&\n         probe_cluster_id == rhs.probe_cluster_id &&\n         probe_cluster_min_probes == rhs.probe_cluster_min_probes &&\n         probe_cluster_min_bytes == rhs.probe_cluster_min_bytes;\n}\n\nProcessInterval::ProcessInterval() = default;\nProcessInterval::ProcessInterval(const ProcessInterval&) = default;\nProcessInterval::~ProcessInterval() = default;\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/network_types.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_TRANSPORT_NETWORK_TYPES_H_\n#define API_TRANSPORT_NETWORK_TYPES_H_\n\n#include \"api/units/data_rate.h\"\n#include \"api/units/data_size.h\"\n#include \"api/units/time_delta.h\"\n#include \"api/units/timestamp.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n#include <vector>\n\nnamespace webrtc {\n\n// Configuration\n\n// Use StreamsConfig for information about streams that is required for specific\n// adjustments to the algorithms in network controllers. Especially useful\n// for experiments.\nstruct StreamsConfig {\n  StreamsConfig();\n  StreamsConfig(const StreamsConfig&);\n  ~StreamsConfig();\n  Timestamp at_time = Timestamp::PlusInfinity();\n  absl::optional<bool> requests_alr_probing;\n  absl::optional<double> pacing_factor;\n  absl::optional<DataRate> min_total_allocated_bitrate = absl::nullopt;\n  absl::optional<DataRate> max_padding_rate;\n  absl::optional<DataRate> max_total_allocated_bitrate;\n};\n\nstruct TargetRateConstraints {\n  TargetRateConstraints();\n  TargetRateConstraints(const TargetRateConstraints&);\n  ~TargetRateConstraints();\n  Timestamp at_time = Timestamp::PlusInfinity();\n  absl::optional<DataRate> min_data_rate;\n  absl::optional<DataRate> max_data_rate;\n  // The initial bandwidth estimate to base target rate on. This should be used\n  // as the basis for initial OnTargetTransferRate and OnPacerConfig callbacks.\n  absl::optional<DataRate> starting_rate;\n};\n\n// Send side information\n\nstruct NetworkAvailability {\n  Timestamp at_time = Timestamp::PlusInfinity();\n  bool network_available = false;\n};\n\nstruct NetworkRouteChange {\n  NetworkRouteChange();\n  NetworkRouteChange(const NetworkRouteChange&);\n  ~NetworkRouteChange();\n  Timestamp at_time = Timestamp::PlusInfinity();\n  // The TargetRateConstraints are set here so they can be changed synchronously\n  // when network route changes.\n  TargetRateConstraints constraints;\n};\n\nstruct PacedPacketInfo {\n  PacedPacketInfo();\n  PacedPacketInfo(int probe_cluster_id,\n                  int probe_cluster_min_probes,\n                  int probe_cluster_min_bytes);\n\n  bool operator==(const PacedPacketInfo& rhs) const;\n\n  // TODO(srte): Move probing info to a separate, optional struct.\n  static constexpr int kNotAProbe = -1;\n  int send_bitrate_bps = -1;\n  int probe_cluster_id = kNotAProbe;\n  int probe_cluster_min_probes = -1;\n  int probe_cluster_min_bytes = -1;\n};\n\nstruct SentPacket {\n  Timestamp send_time = Timestamp::PlusInfinity();\n  DataSize size = DataSize::Zero();\n  DataSize prior_unacked_data = DataSize::Zero();\n  PacedPacketInfo pacing_info;\n  // Transport independent sequence number, any tracked packet should have a\n  // sequence number that is unique over the whole call and increasing by 1 for\n  // each packet.\n  int64_t sequence_number;\n  // Tracked data in flight when the packet was sent, excluding unacked data.\n  DataSize data_in_flight = DataSize::Zero();\n};\n\nstruct ReceivedPacket {\n  Timestamp send_time = Timestamp::MinusInfinity();\n  Timestamp receive_time = Timestamp::PlusInfinity();\n  DataSize size = DataSize::Zero();\n};\n\n// Transport level feedback\n\nstruct RemoteBitrateReport {\n  Timestamp receive_time = Timestamp::PlusInfinity();\n  DataRate bandwidth = DataRate::Infinity();\n};\n\nstruct RoundTripTimeUpdate {\n  Timestamp receive_time = Timestamp::PlusInfinity();\n  TimeDelta round_trip_time = TimeDelta::PlusInfinity();\n  bool smoothed = false;\n};\n\nstruct TransportLossReport {\n  Timestamp receive_time = Timestamp::PlusInfinity();\n  Timestamp start_time = Timestamp::PlusInfinity();\n  Timestamp end_time = Timestamp::PlusInfinity();\n  uint64_t packets_lost_delta = 0;\n  uint64_t packets_received_delta = 0;\n};\n\n// Packet level feedback\n\nstruct PacketResult {\n  class ReceiveTimeOrder {\n   public:\n    bool operator()(const PacketResult& lhs, const PacketResult& rhs);\n  };\n\n  PacketResult();\n  PacketResult(const PacketResult&);\n  ~PacketResult();\n\n  SentPacket sent_packet;\n  Timestamp receive_time = Timestamp::PlusInfinity();\n};\n\nstruct TransportPacketsFeedback {\n  TransportPacketsFeedback();\n  TransportPacketsFeedback(const TransportPacketsFeedback& other);\n  ~TransportPacketsFeedback();\n\n  Timestamp feedback_time = Timestamp::PlusInfinity();\n  Timestamp first_unacked_send_time = Timestamp::PlusInfinity();\n  DataSize data_in_flight = DataSize::Zero();\n  DataSize prior_in_flight = DataSize::Zero();\n  std::vector<PacketResult> packet_feedbacks;\n\n  // Arrival times for messages without send time information.\n  std::vector<Timestamp> sendless_arrival_times;\n\n  std::vector<PacketResult> ReceivedWithSendInfo() const;\n  std::vector<PacketResult> LostWithSendInfo() const;\n  std::vector<PacketResult> PacketsWithFeedback() const;\n  std::vector<PacketResult> SortedByReceiveTime() const;\n};\n\n// Network estimation\n\nstruct NetworkEstimate {\n  Timestamp at_time = Timestamp::PlusInfinity();\n  DataRate bandwidth = DataRate::Infinity();\n  TimeDelta round_trip_time = TimeDelta::PlusInfinity();\n  TimeDelta bwe_period = TimeDelta::PlusInfinity();\n\n  float loss_rate_ratio = 0;\n};\n\n// Network control\n\nstruct PacerConfig {\n  Timestamp at_time = Timestamp::PlusInfinity();\n  // Pacer should send at most data_window data over time_window duration.\n  DataSize data_window = DataSize::Infinity();\n  TimeDelta time_window = TimeDelta::PlusInfinity();\n  // Pacer should send at least pad_window data over time_window duration.\n  DataSize pad_window = DataSize::Zero();\n  DataRate data_rate() const { return data_window / time_window; }\n  DataRate pad_rate() const { return pad_window / time_window; }\n};\n\nstruct ProbeClusterConfig {\n  Timestamp at_time = Timestamp::PlusInfinity();\n  DataRate target_data_rate = DataRate::Zero();\n  TimeDelta target_duration = TimeDelta::Zero();\n  int32_t target_probe_count = 0;\n  int32_t id = 0;\n};\n\nstruct TargetTransferRate {\n  Timestamp at_time = Timestamp::PlusInfinity();\n  // The estimate on which the target rate is based on.\n  NetworkEstimate network_estimate;\n  DataRate target_rate = DataRate::Zero();\n};\n\n// Contains updates of network controller comand state. Using optionals to\n// indicate whether a member has been updated. The array of probe clusters\n// should be used to send out probes if not empty.\nstruct NetworkControlUpdate {\n  NetworkControlUpdate();\n  NetworkControlUpdate(const NetworkControlUpdate&);\n  ~NetworkControlUpdate();\n  absl::optional<DataSize> congestion_window;\n  absl::optional<PacerConfig> pacer_config;\n  std::vector<ProbeClusterConfig> probe_cluster_configs;\n  absl::optional<TargetTransferRate> target_rate;\n};\n\n// Process control\nstruct ProcessInterval {\n  ProcessInterval();\n  ProcessInterval(const ProcessInterval&);\n  ~ProcessInterval();\n  Timestamp at_time = Timestamp::PlusInfinity();\n  absl::optional<DataSize> pacer_queue;\n};\n\n// Under development, subject to change without notice.\nstruct NetworkStateEstimate {\n  double confidence = NAN;\n  // The time the estimate was received/calculated.\n  Timestamp update_time = Timestamp::MinusInfinity();\n  Timestamp last_receive_time = Timestamp::MinusInfinity();\n  Timestamp last_send_time = Timestamp::MinusInfinity();\n\n  // Total estimated link capacity.\n  DataRate link_capacity = DataRate::MinusInfinity();\n  // Used as a safe measure of available capacity.\n  DataRate link_capacity_lower = DataRate::MinusInfinity();\n  // Used as limit for increasing bitrate.\n  DataRate link_capacity_upper = DataRate::MinusInfinity();\n\n  TimeDelta pre_link_buffer_delay = TimeDelta::MinusInfinity();\n  TimeDelta post_link_buffer_delay = TimeDelta::MinusInfinity();\n  TimeDelta propagation_delay = TimeDelta::MinusInfinity();\n\n  // Only for debugging\n  TimeDelta time_delta = TimeDelta::MinusInfinity();\n  Timestamp last_feed_time = Timestamp::MinusInfinity();\n  double cross_delay_rate = NAN;\n  double spike_delay_rate = NAN;\n  DataRate link_capacity_std_dev = DataRate::MinusInfinity();\n  DataRate link_capacity_min = DataRate::MinusInfinity();\n  double cross_traffic_ratio = NAN;\n};\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_NETWORK_TYPES_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/transport/webrtc_key_value_config.h",
    "content": "/*\n *  Copyright 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_\n#define API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_\n\n#include <absl/strings/string_view.h>\n#include <string>\n\nnamespace webrtc {\n\n// An interface that provides a key-value mapping for configuring internal\n// details of WebRTC. Note that there's no guarantess that the meaning of a\n// particular key value mapping will be preserved over time and no announcements\n// will be made if they are changed. It's up to the library user to ensure that\n// the behavior does not break.\nclass WebRtcKeyValueConfig {\n public:\n  virtual ~WebRtcKeyValueConfig() = default;\n  // The configured value for the given key. Defaults to an empty string.\n  virtual std::string Lookup(absl::string_view key) const = 0;\n};\n}  // namespace webrtc\n\n#endif  // API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/data_rate.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/units/data_rate.h\"\n\n#include <sstream>\n\nnamespace webrtc {\n\nstd::string ToString(DataRate value) {\n  std::ostringstream sb;\n  if (value.IsPlusInfinity()) {\n    sb << \"+inf bps\";\n  } else if (value.IsMinusInfinity()) {\n    sb << \"-inf bps\";\n  } else {\n    if (value.bps() == 0 || value.bps() % 1000 != 0) {\n      sb << value.bps() << \" bps\";\n    } else {\n      sb << value.kbps() << \" kbps\";\n    }\n  }\n  return sb.str();\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/data_rate.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_UNITS_DATA_RATE_H_\n#define API_UNITS_DATA_RATE_H_\n\n#include \"api/units/data_size.h\"\n#include \"api/units/frequency.h\"\n#include \"api/units/time_delta.h\"\n#include \"rtc_base/units/unit_base.h\"\n\n#include <limits>\n#include <string>\n#include <type_traits>\n#ifdef UNIT_TEST\n#include <ostream>  // no-presubmit-check TODO(webrtc:8982)\n#endif              // UNIT_TEST\n\nnamespace webrtc {\n// DataRate is a class that represents a given data rate. This can be used to\n// represent bandwidth, encoding bitrate, etc. The internal storage is bits per\n// second (bps).\nclass DataRate final : public rtc_units_impl::RelativeUnit<DataRate> {\n public:\n  DataRate() = delete;\n  static constexpr DataRate Infinity() { return PlusInfinity(); }\n  template <int64_t bps>\n  static constexpr DataRate BitsPerSec() {\n    return FromStaticValue<bps>();\n  }\n  template <int64_t kbps>\n  static constexpr DataRate KilobitsPerSec() {\n    return FromStaticFraction<kbps, 1000>();\n  }\n  template <typename T>\n  static constexpr DataRate bps(T bits_per_second) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromValue(bits_per_second);\n  }\n  template <typename T>\n  static constexpr DataRate bytes_per_sec(T bytes_per_second) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<8>(bytes_per_second);\n  }\n  template <typename T>\n  static constexpr DataRate kbps(T kilobits_per_sec) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000>(kilobits_per_sec);\n  }\n  template <typename T = int64_t>\n  constexpr T bps() const {\n    return ToValue<T>();\n  }\n  template <typename T = int64_t>\n  constexpr T bytes_per_sec() const {\n    return ToFraction<8, T>();\n  }\n  template <typename T = int64_t>\n  T kbps() const {\n    return ToFraction<1000, T>();\n  }\n  constexpr int64_t bps_or(int64_t fallback_value) const {\n    return ToValueOr(fallback_value);\n  }\n  constexpr int64_t kbps_or(int64_t fallback_value) const {\n    return ToFractionOr<1000>(fallback_value);\n  }\n\n private:\n  // Bits per second used internally to simplify debugging by making the value\n  // more recognizable.\n  friend class rtc_units_impl::UnitBase<DataRate>;\n  using RelativeUnit::RelativeUnit;\n  static constexpr bool one_sided = true;\n};\n\nnamespace data_rate_impl {\ninline int64_t Microbits(const DataSize& size) {\n  // MS_NOTE: check removed.\n  // constexpr int64_t kMaxBeforeConversion =\n      // std::numeric_limits<int64_t>::max() / 8000000;\n  // RTC_DCHECK_LE(size.bytes(), kMaxBeforeConversion)\n      // << \"size is too large to be expressed in microbits\";\n  return size.bytes() * 8000000;\n}\n\ninline int64_t MillibytePerSec(const DataRate& size) {\n  // MS_NOTE: check removed.\n  // constexpr int64_t kMaxBeforeConversion =\n      // std::numeric_limits<int64_t>::max() / (1000 / 8);\n  // RTC_DCHECK_LE(size.bps(), kMaxBeforeConversion)\n      // << \"rate is too large to be expressed in microbytes per second\";\n  return size.bps() * (1000 / 8);\n}\n}  // namespace data_rate_impl\n\ninline DataRate operator/(const DataSize size, const TimeDelta duration) {\n  return DataRate::bps(data_rate_impl::Microbits(size) / duration.us());\n}\ninline TimeDelta operator/(const DataSize size, const DataRate rate) {\n  return TimeDelta::us(data_rate_impl::Microbits(size) / rate.bps());\n}\ninline DataSize operator*(const DataRate rate, const TimeDelta duration) {\n  int64_t microbits = rate.bps() * duration.us();\n  return DataSize::bytes((microbits + 4000000) / 8000000);\n}\ninline DataSize operator*(const TimeDelta duration, const DataRate rate) {\n  return rate * duration;\n}\n\ninline DataSize operator/(const DataRate rate, const Frequency frequency) {\n  int64_t millihertz = frequency.millihertz<int64_t>();\n  // Note that the value is truncated here reather than rounded, potentially\n  // introducing an error of .5 bytes if rounding were expected.\n  return DataSize::bytes(data_rate_impl::MillibytePerSec(rate) / millihertz);\n}\ninline Frequency operator/(const DataRate rate, const DataSize size) {\n  return Frequency::millihertz(data_rate_impl::MillibytePerSec(rate) /\n                               size.bytes());\n}\ninline DataRate operator*(const DataSize size, const Frequency frequency) {\n  // RTC_DCHECK(frequency.IsZero() ||\n             // size.bytes() <= std::numeric_limits<int64_t>::max() / 8 /\n                                 // frequency.millihertz<int64_t>());\n  int64_t millibits_per_second =\n      size.bytes() * 8 * frequency.millihertz<int64_t>();\n  return DataRate::bps((millibits_per_second + 500) / 1000);\n}\ninline DataRate operator*(const Frequency frequency, const DataSize size) {\n  return size * frequency;\n}\n\nstd::string ToString(DataRate value);\ninline std::string ToLogString(DataRate value) {\n  return ToString(value);\n}\n\n#ifdef UNIT_TEST\ninline std::ostream& operator<<(  // no-presubmit-check TODO(webrtc:8982)\n    std::ostream& stream,         // no-presubmit-check TODO(webrtc:8982)\n    DataRate value) {\n  return stream << ToString(value);\n}\n#endif  // UNIT_TEST\n\n}  // namespace webrtc\n\n#endif  // API_UNITS_DATA_RATE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/data_size.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/units/data_size.h\"\n\n#include <sstream>\n\nnamespace webrtc {\n\nstd::string ToString(DataSize value) {\n  std::ostringstream sb;\n  if (value.IsPlusInfinity()) {\n    sb << \"+inf bytes\";\n  } else if (value.IsMinusInfinity()) {\n    sb << \"-inf bytes\";\n  } else {\n    sb << value.bytes() << \" bytes\";\n  }\n  return sb.str();\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/data_size.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_UNITS_DATA_SIZE_H_\n#define API_UNITS_DATA_SIZE_H_\n\n#include \"rtc_base/units/unit_base.h\"\n\n#include <string>\n#include <type_traits>\n#ifdef UNIT_TEST\n#include <ostream>  // no-presubmit-check TODO(webrtc:8982)\n#endif              // UNIT_TEST\n\nnamespace webrtc {\n// DataSize is a class represeting a count of bytes.\nclass DataSize final : public rtc_units_impl::RelativeUnit<DataSize> {\n public:\n  DataSize() = delete;\n  static constexpr DataSize Infinity() { return PlusInfinity(); }\n  template <int64_t bytes>\n  static constexpr DataSize Bytes() {\n    return FromStaticValue<bytes>();\n  }\n\n  template <typename T>\n  static DataSize bytes(T bytes) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromValue(bytes);\n  }\n  template <typename T = int64_t>\n  T bytes() const {\n    return ToValue<T>();\n  }\n\n  constexpr int64_t bytes_or(int64_t fallback_value) const {\n    return ToValueOr(fallback_value);\n  }\n\n private:\n  friend class rtc_units_impl::UnitBase<DataSize>;\n  using RelativeUnit::RelativeUnit;\n  static constexpr bool one_sided = true;\n};\n\nstd::string ToString(DataSize value);\ninline std::string ToLogString(DataSize value) {\n  return ToString(value);\n}\n\n#ifdef UNIT_TEST\ninline std::ostream& operator<<(  // no-presubmit-check TODO(webrtc:8982)\n    std::ostream& stream,         // no-presubmit-check TODO(webrtc:8982)\n    DataSize value) {\n  return stream << ToString(value);\n}\n#endif  // UNIT_TEST\n\n}  // namespace webrtc\n\n#endif  // API_UNITS_DATA_SIZE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/frequency.cc",
    "content": "/*\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#include \"api/units/frequency.h\"\n\n#include <sstream>\n#include <iomanip> // setfill, setw.\n\nnamespace webrtc {\n\nstd::string ToString(Frequency value) {\n  std::ostringstream sb;\n  if (value.IsPlusInfinity()) {\n    sb << \"+inf Hz\";\n  } else if (value.IsMinusInfinity()) {\n    sb << \"-inf Hz\";\n  } else if (value.millihertz<int64_t>() % 1000 != 0) {\n    sb << std::setfill('0') << std::setw(2) << value.hertz<double>() << \" Hz\";\n  } else {\n    sb << value.hertz<int64_t>() << \" Hz\";\n  }\n  return sb.str();\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/frequency.h",
    "content": "/*\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef API_UNITS_FREQUENCY_H_\n#define API_UNITS_FREQUENCY_H_\n\n#include \"api/units/time_delta.h\"\n#include \"rtc_base/units/unit_base.h\"\n\n#include <cstdlib>\n#include <limits>\n#include <string>\n#include <type_traits>\n#ifdef UNIT_TEST\n#include <ostream>  // no-presubmit-check TODO(webrtc:8982)\n#endif              // UNIT_TEST\n\nnamespace webrtc {\n\nclass Frequency final : public rtc_units_impl::RelativeUnit<Frequency> {\n public:\n  Frequency() = delete;\n  template <int64_t hertz>\n  static constexpr Frequency Hertz() {\n    return FromStaticFraction<hertz, 1000>();\n  }\n  template <typename T>\n  static Frequency hertz(T hertz) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000>(hertz);\n  }\n  template <typename T>\n  static Frequency millihertz(T hertz) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromValue(hertz);\n  }\n  template <typename T = int64_t>\n  T hertz() const {\n    return ToFraction<1000, T>();\n  }\n  template <typename T = int64_t>\n  T millihertz() const {\n    return ToValue<T>();\n  }\n\n private:\n  friend class rtc_units_impl::UnitBase<Frequency>;\n  using RelativeUnit::RelativeUnit;\n  static constexpr bool one_sided = true;\n};\n\ninline Frequency operator/(int64_t nominator, const TimeDelta& interval) {\n  constexpr int64_t kKiloPerMicro = 1000 * 1000000;\n  // RTC_DCHECK_LE(nominator, std::numeric_limits<int64_t>::max() / kKiloPerMicro);\n  // RTC_CHECK(interval.IsFinite());\n  // RTC_CHECK(!interval.IsZero());\n  return Frequency::millihertz(nominator * kKiloPerMicro / interval.us());\n}\n\ninline TimeDelta operator/(int64_t nominator, const Frequency& frequency) {\n  constexpr int64_t kMegaPerMilli = 1000000 * 1000;\n  // RTC_DCHECK_LE(nominator, std::numeric_limits<int64_t>::max() / kMegaPerMilli);\n  // RTC_CHECK(frequency.IsFinite());\n  // RTC_CHECK(!frequency.IsZero());\n  return TimeDelta::us(nominator * kMegaPerMilli / frequency.millihertz());\n}\n\nstd::string ToString(Frequency value);\ninline std::string ToLogString(Frequency value) {\n  return ToString(value);\n}\n\n#ifdef UNIT_TEST\ninline std::ostream& operator<<(  // no-presubmit-check TODO(webrtc:8982)\n    std::ostream& stream,         // no-presubmit-check TODO(webrtc:8982)\n    Frequency value) {\n  return stream << ToString(value);\n}\n#endif  // UNIT_TEST\n\n}  // namespace webrtc\n#endif  // API_UNITS_FREQUENCY_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/time_delta.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/units/time_delta.h\"\n\n#include <sstream>\n\nnamespace webrtc {\n\nstd::string ToString(TimeDelta value) {\n  std::ostringstream sb;\n  if (value.IsPlusInfinity()) {\n    sb << \"+inf ms\";\n  } else if (value.IsMinusInfinity()) {\n    sb << \"-inf ms\";\n  } else {\n    if (value.us() == 0 || (value.us() % 1000) != 0)\n      sb << value.us() << \" us\";\n    else if (value.ms() % 1000 != 0)\n      sb << value.ms() << \" ms\";\n    else\n      sb << value.seconds() << \" s\";\n  }\n  return sb.str();\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/time_delta.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_UNITS_TIME_DELTA_H_\n#define API_UNITS_TIME_DELTA_H_\n\n#include \"rtc_base/units/unit_base.h\"\n\n#include <cstdlib>\n#include <string>\n#include <type_traits>\n#ifdef UNIT_TEST\n#include <ostream>  // no-presubmit-check TODO(webrtc:8982)\n#endif              // UNIT_TEST\n\nnamespace webrtc {\n\n// TimeDelta represents the difference between two timestamps. Commonly this can\n// be a duration. However since two Timestamps are not guaranteed to have the\n// same epoch (they might come from different computers, making exact\n// synchronisation infeasible), the duration covered by a TimeDelta can be\n// undefined. To simplify usage, it can be constructed and converted to\n// different units, specifically seconds (s), milliseconds (ms) and\n// microseconds (us).\nclass TimeDelta final : public rtc_units_impl::RelativeUnit<TimeDelta> {\n public:\n  TimeDelta() = delete;\n  template <int64_t seconds>\n  static constexpr TimeDelta Seconds() {\n    return FromStaticFraction<seconds, 1000000>();\n  }\n  template <int64_t ms>\n  static constexpr TimeDelta Millis() {\n    return FromStaticFraction<ms, 1000>();\n  }\n  template <int64_t us>\n  static constexpr TimeDelta Micros() {\n    return FromStaticValue<us>();\n  }\n  template <typename T>\n  static TimeDelta seconds(T seconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000000>(seconds);\n  }\n  template <typename T>\n  static TimeDelta ms(T milliseconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000>(milliseconds);\n  }\n  template <typename T>\n  static TimeDelta us(T microseconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromValue(microseconds);\n  }\n  template <typename T = int64_t>\n  T seconds() const {\n    return ToFraction<1000000, T>();\n  }\n  template <typename T = int64_t>\n  T ms() const {\n    return ToFraction<1000, T>();\n  }\n  template <typename T = int64_t>\n  T us() const {\n    return ToValue<T>();\n  }\n  template <typename T = int64_t>\n  T ns() const {\n    return ToMultiple<1000, T>();\n  }\n\n  constexpr int64_t seconds_or(int64_t fallback_value) const {\n    return ToFractionOr<1000000>(fallback_value);\n  }\n  constexpr int64_t ms_or(int64_t fallback_value) const {\n    return ToFractionOr<1000>(fallback_value);\n  }\n  constexpr int64_t us_or(int64_t fallback_value) const {\n    return ToValueOr(fallback_value);\n  }\n\n  TimeDelta Abs() const { return TimeDelta::us(std::abs(us())); }\n\n private:\n  friend class rtc_units_impl::UnitBase<TimeDelta>;\n  using RelativeUnit::RelativeUnit;\n  static constexpr bool one_sided = false;\n};\n\nstd::string ToString(TimeDelta value);\ninline std::string ToLogString(TimeDelta value) {\n  return ToString(value);\n}\n\n#ifdef UNIT_TEST\ninline std::ostream& operator<<(  // no-presubmit-check TODO(webrtc:8982)\n    std::ostream& stream,         // no-presubmit-check TODO(webrtc:8982)\n    TimeDelta value) {\n  return stream << ToString(value);\n}\n#endif  // UNIT_TEST\n\n}  // namespace webrtc\n\n#endif  // API_UNITS_TIME_DELTA_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/timestamp.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"api/units/timestamp.h\"\n\n#include <sstream>\n\nnamespace webrtc {\n\nstd::string ToString(Timestamp value) {\n  std::ostringstream sb;\n  if (value.IsPlusInfinity()) {\n    sb << \"+inf ms\";\n  } else if (value.IsMinusInfinity()) {\n    sb << \"-inf ms\";\n  } else {\n    if (value.ms() % 1000 == 0)\n      sb << value.seconds() << \" s\";\n    else\n      sb << value.ms() << \" ms\";\n  }\n  return sb.str();\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/api/units/timestamp.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef API_UNITS_TIMESTAMP_H_\n#define API_UNITS_TIMESTAMP_H_\n\n#include \"api/units/time_delta.h\"\n\n#include <string>\n#include <type_traits>\n#ifdef UNIT_TEST\n#include <ostream>  // no-presubmit-check TODO(webrtc:8982)\n#endif              // UNIT_TEST\n\nnamespace webrtc {\n// Timestamp represents the time that has passed since some unspecified epoch.\n// The epoch is assumed to be before any represented timestamps, this means that\n// negative values are not valid. The most notable feature is that the\n// difference of two Timestamps results in a TimeDelta.\nclass Timestamp final : public rtc_units_impl::UnitBase<Timestamp> {\n public:\n  Timestamp() = delete;\n\n  template <int64_t seconds>\n  static constexpr Timestamp Seconds() {\n    return FromStaticFraction<seconds, 1000000>();\n  }\n  template <int64_t ms>\n  static constexpr Timestamp Millis() {\n    return FromStaticFraction<ms, 1000>();\n  }\n  template <int64_t us>\n  static constexpr Timestamp Micros() {\n    return FromStaticValue<us>();\n  }\n\n  template <typename T>\n  static Timestamp seconds(T seconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000000>(seconds);\n  }\n  template <typename T>\n  static Timestamp ms(T milliseconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromFraction<1000>(milliseconds);\n  }\n  template <typename T>\n  static Timestamp us(T microseconds) {\n    static_assert(std::is_arithmetic<T>::value, \"\");\n    return FromValue(microseconds);\n  }\n  template <typename T = int64_t>\n  T seconds() const {\n    return ToFraction<1000000, T>();\n  }\n  template <typename T = int64_t>\n  T ms() const {\n    return ToFraction<1000, T>();\n  }\n  template <typename T = int64_t>\n  T us() const {\n    return ToValue<T>();\n  }\n\n  constexpr int64_t seconds_or(int64_t fallback_value) const {\n    return ToFractionOr<1000000>(fallback_value);\n  }\n  constexpr int64_t ms_or(int64_t fallback_value) const {\n    return ToFractionOr<1000>(fallback_value);\n  }\n  constexpr int64_t us_or(int64_t fallback_value) const {\n    return ToValueOr(fallback_value);\n  }\n\n  Timestamp operator+(const TimeDelta delta) const {\n    if (IsPlusInfinity() || delta.IsPlusInfinity()) {\n      // RTC_DCHECK(!IsMinusInfinity());\n      // RTC_DCHECK(!delta.IsMinusInfinity());\n      return PlusInfinity();\n    } else if (IsMinusInfinity() || delta.IsMinusInfinity()) {\n      // RTC_DCHECK(!IsPlusInfinity());\n      // RTC_DCHECK(!delta.IsPlusInfinity());\n      return MinusInfinity();\n    }\n    return Timestamp::us(us() + delta.us());\n  }\n  Timestamp operator-(const TimeDelta delta) const {\n    if (IsPlusInfinity() || delta.IsMinusInfinity()) {\n      // RTC_DCHECK(!IsMinusInfinity());\n      // RTC_DCHECK(!delta.IsPlusInfinity());\n      return PlusInfinity();\n    } else if (IsMinusInfinity() || delta.IsPlusInfinity()) {\n      // RTC_DCHECK(!IsPlusInfinity());\n      // RTC_DCHECK(!delta.IsMinusInfinity());\n      return MinusInfinity();\n    }\n    return Timestamp::us(us() - delta.us());\n  }\n  TimeDelta operator-(const Timestamp other) const {\n    if (IsPlusInfinity() || other.IsMinusInfinity()) {\n      // RTC_DCHECK(!IsMinusInfinity());\n      // RTC_DCHECK(!other.IsPlusInfinity());\n      return TimeDelta::PlusInfinity();\n    } else if (IsMinusInfinity() || other.IsPlusInfinity()) {\n      // RTC_DCHECK(!IsPlusInfinity());\n      // RTC_DCHECK(!other.IsMinusInfinity());\n      return TimeDelta::MinusInfinity();\n    }\n    return TimeDelta::us(us() - other.us());\n  }\n  Timestamp& operator-=(const TimeDelta delta) {\n    *this = *this - delta;\n    return *this;\n  }\n  Timestamp& operator+=(const TimeDelta delta) {\n    *this = *this + delta;\n    return *this;\n  }\n\n private:\n  friend class rtc_units_impl::UnitBase<Timestamp>;\n  using UnitBase::UnitBase;\n  static constexpr bool one_sided = true;\n};\n\nstd::string ToString(Timestamp value);\ninline std::string ToLogString(Timestamp value) {\n  return ToString(value);\n}\n\n#ifdef UNIT_TEST\ninline std::ostream& operator<<(  // no-presubmit-check TODO(webrtc:8982)\n    std::ostream& stream,         // no-presubmit-check TODO(webrtc:8982)\n    Timestamp value) {\n  return stream << ToString(value);\n}\n#endif  // UNIT_TEST\n\n}  // namespace webrtc\n\n#endif  // API_UNITS_TIMESTAMP_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send.cc",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::RtpTransportControllerSend\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"call/rtp_transport_controller_send.h\"\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/time_delta.h\"\n#include \"api/units/timestamp.h\"\n#include \"system_wrappers/source/field_trial.h\"\n#include \"modules/congestion_controller/goog_cc/goog_cc_network_control.h\"\n\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\n#include <absl/memory/memory.h>\n#include <absl/types/optional.h>\n#include <utility>\n#include <vector>\n\nnamespace webrtc {\nnamespace {\nstatic const size_t kMaxOverheadBytes = 500;\n\nTargetRateConstraints ConvertConstraints(int min_bitrate_bps,\n                                         int max_bitrate_bps,\n                                         int start_bitrate_bps) {\n  TargetRateConstraints msg;\n  msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n  msg.min_data_rate =\n      min_bitrate_bps >= 0 ? DataRate::bps(min_bitrate_bps) : DataRate::Zero();\n  msg.max_data_rate = max_bitrate_bps > 0 ? DataRate::bps(max_bitrate_bps)\n                                          : DataRate::Infinity();\n  if (start_bitrate_bps > 0)\n    msg.starting_rate = DataRate::bps(start_bitrate_bps);\n  return msg;\n}\n\nTargetRateConstraints ConvertConstraints(const BitrateConstraints& contraints) {\n  return ConvertConstraints(contraints.min_bitrate_bps,\n                            contraints.max_bitrate_bps,\n                            contraints.start_bitrate_bps);\n}\n}  // namespace\n\nRtpTransportControllerSend::RtpTransportControllerSend(\n    PacketRouter* packet_router,\n    NetworkStatePredictorFactoryInterface* predictor_factory,\n    NetworkControllerFactoryInterface* controller_factory,\n    const BitrateConstraints& bitrate_config)\n    : packet_router_(packet_router),\n      pacer_(packet_router_),\n      observer_(nullptr),\n      controller_factory_override_(controller_factory),\n      process_interval_(controller_factory_override_->GetProcessInterval()),\n      last_report_block_time_(Timestamp::ms(DepLibUV::GetTimeMsInt64())),\n      send_side_bwe_with_overhead_(\n          webrtc::field_trial::IsEnabled(\"WebRTC-SendSideBwe-WithOverhead\")),\n      transport_overhead_bytes_per_packet_(0),\n      network_available_(false) {\n  initial_config_.constraints = ConvertConstraints(bitrate_config);\n  initial_config_.key_value_config = &trial_based_config_;\n\n  // RTC_DCHECK(bitrate_config.start_bitrate_bps > 0);\n  MS_ASSERT(bitrate_config.start_bitrate_bps > 0, \"start bitrate must be > 0\");\n\n  pacer_.SetPacingRates(bitrate_config.start_bitrate_bps, 0);\n}\n\nRtpTransportControllerSend::~RtpTransportControllerSend() {\n}\n\nvoid RtpTransportControllerSend::UpdateControlState() {\n  absl::optional<TargetTransferRate> update = control_handler_->GetUpdate();\n  if (!update)\n    return;\n\n  // We won't create control_handler_ until we have an observers.\n  // RTC_DCHECK(observer_ != nullptr);\n  MS_ASSERT(observer_ != nullptr, \"no observer\");\n\n  observer_->OnTargetTransferRate(*update);\n}\n\nPacketRouter* RtpTransportControllerSend::packet_router() {\n  return this->packet_router_;\n}\n\nNetworkStateEstimateObserver*\nRtpTransportControllerSend::network_state_estimate_observer() {\n  return this;\n}\n\nTransportFeedbackObserver*\nRtpTransportControllerSend::transport_feedback_observer() {\n  return this;\n}\n\nPacedSender* RtpTransportControllerSend::packet_sender() {\n  return &pacer_;\n}\n\nvoid RtpTransportControllerSend::SetAllocatedSendBitrateLimits(\n    int min_send_bitrate_bps,\n    int max_padding_bitrate_bps,\n    int max_total_bitrate_bps) {\n  streams_config_.min_total_allocated_bitrate =\n      DataRate::bps(min_send_bitrate_bps);\n  streams_config_.max_padding_rate = DataRate::bps(max_padding_bitrate_bps);\n  streams_config_.max_total_allocated_bitrate =\n      DataRate::bps(max_total_bitrate_bps);\n  UpdateStreamsConfig();\n}\n\nvoid RtpTransportControllerSend::SetClientBitratePreferences(const TargetRateConstraints& constraints)\n{\n  controller_->OnTargetRateConstraints(constraints);\n}\n\nvoid RtpTransportControllerSend::SetPacingFactor(float pacing_factor) {\n  streams_config_.pacing_factor = pacing_factor;\n  UpdateStreamsConfig();\n}\n\nvoid RtpTransportControllerSend::RegisterTargetTransferRateObserver(\n    TargetTransferRateObserver* observer) {\n\n    // RTC_DCHECK(observer_ == nullptr);\n    MS_ASSERT(observer_ == nullptr, \"observer already set\");\n\n    observer_ = observer;\n    observer_->OnStartRateUpdate(*initial_config_.constraints.starting_rate);\n    MaybeCreateControllers();\n}\n\nvoid RtpTransportControllerSend::OnNetworkAvailability(bool network_available) {\n  MS_DEBUG_DEV(\"<<<<< network_available:%s\", network_available ? \"true\" : \"false\");\n\n  NetworkAvailability msg;\n  msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n  msg.network_available = network_available;\n\n  if (network_available_ == msg.network_available)\n    return;\n  network_available_ = msg.network_available;\n  if (network_available_) {\n    pacer_.Resume();\n  } else {\n    pacer_.Pause();\n  }\n  pacer_.UpdateOutstandingData(0);\n\n  control_handler_->SetNetworkAvailability(network_available_);\n  PostUpdates(controller_->OnNetworkAvailability(msg));\n  UpdateControlState();\n}\n\nRtcpBandwidthObserver* RtpTransportControllerSend::GetBandwidthObserver() {\n  return this;\n}\n\nvoid RtpTransportControllerSend::EnablePeriodicAlrProbing(bool enable) {\n\tstreams_config_.requests_alr_probing = enable;\n  UpdateStreamsConfig();\n}\n\nvoid RtpTransportControllerSend::OnSentPacket(\n    const rtc::SentPacket& sent_packet, size_t size) {\n  MS_DEBUG_DEV(\"<<<<< size:%zu\", size);\n\n  absl::optional<SentPacket> packet_msg =\n      transport_feedback_adapter_.ProcessSentPacket(sent_packet);\n  if (packet_msg)\n    PostUpdates(controller_->OnSentPacket(*packet_msg));\n  pacer_.UpdateOutstandingData(\n      transport_feedback_adapter_.GetOutstandingData().bytes());\n}\n\nvoid RtpTransportControllerSend::OnTransportOverheadChanged(\n    size_t transport_overhead_bytes_per_packet) {\n  MS_DEBUG_DEV(\"<<<<< transport_overhead_bytes_per_packet:%zu\", transport_overhead_bytes_per_packet);\n\n  if (transport_overhead_bytes_per_packet >= kMaxOverheadBytes) {\n    MS_ERROR(\"transport overhead exceeds: %zu\", kMaxOverheadBytes);\n    return;\n  }\n}\n\nvoid RtpTransportControllerSend::OnReceivedEstimatedBitrate(uint32_t bitrate) {\n  MS_DEBUG_DEV(\"<<<<< bitrate:%zu\", bitrate);\n\n  RemoteBitrateReport msg;\n  msg.receive_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n  msg.bandwidth = DataRate::bps(bitrate);\n\n  PostUpdates(controller_->OnRemoteBitrateReport(msg));\n}\n\nvoid RtpTransportControllerSend::OnReceivedRtcpReceiverReport(\n    const ReportBlockList& report_blocks,\n    int64_t rtt_ms,\n    int64_t now_ms) {\n  MS_DEBUG_DEV(\"<<<<< rtt_ms:%\" PRIi64, rtt_ms);\n\n  OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms);\n\n  RoundTripTimeUpdate report;\n  report.receive_time = Timestamp::ms(now_ms);\n  report.round_trip_time = TimeDelta::ms(rtt_ms);\n  report.smoothed = false;\n  if (!report.round_trip_time.IsZero())\n    PostUpdates(controller_->OnRoundTripTimeUpdate(report));\n}\n\nvoid RtpTransportControllerSend::OnAddPacket(\n    const RtpPacketSendInfo& packet_info) {\n  transport_feedback_adapter_.AddPacket(\n      packet_info,\n      send_side_bwe_with_overhead_ ? transport_overhead_bytes_per_packet_.load()\n                                   : 0,\n      Timestamp::ms(DepLibUV::GetTimeMsInt64()));\n}\n\nvoid RtpTransportControllerSend::OnTransportFeedback(\n    const RTC::RTCP::FeedbackRtpTransportPacket& feedback) {\n  MS_DEBUG_DEV(\"<<<<<\");\n\n  absl::optional<TransportPacketsFeedback> feedback_msg =\n      transport_feedback_adapter_.ProcessTransportFeedback(\n          feedback, Timestamp::ms(DepLibUV::GetTimeMsInt64()));\n  if (feedback_msg)\n    PostUpdates(controller_->OnTransportPacketsFeedback(*feedback_msg));\n  pacer_.UpdateOutstandingData(\n      transport_feedback_adapter_.GetOutstandingData().bytes());\n}\n\nvoid RtpTransportControllerSend::OnRemoteNetworkEstimate(\n    NetworkStateEstimate estimate) {\n  estimate.update_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n  controller_->OnNetworkStateEstimate(estimate);\n}\n\nvoid RtpTransportControllerSend::Process()\n{\n  // TODO (ibc): Must really check if we need this ugly periodic timer which is called\n  // every 5ms.\n  // NOTE: Yes, otherwise the pssss scenario does not work:\n  // https://bitbucket.org/versatica/mediasoup/issues/12/no-probation-if-no-real-media\n\tUpdateControllerWithTimeInterval();\n}\n\nvoid RtpTransportControllerSend::MaybeCreateControllers() {\n  // RTC_DCHECK(!controller_);\n  // RTC_DCHECK(!control_handler_);\n  MS_ASSERT(!controller_, \"controller already set\");\n  MS_ASSERT(!control_handler_, \"controller handler already set\");\n\n  control_handler_ = absl::make_unique<CongestionControlHandler>();\n\n  initial_config_.constraints.at_time =\n      Timestamp::ms(DepLibUV::GetTimeMsInt64());\n\n  controller_ = controller_factory_override_->Create(initial_config_);\n  process_interval_ = controller_factory_override_->GetProcessInterval();\n\n  UpdateControllerWithTimeInterval();\n}\n\nvoid RtpTransportControllerSend::UpdateControllerWithTimeInterval() {\n  MS_DEBUG_DEV(\"<<<<<\");\n\n  // RTC_DCHECK(controller_);\n  MS_ASSERT(controller_, \"controller not set\");\n\n  ProcessInterval msg;\n  msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n\n  PostUpdates(controller_->OnProcessInterval(msg));\n}\n\nvoid RtpTransportControllerSend::UpdateStreamsConfig() {\n  MS_DEBUG_DEV(\"<<<<<\");\n\n  streams_config_.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64());\n  if (controller_)\n    PostUpdates(controller_->OnStreamsConfig(streams_config_));\n}\n\nvoid RtpTransportControllerSend::PostUpdates(NetworkControlUpdate update) {\n  if (update.congestion_window) {\n    if (update.congestion_window->IsFinite())\n      pacer_.SetCongestionWindow(update.congestion_window->bytes());\n    else\n      pacer_.SetCongestionWindow(PacedSender::kNoCongestionWindow);\n  }\n  if (update.pacer_config) {\n    pacer_.SetPacingRates(update.pacer_config->data_rate().bps(),\n                          update.pacer_config->pad_rate().bps());\n  }\n\n  // TODO: REMOVE: this removes any probation.\n  // update.probe_cluster_configs.clear();\n\n  for (const auto& probe : update.probe_cluster_configs) {\n    int64_t bitrate_bps = probe.target_data_rate.bps();\n    pacer_.CreateProbeCluster(bitrate_bps, probe.id);\n  }\n  if (update.target_rate) {\n    control_handler_->SetTargetRate(*update.target_rate);\n    UpdateControlState();\n  }\n}\n\nvoid RtpTransportControllerSend::OnReceivedRtcpReceiverReportBlocks(\n    const ReportBlockList& report_blocks,\n    int64_t now_ms) {\n  if (report_blocks.empty())\n    return;\n\n  int total_packets_lost_delta = 0;\n  int total_packets_delta = 0;\n\n  // Compute the packet loss from all report blocks.\n  for (const RTCPReportBlock& report_block : report_blocks) {\n    auto it = last_report_blocks_.find(report_block.source_ssrc);\n    if (it != last_report_blocks_.end()) {\n      auto number_of_packets = report_block.extended_highest_sequence_number -\n                               it->second.extended_highest_sequence_number;\n      total_packets_delta += number_of_packets;\n      auto lost_delta = report_block.packets_lost - it->second.packets_lost;\n      total_packets_lost_delta += lost_delta;\n    }\n    last_report_blocks_[report_block.source_ssrc] = report_block;\n  }\n  // Can only compute delta if there has been previous blocks to compare to. If\n  // not, total_packets_delta will be unchanged and there's nothing more to do.\n  if (!total_packets_delta)\n    return;\n  int packets_received_delta = total_packets_delta - total_packets_lost_delta;\n  // To detect lost packets, at least one packet has to be received. This check\n  // is needed to avoid bandwith detection update in\n  // VideoSendStreamTest.SuspendBelowMinBitrate\n\n  if (packets_received_delta < 1)\n    return;\n  Timestamp now = Timestamp::ms(now_ms);\n  TransportLossReport msg;\n  msg.packets_lost_delta = total_packets_lost_delta;\n  msg.packets_received_delta = packets_received_delta;\n  msg.receive_time = now;\n  msg.start_time = last_report_block_time_;\n  msg.end_time = now;\n\n  PostUpdates(controller_->OnTransportLossReport(msg));\n  last_report_block_time_ = now;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send.h",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_\n#define CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_\n\n#include \"api/network_state_predictor.h\"\n#include \"api/transport/network_control.h\"\n#include \"api/transport/field_trial_based_config.h\"\n// #include \"call/rtp_bitrate_configurator.h\"\n#include \"call/rtp_transport_controller_send_interface.h\"\n#include \"modules/congestion_controller/rtp/control_handler.h\"\n#include \"modules/congestion_controller/rtp/transport_feedback_adapter.h\"\n#include \"rtc_base/constructor_magic.h\"\n#include \"modules/pacing/packet_router.h\"\n#include \"modules/pacing/paced_sender.h\"\n\n#include <atomic>\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace webrtc {\n\n// TODO(nisse): When we get the underlying transports here, we should\n// have one object implementing RtpTransportControllerSendInterface\n// per transport, sharing the same congestion controller.\nclass RtpTransportControllerSend final\n    : public RtpTransportControllerSendInterface,\n      public RtcpBandwidthObserver,\n      public TransportFeedbackObserver,\n      public NetworkStateEstimateObserver {\n public:\n  RtpTransportControllerSend(\n      PacketRouter* packet_router,\n      NetworkStatePredictorFactoryInterface* predictor_factory,\n      NetworkControllerFactoryInterface* controller_factory,\n      const BitrateConstraints& bitrate_config);\n  ~RtpTransportControllerSend() override;\n\n  PacketRouter* packet_router() override;\n\n  NetworkStateEstimateObserver* network_state_estimate_observer() override;\n  TransportFeedbackObserver* transport_feedback_observer() override;\n  PacedSender* packet_sender() override;\n\n  void SetAllocatedSendBitrateLimits(int min_send_bitrate_bps,\n                                     int max_padding_bitrate_bps,\n                                     int max_total_bitrate_bps) override;\n\n  void SetClientBitratePreferences(const TargetRateConstraints& constraints);\n\n  void SetPacingFactor(float pacing_factor) override;\n  void RegisterTargetTransferRateObserver(\n      TargetTransferRateObserver* observer) override;\n  void OnNetworkAvailability(bool network_available) override;\n  RtcpBandwidthObserver* GetBandwidthObserver() override;\n  void EnablePeriodicAlrProbing(bool enable) override;\n  void OnSentPacket(const rtc::SentPacket& sent_packet, size_t size) override;\n\n  void OnTransportOverheadChanged(\n      size_t transport_overhead_per_packet) override;\n\n  // Implements RtcpBandwidthObserver interface\n  void OnReceivedEstimatedBitrate(uint32_t bitrate) override;\n  void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks,\n                                    int64_t rtt,\n                                    int64_t now_ms) override;\n\n  // Implements TransportFeedbackObserver interface\n  void OnAddPacket(const RtpPacketSendInfo& packet_info) override;\n  void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback) override;\n\n  // Implements NetworkStateEstimateObserver interface\n  void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override;\n\n  void Process();\n\n private:\n  void MaybeCreateControllers();\n\n  void UpdateControllerWithTimeInterval();\n\n  void UpdateStreamsConfig();\n  void OnReceivedRtcpReceiverReportBlocks(const ReportBlockList& report_blocks,\n                                          int64_t now_ms);\n  void PostUpdates(NetworkControlUpdate update);\n  void UpdateControlState();\n\n  const FieldTrialBasedConfig trial_based_config_;\n\n  PacketRouter* packet_router_;\n  PacedSender pacer_;\n\n  TargetTransferRateObserver* observer_;\n\n  NetworkControllerFactoryInterface* const controller_factory_override_;\n\n  TransportFeedbackAdapter transport_feedback_adapter_;\n\n  std::unique_ptr<CongestionControlHandler> control_handler_;\n\n  std::unique_ptr<NetworkControllerInterface> controller_ ;\n\n  TimeDelta process_interval_;\n\n  std::map<uint32_t, RTCPReportBlock> last_report_blocks_;\n  Timestamp last_report_block_time_;\n\n  NetworkControllerConfig initial_config_;\n\tStreamsConfig streams_config_;\n\n  const bool send_side_bwe_with_overhead_;\n  // MS_NOTE: not used.\n  // const bool add_pacing_to_cwin_;\n  // Transport overhead is written by OnNetworkRouteChanged and read by\n  // AddPacket.\n  // TODO(srte): Remove atomic when feedback adapter runs on task queue.\n  std::atomic<size_t> transport_overhead_bytes_per_packet_;\n\n  bool network_available_;\n\n  // TODO(perkj): |task_queue_| is supposed to replace |process_thread_|.\n  // |task_queue_| is defined last to ensure all pending tasks are cancelled\n  // and deleted before any other members.\n  RTC_DISALLOW_COPY_AND_ASSIGN(RtpTransportControllerSend);\n};\n\n}  // namespace webrtc\n\n#endif  // CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send_interface.h",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_\n#define CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_\n\n#include \"api/bitrate_constraints.h\"\n#include \"api/transport/bitrate_settings.h\"\n// #include \"call/rtp_config.h\"\n// #include \"modules/rtp_rtcp/include/report_block_data.h\"\n// #include \"modules/rtp_rtcp/include/rtp_packet_sender.h\"\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n// #include \"modules/rtp_rtcp/source/rtp_packet_received.h\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace rtc {\nstruct SentPacket;\nstruct NetworkRoute;\n}  // namespace rtc\nnamespace webrtc {\n\nclass TargetTransferRateObserver;\nclass Transport;\nclass PacedSender;\nclass PacketFeedbackObserver;\nclass PacketRouter;\nclass RateLimiter;\nclass RtcpBandwidthObserver;\nclass RtpPacketSender;\nclass TransportFeedbackObserver;\n\n// An RtpTransportController should own everything related to the RTP\n// transport to/from a remote endpoint. We should have separate\n// interfaces for send and receive side, even if they are implemented\n// by the same class. This is an ongoing refactoring project. At some\n// point, this class should be promoted to a public api under\n// webrtc/api/rtp/.\n//\n// For a start, this object is just a collection of the objects needed\n// by the VideoSendStream constructor. The plan is to move ownership\n// of all RTP-related objects here, and add methods to create per-ssrc\n// objects which would then be passed to VideoSendStream. Eventually,\n// direct accessors like packet_router() should be removed.\n//\n// This should also have a reference to the underlying\n// webrtc::Transport(s). Currently, webrtc::Transport is implemented by\n// WebRtcVideoChannel and WebRtcVoiceMediaChannel, and owned by\n// WebrtcSession. Video and audio always uses different transport\n// objects, even in the common case where they are bundled over the\n// same underlying transport.\n//\n// Extracting the logic of the webrtc::Transport from BaseChannel and\n// subclasses into a separate class seems to be a prerequesite for\n// moving the transport here.\nclass RtpTransportControllerSendInterface {\n public:\n  virtual ~RtpTransportControllerSendInterface() {}\n  virtual PacketRouter* packet_router() = 0;\n\n  virtual NetworkStateEstimateObserver* network_state_estimate_observer() = 0;\n  virtual TransportFeedbackObserver* transport_feedback_observer() = 0;\n\n  virtual PacedSender* packet_sender() = 0;\n\n  // SetAllocatedSendBitrateLimits sets bitrates limits imposed by send codec\n  // settings.\n  // |min_send_bitrate_bps| is the total minimum send bitrate required by all\n  // sending streams.  This is the minimum bitrate the PacedSender will use.\n  // |max_padding_bitrate_bps| is the max\n  // bitrate the send streams request for padding. This can be higher than the\n  // current network estimate and tells the PacedSender how much it should max\n  // pad unless there is real packets to send.\n  virtual void SetAllocatedSendBitrateLimits(int min_send_bitrate_bps,\n                                             int max_padding_bitrate_bps,\n                                             int total_bitrate_bps) = 0;\n\n  virtual void SetPacingFactor(float pacing_factor) = 0;\n\n  // MS_NOTE: not used.\n  // virtual void RegisterPacketFeedbackObserver(\n      // PacketFeedbackObserver* observer) = 0;\n  // virtual void DeRegisterPacketFeedbackObserver(\n      // PacketFeedbackObserver* observer) = 0;\n  virtual void RegisterTargetTransferRateObserver(\n      TargetTransferRateObserver* observer) = 0;\n  virtual void OnNetworkAvailability(bool network_available) = 0;\n  virtual RtcpBandwidthObserver* GetBandwidthObserver() = 0;\n  virtual void EnablePeriodicAlrProbing(bool enable) = 0;\n  // MS_NOTE: signature changed.\n  virtual void OnSentPacket(const rtc::SentPacket& sent_packet, size_t size) = 0;\n\n  // MS_NOTE: not used.\n  // virtual void SetClientBitratePreferences(\n      // const BitrateSettings& preferences) = 0;\n\n  virtual void OnTransportOverheadChanged(\n      size_t transport_overhead_per_packet) = 0;\n};\n\n}  // namespace webrtc\n\n#endif  // CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/mediasoup_helpers.h",
    "content": "#ifndef LIBWEBRTC_MEDIASOUP_HELPERS_H\n#define LIBWEBRTC_MEDIASOUP_HELPERS_H\n\n#include \"modules/rtp_rtcp/source/rtp_packet/transport_feedback.h\"\n\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\n#include <cstdint>\n#include <vector>\n\nnamespace mediasoup_helpers\n{\n\t/**\n\t * Helpers to retrieve necessary data from mediasoup FeedbackRtpTransportPacket.\n\t */\n\tnamespace FeedbackRtpTransport\n\t{\n\t\tconst std::vector<webrtc::rtcp::ReceivedPacket> GetReceivedPackets(\n\t\t\tconst RTC::RTCP::FeedbackRtpTransportPacket* packet)\n\t\t{\n\t\t\tstd::vector<webrtc::rtcp::ReceivedPacket> receivedPackets;\n\n\t\t\tfor (auto& packetResult : packet->GetPacketResults())\n\t\t\t{\n\t\t\t  if (packetResult.received)\n\t\t\t    receivedPackets.emplace_back(packetResult.sequenceNumber, packetResult.delta);\n\t\t\t}\n\n\t\t\treturn receivedPackets;\n\t\t}\n\n\t\t// Get the reference time in microseconds, including any precision loss.\n\t\tint64_t GetBaseTimeUs(const RTC::RTCP::FeedbackRtpTransportPacket* packet)\n\t\t{\n\t\t\treturn packet->GetReferenceTimestamp() * 1000;\n\t\t}\n\n\t\t// Get the unwrapped delta between current base time and |prev_timestamp_us|.\n\t\tint64_t GetBaseDeltaUs(\n\t\t  const RTC::RTCP::FeedbackRtpTransportPacket* packet, int64_t prev_timestamp_us)\n\t\t{\n\t\t\treturn packet->GetBaseDelta(prev_timestamp_us / 1000) * 1000;\n\t\t}\n\t} // namespace FeedbackRtpTransport\n} // namespace mediasoup_helpers\n\n#endif\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/bitrate_controller/loss_based_bandwidth_estimation.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/time_delta.h\"\n#include \"system_wrappers/source/field_trial.h\"\n\n#include <algorithm>\n#include <string>\n#include <vector>\n\nnamespace webrtc {\nnamespace {\nconst char kBweLossBasedControl[] = \"WebRTC-Bwe-LossBasedControl\";\n\n// Increase slower when RTT is high.\ndouble GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) {\n  // Clamp the RTT\n  if (rtt < config.increase_low_rtt) {\n    rtt = config.increase_low_rtt;\n  } else if (rtt > config.increase_high_rtt) {\n    rtt = config.increase_high_rtt;\n  }\n  auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt;\n  if (rtt_range <= TimeDelta::Zero()) {\n    // RTC_DCHECK(false);  // Only on misconfiguration.\n    return config.min_increase_factor;\n  }\n  auto rtt_offset = rtt - config.increase_low_rtt;\n  auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0));\n  auto factor_range = config.max_increase_factor - config.min_increase_factor;\n  return config.min_increase_factor + (1 - relative_offset) * factor_range;\n}\n\ndouble LossFromBitrate(DataRate bitrate,\n                       DataRate loss_bandwidth_balance,\n                       double exponent) {\n  if (loss_bandwidth_balance >= bitrate)\n    return 1.0;\n  return pow(loss_bandwidth_balance / bitrate, exponent);\n}\n\nDataRate BitrateFromLoss(double loss,\n                         DataRate loss_bandwidth_balance,\n                         double exponent) {\n  if (exponent <= 0) {\n    // RTC_DCHECK(false);\n    return DataRate::Infinity();\n  }\n  if (loss < 1e-5)\n    return DataRate::Infinity();\n  return loss_bandwidth_balance * pow(loss, -1.0 / exponent);\n}\n\ndouble ExponentialUpdate(TimeDelta window, TimeDelta interval) {\n  // Use the convention that exponential window length (which is really\n  // infinite) is the time it takes to dampen to 1/e.\n  if (window <= TimeDelta::Zero()) {\n    // RTC_DCHECK(false);\n    return 1.0f;\n  }\n  return 1.0f - exp(interval / window * -1.0);\n}\n\n}  // namespace\n\nLossBasedControlConfig::LossBasedControlConfig()\n    : enabled(field_trial::IsEnabled(kBweLossBasedControl)),\n      min_increase_factor(\"min_incr\", 1.02),\n      max_increase_factor(\"max_incr\", 1.08),\n      increase_low_rtt(\"incr_low_rtt\", TimeDelta::ms(200)),\n      increase_high_rtt(\"incr_high_rtt\", TimeDelta::ms(800)),\n      decrease_factor(\"decr\", 0.99),\n      loss_window(\"loss_win\", TimeDelta::ms(800)),\n      loss_max_window(\"loss_max_win\", TimeDelta::ms(800)),\n      acknowledged_rate_max_window(\"ackrate_max_win\", TimeDelta::ms(800)),\n      increase_offset(\"incr_offset\", DataRate::bps(1000)),\n      loss_bandwidth_balance_increase(\"balance_incr\", DataRate::kbps(0.5)),\n      loss_bandwidth_balance_decrease(\"balance_decr\", DataRate::kbps(4)),\n      loss_bandwidth_balance_exponent(\"exponent\", 0.5),\n      allow_resets(\"resets\", false),\n      decrease_interval(\"decr_intvl\", TimeDelta::ms(300)),\n      loss_report_timeout(\"timeout\", TimeDelta::ms(6000)) {\n  std::string trial_string = field_trial::FindFullName(kBweLossBasedControl);\n  ParseFieldTrial(\n      {&min_increase_factor, &max_increase_factor, &increase_low_rtt,\n       &increase_high_rtt, &decrease_factor, &loss_window, &loss_max_window,\n       &acknowledged_rate_max_window, &increase_offset,\n       &loss_bandwidth_balance_increase, &loss_bandwidth_balance_decrease,\n       &loss_bandwidth_balance_exponent, &allow_resets, &decrease_interval,\n       &loss_report_timeout},\n      trial_string);\n}\nLossBasedControlConfig::LossBasedControlConfig(const LossBasedControlConfig&) =\n    default;\nLossBasedControlConfig::~LossBasedControlConfig() = default;\n\nLossBasedBandwidthEstimation::LossBasedBandwidthEstimation()\n    : config_(LossBasedControlConfig()),\n      average_loss_(0),\n      average_loss_max_(0),\n      loss_based_bitrate_(DataRate::Zero()),\n      acknowledged_bitrate_max_(DataRate::Zero()),\n      acknowledged_bitrate_last_update_(Timestamp::MinusInfinity()),\n      time_last_decrease_(Timestamp::MinusInfinity()),\n      has_decreased_since_last_loss_report_(false),\n      last_loss_packet_report_(Timestamp::MinusInfinity()),\n      last_loss_ratio_(0) {}\n\nvoid LossBasedBandwidthEstimation::UpdateLossStatistics(\n    const std::vector<PacketResult>& packet_results,\n    Timestamp at_time) {\n  if (packet_results.empty()) {\n    // RTC_DCHECK(false);\n    return;\n  }\n  int loss_count = 0;\n  for (const auto& pkt : packet_results) {\n    loss_count += pkt.receive_time.IsInfinite() ? 1 : 0;\n  }\n  last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size();\n  const TimeDelta time_passed = last_loss_packet_report_.IsFinite()\n                                    ? at_time - last_loss_packet_report_\n                                    : TimeDelta::seconds(1);\n  last_loss_packet_report_ = at_time;\n  has_decreased_since_last_loss_report_ = false;\n\n  average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) *\n                   (last_loss_ratio_ - average_loss_);\n  if (average_loss_ > average_loss_max_) {\n    average_loss_max_ = average_loss_;\n  } else {\n    average_loss_max_ +=\n        ExponentialUpdate(config_.loss_max_window, time_passed) *\n        (average_loss_ - average_loss_max_);\n  }\n}\n\nvoid LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate(\n    DataRate acknowledged_bitrate,\n    Timestamp at_time) {\n  const TimeDelta time_passed =\n      acknowledged_bitrate_last_update_.IsFinite()\n          ? at_time - acknowledged_bitrate_last_update_\n          : TimeDelta::seconds(1);\n  acknowledged_bitrate_last_update_ = at_time;\n  if (acknowledged_bitrate > acknowledged_bitrate_max_) {\n    acknowledged_bitrate_max_ = acknowledged_bitrate;\n  } else {\n    acknowledged_bitrate_max_ -=\n        ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) *\n        (acknowledged_bitrate_max_ - acknowledged_bitrate);\n  }\n}\n\nvoid LossBasedBandwidthEstimation::Update(Timestamp at_time,\n                                          DataRate min_bitrate,\n                                          TimeDelta last_round_trip_time) {\n  // Only increase if loss has been low for some time.\n  const double loss_estimate_for_increase = average_loss_max_;\n  // Avoid multiple decreases from averaging over one loss spike.\n  const double loss_estimate_for_decrease =\n      std::min(average_loss_, last_loss_ratio_);\n  const bool allow_decrease =\n      !has_decreased_since_last_loss_report_ &&\n      (at_time - time_last_decrease_ >=\n       last_round_trip_time + config_.decrease_interval);\n\n  if (loss_estimate_for_increase < loss_increase_threshold()) {\n    // Increase bitrate by RTT-adaptive ratio.\n    DataRate new_increased_bitrate =\n        min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) +\n        config_.increase_offset;\n    // The bitrate that would make the loss \"just high enough\".\n    const DataRate new_increased_bitrate_cap = BitrateFromLoss(\n        loss_estimate_for_increase, config_.loss_bandwidth_balance_increase,\n        config_.loss_bandwidth_balance_exponent);\n    new_increased_bitrate =\n        std::min(new_increased_bitrate, new_increased_bitrate_cap);\n    loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_);\n  } else if (loss_estimate_for_decrease > loss_decrease_threshold() &&\n             allow_decrease) {\n    // The bitrate that would make the loss \"just acceptable\".\n    const DataRate new_decreased_bitrate_floor = BitrateFromLoss(\n        loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease,\n        config_.loss_bandwidth_balance_exponent);\n    DataRate new_decreased_bitrate =\n        std::max(decreased_bitrate(), new_decreased_bitrate_floor);\n    if (new_decreased_bitrate < loss_based_bitrate_) {\n      time_last_decrease_ = at_time;\n      has_decreased_since_last_loss_report_ = true;\n      loss_based_bitrate_ = new_decreased_bitrate;\n    }\n  }\n}\n\nvoid LossBasedBandwidthEstimation::Reset(DataRate bitrate) {\n  loss_based_bitrate_ = bitrate;\n  average_loss_ = 0;\n  average_loss_max_ = 0;\n}\n\ndouble LossBasedBandwidthEstimation::loss_increase_threshold() const {\n  return LossFromBitrate(loss_based_bitrate_,\n                         config_.loss_bandwidth_balance_increase,\n                         config_.loss_bandwidth_balance_exponent);\n}\n\ndouble LossBasedBandwidthEstimation::loss_decrease_threshold() const {\n  return LossFromBitrate(loss_based_bitrate_,\n                         config_.loss_bandwidth_balance_decrease,\n                         config_.loss_bandwidth_balance_exponent);\n}\n\nDataRate LossBasedBandwidthEstimation::decreased_bitrate() const {\n  return config_.decrease_factor * acknowledged_bitrate_max_;\n}\n\nvoid LossBasedBandwidthEstimation::MaybeReset(DataRate bitrate) {\n  if (config_.allow_resets)\n    Reset(bitrate);\n}\n\nvoid LossBasedBandwidthEstimation::SetInitialBitrate(DataRate bitrate) {\n  Reset(bitrate);\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_\n#define MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/time_delta.h\"\n#include \"api/units/timestamp.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <vector>\n\nnamespace webrtc {\n\nstruct LossBasedControlConfig {\n  LossBasedControlConfig();\n  LossBasedControlConfig(const LossBasedControlConfig&);\n  LossBasedControlConfig& operator=(const LossBasedControlConfig&) = default;\n  ~LossBasedControlConfig();\n  bool enabled;\n  FieldTrialParameter<double> min_increase_factor;\n  FieldTrialParameter<double> max_increase_factor;\n  FieldTrialParameter<TimeDelta> increase_low_rtt;\n  FieldTrialParameter<TimeDelta> increase_high_rtt;\n  FieldTrialParameter<double> decrease_factor;\n  FieldTrialParameter<TimeDelta> loss_window;\n  FieldTrialParameter<TimeDelta> loss_max_window;\n  FieldTrialParameter<TimeDelta> acknowledged_rate_max_window;\n  FieldTrialParameter<DataRate> increase_offset;\n  FieldTrialParameter<DataRate> loss_bandwidth_balance_increase;\n  FieldTrialParameter<DataRate> loss_bandwidth_balance_decrease;\n  FieldTrialParameter<double> loss_bandwidth_balance_exponent;\n  FieldTrialParameter<bool> allow_resets;\n  FieldTrialParameter<TimeDelta> decrease_interval;\n  FieldTrialParameter<TimeDelta> loss_report_timeout;\n};\n\nclass LossBasedBandwidthEstimation {\n public:\n  LossBasedBandwidthEstimation();\n  void Update(Timestamp at_time,\n              DataRate min_bitrate,\n              TimeDelta last_round_trip_time);\n  void UpdateAcknowledgedBitrate(DataRate acknowledged_bitrate,\n                                 Timestamp at_time);\n  void MaybeReset(DataRate bitrate);\n  void SetInitialBitrate(DataRate bitrate);\n  bool Enabled() const { return config_.enabled; }\n  void UpdateLossStatistics(const std::vector<PacketResult>& packet_results,\n                            Timestamp at_time);\n  DataRate GetEstimate() const { return loss_based_bitrate_; }\n\n private:\n  friend class GoogCcStatePrinter;\n  void Reset(DataRate bitrate);\n  double loss_increase_threshold() const;\n  double loss_decrease_threshold() const;\n  DataRate decreased_bitrate() const;\n\n  LossBasedControlConfig config_;\n  double average_loss_;\n  double average_loss_max_;\n  DataRate loss_based_bitrate_;\n  DataRate acknowledged_bitrate_max_;\n  Timestamp acknowledged_bitrate_last_update_;\n  Timestamp time_last_decrease_;\n  bool has_decreased_since_last_loss_report_;\n  Timestamp last_loss_packet_report_;\n  double last_loss_ratio_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::SendSideBandwidthEstimation\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/bitrate_controller/send_side_bandwidth_estimation.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"system_wrappers/source/field_trial.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n#include <cstdio>\n#include <limits>\n#include <string>\n\nnamespace webrtc {\nnamespace {\nconstexpr TimeDelta kBweIncreaseInterval = TimeDelta::Millis<1000>();\nconstexpr TimeDelta kBweDecreaseInterval = TimeDelta::Millis<300>();\nconstexpr TimeDelta kStartPhase = TimeDelta::Millis<2000>();\nconstexpr TimeDelta kBweConverganceTime = TimeDelta::Millis<20000>();\nconstexpr int kLimitNumPackets = 20;\nconstexpr DataRate kDefaultMaxBitrate = DataRate::BitsPerSec<1000000000>();\nconstexpr TimeDelta kLowBitrateLogPeriod = TimeDelta::Millis<10000>();\nconstexpr TimeDelta kRtcEventLogPeriod = TimeDelta::Millis<5000>();\n// Expecting that RTCP feedback is sent uniformly within [0.5, 1.5]s intervals.\nconstexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis<5000>();\nconstexpr int kFeedbackTimeoutIntervals = 3;\nconstexpr TimeDelta kTimeoutInterval = TimeDelta::Millis<1000>();\n\nconstexpr float kDefaultLowLossThreshold = 0.02f;\nconstexpr float kDefaultHighLossThreshold = 0.1f;\nconstexpr DataRate kDefaultBitrateThreshold = DataRate::Zero();\n\nstruct UmaRampUpMetric {\n  const char* metric_name;\n  int bitrate_kbps;\n};\n\nconst UmaRampUpMetric kUmaRampupMetrics[] = {\n    {\"WebRTC.BWE.RampUpTimeTo500kbpsInMs\", 500},\n    {\"WebRTC.BWE.RampUpTimeTo1000kbpsInMs\", 1000},\n    {\"WebRTC.BWE.RampUpTimeTo2000kbpsInMs\", 2000}};\nconst size_t kNumUmaRampupMetrics =\n    sizeof(kUmaRampupMetrics) / sizeof(kUmaRampupMetrics[0]);\n\nconst char kBweLosExperiment[] = \"WebRTC-BweLossExperiment\";\n\nbool BweLossExperimentIsEnabled() {\n  std::string experiment_string =\n      webrtc::field_trial::FindFullName(kBweLosExperiment);\n  // The experiment is enabled iff the field trial string begins with \"Enabled\".\n  return experiment_string.find(\"Enabled\") == 0;\n}\n\nbool ReadBweLossExperimentParameters(float* low_loss_threshold,\n                                     float* high_loss_threshold,\n                                     uint32_t* bitrate_threshold_kbps) {\n  // RTC_DCHECK(low_loss_threshold);\n  // RTC_DCHECK(high_loss_threshold);\n  // RTC_DCHECK(bitrate_threshold_kbps);\n  std::string experiment_string =\n      webrtc::field_trial::FindFullName(kBweLosExperiment);\n  int parsed_values =\n      sscanf(experiment_string.c_str(), \"Enabled-%f,%f,%u\", low_loss_threshold,\n             high_loss_threshold, bitrate_threshold_kbps);\n  if (parsed_values == 3) {\n    // RTC_CHECK_GT(*low_loss_threshold, 0.0f)\n        // << \"Loss threshold must be greater than 0.\";\n    // RTC_CHECK_LE(*low_loss_threshold, 1.0f)\n        // << \"Loss threshold must be less than or equal to 1.\";\n    // RTC_CHECK_GT(*high_loss_threshold, 0.0f)\n        // << \"Loss threshold must be greater than 0.\";\n    // RTC_CHECK_LE(*high_loss_threshold, 1.0f)\n        // << \"Loss threshold must be less than or equal to 1.\";\n    // RTC_CHECK_LE(*low_loss_threshold, *high_loss_threshold)\n        // << \"The low loss threshold must be less than or equal to the high loss \"\n           // \"threshold.\";\n    // RTC_CHECK_GE(*bitrate_threshold_kbps, 0)\n        // << \"Bitrate threshold can't be negative.\";\n    // RTC_CHECK_LT(*bitrate_threshold_kbps,\n                 // std::numeric_limits<int>::max() / 1000)\n        // << \"Bitrate must be smaller enough to avoid overflows.\";\n    return true;\n  }\n  MS_WARN_TAG(bwe, \"Failed to parse parameters for BweLossExperiment \"\n      \"experiment from field trial string. Using default\");\n\n  *low_loss_threshold = kDefaultLowLossThreshold;\n  *high_loss_threshold = kDefaultHighLossThreshold;\n  *bitrate_threshold_kbps = kDefaultBitrateThreshold.kbps();\n  return false;\n}\n}  // namespace\n\nLinkCapacityTracker::LinkCapacityTracker()\n    : tracking_rate(\"rate\", TimeDelta::seconds(10)) {\n  ParseFieldTrial({&tracking_rate},\n                  field_trial::FindFullName(\"WebRTC-Bwe-LinkCapacity\"));\n}\n\nLinkCapacityTracker::~LinkCapacityTracker() {}\n\nvoid LinkCapacityTracker::OnOveruse(DataRate acknowledged_rate,\n                                    Timestamp at_time) {\n  capacity_estimate_bps_ =\n      std::min(capacity_estimate_bps_, acknowledged_rate.bps<double>());\n  last_link_capacity_update_ = at_time;\n}\n\nvoid LinkCapacityTracker::OnStartingRate(DataRate start_rate) {\n  if (last_link_capacity_update_.IsInfinite())\n    capacity_estimate_bps_ = start_rate.bps<double>();\n}\n\nvoid LinkCapacityTracker::OnRateUpdate(DataRate acknowledged,\n                                       Timestamp at_time) {\n  if (acknowledged.bps() > capacity_estimate_bps_) {\n    TimeDelta delta = at_time - last_link_capacity_update_;\n    double alpha = delta.IsFinite() ? exp(-(delta / tracking_rate.Get())) : 0;\n    capacity_estimate_bps_ = alpha * capacity_estimate_bps_ +\n                             (1 - alpha) * acknowledged.bps<double>();\n  }\n  last_link_capacity_update_ = at_time;\n}\n\nvoid LinkCapacityTracker::OnRttBackoff(DataRate backoff_rate,\n                                       Timestamp at_time) {\n  capacity_estimate_bps_ =\n      std::min(capacity_estimate_bps_, backoff_rate.bps<double>());\n  last_link_capacity_update_ = at_time;\n}\n\nDataRate LinkCapacityTracker::estimate() const {\n  return DataRate::bps(capacity_estimate_bps_);\n}\n\nRttBasedBackoff::RttBasedBackoff()\n    : rtt_limit_(\"limit\", TimeDelta::PlusInfinity()),\n      drop_fraction_(\"fraction\", 0.5),\n      drop_interval_(\"interval\", TimeDelta::ms(300)),\n      persist_on_route_change_(\"persist\"),\n      safe_timeout_(\"safe_timeout\", true),\n      bandwidth_floor_(\"floor\", DataRate::kbps(5)),\n      // By initializing this to plus infinity, we make sure that we never\n      // trigger rtt backoff unless packet feedback is enabled.\n      last_propagation_rtt_update_(Timestamp::PlusInfinity()),\n      last_propagation_rtt_(TimeDelta::Zero()),\n      last_packet_sent_(Timestamp::MinusInfinity()) {\n  ParseFieldTrial(\n      {&rtt_limit_, &drop_fraction_, &drop_interval_, &persist_on_route_change_,\n       &safe_timeout_, &bandwidth_floor_},\n      field_trial::FindFullName(\"WebRTC-Bwe-MaxRttLimit\"));\n}\n\nvoid RttBasedBackoff::OnRouteChange() {\n  if (!persist_on_route_change_) {\n    last_propagation_rtt_update_ = Timestamp::PlusInfinity();\n    last_propagation_rtt_ = TimeDelta::Zero();\n  }\n}\n\nvoid RttBasedBackoff::UpdatePropagationRtt(Timestamp at_time,\n                                           TimeDelta propagation_rtt) {\n  last_propagation_rtt_update_ = at_time;\n  last_propagation_rtt_ = propagation_rtt;\n}\n\nTimeDelta RttBasedBackoff::CorrectedRtt(Timestamp at_time) const {\n  TimeDelta time_since_rtt = at_time - last_propagation_rtt_update_;\n  TimeDelta timeout_correction = time_since_rtt;\n  if (safe_timeout_) {\n    // Avoid timeout when no packets are being sent.\n    TimeDelta time_since_packet_sent = at_time - last_packet_sent_;\n    timeout_correction =\n        std::max(time_since_rtt - time_since_packet_sent, TimeDelta::Zero());\n  }\n  return timeout_correction + last_propagation_rtt_;\n}\n\nRttBasedBackoff::~RttBasedBackoff() = default;\n\nSendSideBandwidthEstimation::SendSideBandwidthEstimation()\n    : lost_packets_since_last_loss_update_(0),\n      expected_packets_since_last_loss_update_(0),\n      current_bitrate_(DataRate::Zero()),\n      min_bitrate_configured_(\n          DataRate::bps(congestion_controller::GetMinBitrateBps())),\n      max_bitrate_configured_(kDefaultMaxBitrate),\n      last_low_bitrate_log_(Timestamp::MinusInfinity()),\n      has_decreased_since_last_fraction_loss_(false),\n      last_loss_feedback_(Timestamp::MinusInfinity()),\n      last_loss_packet_report_(Timestamp::MinusInfinity()),\n      last_timeout_(Timestamp::MinusInfinity()),\n      last_fraction_loss_(0),\n      last_logged_fraction_loss_(0),\n      last_round_trip_time_(TimeDelta::Zero()),\n      bwe_incoming_(DataRate::Zero()),\n      delay_based_bitrate_(DataRate::Zero()),\n      time_last_decrease_(Timestamp::MinusInfinity()),\n      first_report_time_(Timestamp::MinusInfinity()),\n      initially_lost_packets_(0),\n      bitrate_at_2_seconds_(DataRate::Zero()),\n      uma_update_state_(kNoUpdate),\n      uma_rtt_state_(kNoUpdate),\n      rampup_uma_stats_updated_(kNumUmaRampupMetrics, false),\n      last_rtc_event_log_(Timestamp::MinusInfinity()),\n      in_timeout_experiment_(\n          webrtc::field_trial::IsEnabled(\"WebRTC-FeedbackTimeout\")),\n      low_loss_threshold_(kDefaultLowLossThreshold),\n      high_loss_threshold_(kDefaultHighLossThreshold),\n      bitrate_threshold_(kDefaultBitrateThreshold) {\n  // RTC_DCHECK(event_log);\n  if (BweLossExperimentIsEnabled()) {\n    uint32_t bitrate_threshold_kbps;\n    if (ReadBweLossExperimentParameters(&low_loss_threshold_,\n                                        &high_loss_threshold_,\n                                        &bitrate_threshold_kbps)) {\n      MS_DEBUG_TAG(bwe,  \"Enabled BweLossExperiment with parameters %f, %f, %d\",\n                       low_loss_threshold_, high_loss_threshold_,\n                       bitrate_threshold_kbps);\n      bitrate_threshold_ = DataRate::kbps(bitrate_threshold_kbps);\n    }\n  }\n}\n\nSendSideBandwidthEstimation::~SendSideBandwidthEstimation() {}\n\nvoid SendSideBandwidthEstimation::OnRouteChange() {\n  lost_packets_since_last_loss_update_ = 0;\n  expected_packets_since_last_loss_update_ = 0;\n  current_bitrate_ = DataRate::Zero();\n  min_bitrate_configured_ =\n      DataRate::bps(congestion_controller::GetMinBitrateBps());\n  max_bitrate_configured_ = kDefaultMaxBitrate;\n  last_low_bitrate_log_ = Timestamp::MinusInfinity();\n  has_decreased_since_last_fraction_loss_ = false;\n  last_loss_feedback_ = Timestamp::MinusInfinity();\n  last_loss_packet_report_ = Timestamp::MinusInfinity();\n  last_timeout_ = Timestamp::MinusInfinity();\n  last_fraction_loss_ = 0;\n  last_logged_fraction_loss_ = 0;\n  last_round_trip_time_ = TimeDelta::Zero();\n  bwe_incoming_ = DataRate::Zero();\n  delay_based_bitrate_ = DataRate::Zero();\n  time_last_decrease_ = Timestamp::MinusInfinity();\n  first_report_time_ = Timestamp::MinusInfinity();\n  initially_lost_packets_ = 0;\n  bitrate_at_2_seconds_ = DataRate::Zero();\n  uma_update_state_ = kNoUpdate;\n  uma_rtt_state_ = kNoUpdate;\n  last_rtc_event_log_ = Timestamp::MinusInfinity();\n\n  rtt_backoff_.OnRouteChange();\n}\n\nvoid SendSideBandwidthEstimation::SetBitrates(\n    absl::optional<DataRate> send_bitrate,\n    DataRate min_bitrate,\n    DataRate max_bitrate,\n    Timestamp at_time) {\n  SetMinMaxBitrate(min_bitrate, max_bitrate);\n  if (send_bitrate) {\n    link_capacity_.OnStartingRate(*send_bitrate);\n    SetSendBitrate(*send_bitrate, at_time);\n  }\n}\n\nvoid SendSideBandwidthEstimation::SetSendBitrate(DataRate bitrate,\n                                                 Timestamp at_time) {\n  MS_DEBUG_DEV(\"bitrate: %lld\", bitrate.bps());\n\n  // RTC_DCHECK_GT(bitrate, DataRate::Zero());\n  // Reset to avoid being capped by the estimate.\n  delay_based_bitrate_ = DataRate::Zero();\n  if (loss_based_bandwidth_estimation_.Enabled()) {\n    loss_based_bandwidth_estimation_.MaybeReset(bitrate);\n  }\n  CapBitrateToThresholds(at_time, bitrate);\n  // Clear last sent bitrate history so the new value can be used directly\n  // and not capped.\n  min_bitrate_history_.clear();\n}\n\nvoid SendSideBandwidthEstimation::SetMinMaxBitrate(DataRate min_bitrate,\n                                                   DataRate max_bitrate) {\n  min_bitrate_configured_ =\n      std::max(min_bitrate, congestion_controller::GetMinBitrate());\n  if (max_bitrate > DataRate::Zero() && max_bitrate.IsFinite()) {\n    max_bitrate_configured_ = std::max(min_bitrate_configured_, max_bitrate);\n  } else {\n    max_bitrate_configured_ = kDefaultMaxBitrate;\n  }\n}\n\nint SendSideBandwidthEstimation::GetMinBitrate() const {\n  return min_bitrate_configured_.bps<int>();\n}\n\nvoid SendSideBandwidthEstimation::CurrentEstimate(int* bitrate,\n                                                  uint8_t* loss,\n                                                  int64_t* rtt) const {\n  *bitrate = std::max<int32_t>(current_bitrate_.bps<int>(), GetMinBitrate());\n  *loss = last_fraction_loss_;\n  *rtt = last_round_trip_time_.ms<int64_t>();\n\n  MS_DEBUG_DEV(\"bitrate:%d (current_bitrate_:%\" PRIi64 \", GetMinBitrate():%d), loss:%d, rtt:%\" PRIi64,\n      *bitrate,\n      current_bitrate_.bps(),\n      GetMinBitrate(),\n      *loss,\n      *rtt);\n}\n\nDataRate SendSideBandwidthEstimation::GetEstimatedLinkCapacity() const {\n  return link_capacity_.estimate();\n}\n\nvoid SendSideBandwidthEstimation::UpdateReceiverEstimate(Timestamp at_time,\n                                                         DataRate bandwidth) {\n  bwe_incoming_ = bandwidth;\n  CapBitrateToThresholds(at_time, current_bitrate_);\n}\n\nvoid SendSideBandwidthEstimation::UpdateDelayBasedEstimate(Timestamp at_time,\n                                                           DataRate bitrate) {\n  if (acknowledged_rate_) {\n    if (bitrate < delay_based_bitrate_) {\n      link_capacity_.OnOveruse(*acknowledged_rate_, at_time);\n    }\n  }\n  delay_based_bitrate_ = bitrate;\n  CapBitrateToThresholds(at_time, current_bitrate_);\n}\n\nvoid SendSideBandwidthEstimation::SetAcknowledgedRate(\n    absl::optional<DataRate> acknowledged_rate,\n    Timestamp at_time) {\n  acknowledged_rate_ = acknowledged_rate;\n  if (acknowledged_rate && loss_based_bandwidth_estimation_.Enabled()) {\n    loss_based_bandwidth_estimation_.UpdateAcknowledgedBitrate(\n        *acknowledged_rate, at_time);\n  }\n}\n\nvoid SendSideBandwidthEstimation::IncomingPacketFeedbackVector(\n    const TransportPacketsFeedback& report) {\n  if (loss_based_bandwidth_estimation_.Enabled()) {\n    loss_based_bandwidth_estimation_.UpdateLossStatistics(\n        report.packet_feedbacks, report.feedback_time);\n  }\n}\n\nvoid SendSideBandwidthEstimation::UpdateReceiverBlock(uint8_t fraction_loss,\n                                                      TimeDelta rtt,\n                                                      int number_of_packets,\n                                                      Timestamp at_time) {\n  const int kRoundingConstant = 128;\n  int packets_lost = (static_cast<int>(fraction_loss) * number_of_packets +\n                      kRoundingConstant) >>\n                     8;\n  UpdatePacketsLost(packets_lost, number_of_packets, at_time);\n  UpdateRtt(rtt, at_time);\n}\n\nvoid SendSideBandwidthEstimation::UpdatePacketsLost(int packets_lost,\n                                                    int number_of_packets,\n                                                    Timestamp at_time) {\n  last_loss_feedback_ = at_time;\n  if (first_report_time_.IsInfinite())\n    first_report_time_ = at_time;\n\n  // Check sequence number diff and weight loss report\n  if (number_of_packets > 0) {\n    int64_t expected =\n      expected_packets_since_last_loss_update_ + number_of_packets;\n\n    // Don't generate a loss rate until it can be based on enough packets.\n    if (expected < kLimitNumPackets) {\n      // Accumulate reports.\n      expected_packets_since_last_loss_update_ = expected;\n      lost_packets_since_last_loss_update_ += packets_lost;\n      return;\n    }\n\n    has_decreased_since_last_fraction_loss_ = false;\n    int64_t lost_q8 =\n      std::max<int64_t>(lost_packets_since_last_loss_update_ + packets_lost, 0) << 8;\n    last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);\n\n    // Reset accumulators.\n    lost_packets_since_last_loss_update_ = 0;\n    expected_packets_since_last_loss_update_ = 0;\n    last_loss_packet_report_ = at_time;\n    UpdateEstimate(at_time);\n  }\n\n  UpdateUmaStatsPacketsLost(at_time, packets_lost);\n}\n\nvoid SendSideBandwidthEstimation::UpdateUmaStatsPacketsLost(Timestamp at_time,\n                                                            int packets_lost) {\n  DataRate bitrate_kbps = DataRate::kbps((current_bitrate_.bps() + 500) / 1000);\n  for (size_t i = 0; i < kNumUmaRampupMetrics; ++i) {\n    if (!rampup_uma_stats_updated_[i] &&\n        bitrate_kbps.kbps() >= kUmaRampupMetrics[i].bitrate_kbps) {\n      // RTC_HISTOGRAMS_COUNTS_100000(i, kUmaRampupMetrics[i].metric_name,\n                                   // (at_time - first_report_time_).ms());\n      rampup_uma_stats_updated_[i] = true;\n    }\n  }\n  if (IsInStartPhase(at_time)) {\n    initially_lost_packets_ += packets_lost;\n  } else if (uma_update_state_ == kNoUpdate) {\n    uma_update_state_ = kFirstDone;\n    bitrate_at_2_seconds_ = bitrate_kbps;\n    // RTC_HISTOGRAM_COUNTS(\"WebRTC.BWE.InitiallyLostPackets\",\n                         // initially_lost_packets_, 0, 100, 50);\n    // RTC_HISTOGRAM_COUNTS(\"WebRTC.BWE.InitialBandwidthEstimate\",\n                         // bitrate_at_2_seconds_.kbps(), 0, 2000, 50);\n  } else if (uma_update_state_ == kFirstDone &&\n             at_time - first_report_time_ >= kBweConverganceTime) {\n    uma_update_state_ = kDone;\n    // int bitrate_diff_kbps = std::max(\n        // bitrate_at_2_seconds_.kbps<int>() - bitrate_kbps.kbps<int>(), 0);\n    // RTC_HISTOGRAM_COUNTS(\"WebRTC.BWE.InitialVsConvergedDiff\", bitrate_diff_kbps,\n                         // 0, 2000, 50);\n  }\n}\n\nvoid SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) {\n  // Update RTT if we were able to compute an RTT based on this RTCP.\n  // FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.\n  if (rtt > TimeDelta::Zero())\n    last_round_trip_time_ = rtt;\n\n  if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) {\n    uma_rtt_state_ = kDone;\n    // RTC_HISTOGRAM_COUNTS(\"WebRTC.BWE.InitialRtt\", rtt.ms<int>(), 0, 2000, 50);\n  }\n}\n\nvoid SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {\n  DataRate new_bitrate = current_bitrate_;\n  if (rtt_backoff_.CorrectedRtt(at_time) > rtt_backoff_.rtt_limit_) {\n    if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ &&\n        current_bitrate_ > rtt_backoff_.bandwidth_floor_) {\n      time_last_decrease_ = at_time;\n      new_bitrate = std::max(current_bitrate_ * rtt_backoff_.drop_fraction_,\n                             rtt_backoff_.bandwidth_floor_.Get());\n      link_capacity_.OnRttBackoff(new_bitrate, at_time);\n    }\n    CapBitrateToThresholds(at_time, new_bitrate);\n    return;\n  }\n\n  // We trust the REMB and/or delay-based estimate during the first 2 seconds if\n  // we haven't had any packet loss reported, to allow startup bitrate probing.\n  if (last_fraction_loss_ == 0 && IsInStartPhase(at_time)) {\n    new_bitrate = std::max(bwe_incoming_, new_bitrate);\n    new_bitrate = std::max(delay_based_bitrate_, new_bitrate);\n    if (loss_based_bandwidth_estimation_.Enabled()) {\n      loss_based_bandwidth_estimation_.SetInitialBitrate(new_bitrate);\n    }\n\n    if (new_bitrate != current_bitrate_) {\n      min_bitrate_history_.clear();\n      if (loss_based_bandwidth_estimation_.Enabled()) {\n        min_bitrate_history_.push_back(std::make_pair(at_time, new_bitrate));\n      } else {\n        min_bitrate_history_.push_back(\n            std::make_pair(at_time, current_bitrate_));\n      }\n      CapBitrateToThresholds(at_time, new_bitrate);\n      return;\n    }\n  }\n  UpdateMinHistory(at_time);\n  if (last_loss_packet_report_.IsInfinite()) {\n    // No feedback received.\n    CapBitrateToThresholds(at_time, current_bitrate_);\n    return;\n  }\n\n  if (loss_based_bandwidth_estimation_.Enabled()) {\n    loss_based_bandwidth_estimation_.Update(\n        at_time, min_bitrate_history_.front().second, last_round_trip_time_);\n    new_bitrate = MaybeRampupOrBackoff(new_bitrate, at_time);\n    CapBitrateToThresholds(at_time, new_bitrate);\n    return;\n  }\n\n  TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_;\n  TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_;\n  if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {\n    // We only care about loss above a given bitrate threshold.\n    float loss = last_fraction_loss_ / 256.0f;\n    // We only make decisions based on loss when the bitrate is above a\n    // threshold. This is a crude way of handling loss which is uncorrelated\n    // to congestion.\n    if (current_bitrate_ < bitrate_threshold_ || loss <= low_loss_threshold_) {\n      // Loss < 2%: Increase rate by 8% of the min bitrate in the last\n      // kBweIncreaseInterval.\n      // Note that by remembering the bitrate over the last second one can\n      // rampup up one second faster than if only allowed to start ramping\n      // at 8% per second rate now. E.g.:\n      //   If sending a constant 100kbps it can rampup immediately to 108kbps\n      //   whenever a receiver report is received with lower packet loss.\n      //   If instead one would do: current_bitrate_ *= 1.08^(delta time),\n      //   it would take over one second since the lower packet loss to achieve\n      //   108kbps.\n      new_bitrate =\n          DataRate::bps(min_bitrate_history_.front().second.bps() * 1.08 + 0.5);\n\n      // Add 1 kbps extra, just to make sure that we do not get stuck\n      // (gives a little extra increase at low rates, negligible at higher\n      // rates).\n      new_bitrate += DataRate::bps(1000);\n    } else if (current_bitrate_ > bitrate_threshold_) {\n      if (loss <= high_loss_threshold_) {\n        // Loss between 2% - 10%: Do nothing.\n      } else {\n        // Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval\n        // + rtt.\n        if (!has_decreased_since_last_fraction_loss_ &&\n            (at_time - time_last_decrease_) >=\n                (kBweDecreaseInterval + last_round_trip_time_)) {\n          time_last_decrease_ = at_time;\n\n          // Reduce rate:\n          //   newRate = rate * (1 - 0.5*lossRate);\n          //   where packetLoss = 256*lossRate;\n          new_bitrate =\n              DataRate::bps((current_bitrate_.bps() *\n                             static_cast<double>(512 - last_fraction_loss_)) /\n                            512.0);\n          has_decreased_since_last_fraction_loss_ = true;\n        }\n      }\n    }\n  } else if (time_since_loss_feedback >\n                 kFeedbackTimeoutIntervals * kMaxRtcpFeedbackInterval &&\n             (last_timeout_.IsInfinite() ||\n              at_time - last_timeout_ > kTimeoutInterval)) {\n    if (in_timeout_experiment_) {\n      MS_WARN_TAG(bwe, \"Feedback timed out (%s), reducint bitrate\",\n                          ToString(time_since_loss_feedback).c_str());\n      new_bitrate = new_bitrate * 0.8;\n      // Reset accumulators since we've already acted on missing feedback and\n      // shouldn't to act again on these old lost packets.\n      lost_packets_since_last_loss_update_ = 0;\n      expected_packets_since_last_loss_update_ = 0;\n      last_timeout_ = at_time;\n    }\n  }\n\n  CapBitrateToThresholds(at_time, new_bitrate);\n}\n\nvoid SendSideBandwidthEstimation::UpdatePropagationRtt(\n    Timestamp at_time,\n    TimeDelta propagation_rtt) {\n  rtt_backoff_.UpdatePropagationRtt(at_time, propagation_rtt);\n}\n\nvoid SendSideBandwidthEstimation::OnSentPacket(const SentPacket& sent_packet) {\n  // Only feedback-triggering packets will be reported here.\n  rtt_backoff_.last_packet_sent_ = sent_packet.send_time;\n}\n\nbool SendSideBandwidthEstimation::IsInStartPhase(Timestamp at_time) const {\n  return first_report_time_.IsInfinite() ||\n         at_time - first_report_time_ < kStartPhase;\n}\n\nvoid SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) {\n  // Remove old data points from history.\n  // Since history precision is in ms, add one so it is able to increase\n  // bitrate if it is off by as little as 0.5ms.\n  while (!min_bitrate_history_.empty() &&\n         at_time - min_bitrate_history_.front().first + TimeDelta::ms(1) >\n             kBweIncreaseInterval) {\n    min_bitrate_history_.pop_front();\n  }\n\n  // Typical minimum sliding-window algorithm: Pop values higher than current\n  // bitrate before pushing it.\n  while (!min_bitrate_history_.empty() &&\n         current_bitrate_ <= min_bitrate_history_.back().second) {\n    min_bitrate_history_.pop_back();\n  }\n\n  min_bitrate_history_.push_back(std::make_pair(at_time, current_bitrate_));\n}\n\nDataRate SendSideBandwidthEstimation::MaybeRampupOrBackoff(DataRate new_bitrate,\n                                                           Timestamp at_time) {\n  // TODO(crodbro): reuse this code in UpdateEstimate instead of current\n  // inlining of very similar functionality.\n  const TimeDelta time_since_loss_packet_report =\n      at_time - last_loss_packet_report_;\n  const TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_;\n  if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {\n    new_bitrate = min_bitrate_history_.front().second * 1.08;\n    new_bitrate += DataRate::bps(1000);\n  } else if (time_since_loss_feedback >\n                 kFeedbackTimeoutIntervals * kMaxRtcpFeedbackInterval &&\n             (last_timeout_.IsInfinite() ||\n              at_time - last_timeout_ > kTimeoutInterval)) {\n    if (in_timeout_experiment_) {\n      MS_WARN_TAG(bwe,\"Feedback timed out (%s), reducint bitrate\",\n                          ToString(time_since_loss_feedback).c_str());\n      new_bitrate = new_bitrate * 0.8;\n      // Reset accumulators since we've already acted on missing feedback and\n      // shouldn't to act again on these old lost packets.\n      lost_packets_since_last_loss_update_ = 0;\n      expected_packets_since_last_loss_update_ = 0;\n      last_timeout_ = at_time;\n    }\n  }\n  return new_bitrate;\n}\n\nvoid SendSideBandwidthEstimation::CapBitrateToThresholds(Timestamp at_time,\n                                                         DataRate bitrate) {\n  if (bwe_incoming_ > DataRate::Zero() && bitrate > bwe_incoming_) {\n    MS_DEBUG_DEV(\"bwe_incoming_:%lld\", bwe_incoming_.bps());\n    bitrate = bwe_incoming_;\n  }\n  if (delay_based_bitrate_ > DataRate::Zero() &&\n      bitrate > delay_based_bitrate_) {\n    MS_DEBUG_DEV(\"delay_based_bitrate_:%lld\", delay_based_bitrate_.bps());\n    bitrate = delay_based_bitrate_;\n  }\n  if (loss_based_bandwidth_estimation_.Enabled() &&\n      loss_based_bandwidth_estimation_.GetEstimate() > DataRate::Zero()) {\n    MS_DEBUG_DEV(\"loss_based_bandwidth_estimation_.GetEstimate():%lld\", loss_based_bandwidth_estimation_.GetEstimate().bps());\n    bitrate = std::min(bitrate, loss_based_bandwidth_estimation_.GetEstimate());\n  }\n  if (bitrate > max_bitrate_configured_) {\n    MS_DEBUG_DEV(\"bitrate > max_bitrate_configured_, setting bitrate to max_bitrate_configured_\");\n    bitrate = max_bitrate_configured_;\n  }\n  if (bitrate < min_bitrate_configured_) {\n    MS_DEBUG_DEV(\"bitrate < min_bitrate_configured_\");\n    if (last_low_bitrate_log_.IsInfinite() ||\n        at_time - last_low_bitrate_log_ > kLowBitrateLogPeriod) {\n      MS_WARN_TAG(bwe, \"Estimated available bandwidth %s\"\n                        \" is below configured min bitrate %s\",\n                        ToString(bitrate).c_str(),\n                        ToString(min_bitrate_configured_).c_str());\n      last_low_bitrate_log_ = at_time;\n    }\n    bitrate = min_bitrate_configured_;\n  }\n\n  if (bitrate != current_bitrate_ ||\n      last_fraction_loss_ != last_logged_fraction_loss_ ||\n      at_time - last_rtc_event_log_ > kRtcEventLogPeriod) {\n    last_logged_fraction_loss_ = last_fraction_loss_;\n    last_rtc_event_log_ = at_time;\n  }\n  MS_DEBUG_DEV(\"current_bitrate_:%lld\", current_bitrate_.bps());\n  current_bitrate_ = bitrate;\n\n  if (acknowledged_rate_) {\n    link_capacity_.OnRateUpdate(std::min(current_bitrate_, *acknowledged_rate_),\n                                at_time);\n  }\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n *\n *  FEC and NACK added bitrate is handled outside class\n */\n\n#ifndef MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_\n#define MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/time_delta.h\"\n#include \"api/units/timestamp.h\"\n#include \"modules/bitrate_controller/loss_based_bandwidth_estimation.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n#include <deque>\n#include <utility>\n#include <vector>\n\nnamespace webrtc {\n\nclass LinkCapacityTracker {\n public:\n  LinkCapacityTracker();\n  ~LinkCapacityTracker();\n  void OnOveruse(DataRate acknowledged_rate, Timestamp at_time);\n  void OnStartingRate(DataRate start_rate);\n  void OnRateUpdate(DataRate acknowledged, Timestamp at_time);\n  void OnRttBackoff(DataRate backoff_rate, Timestamp at_time);\n  DataRate estimate() const;\n\n private:\n  FieldTrialParameter<TimeDelta> tracking_rate;\n  double capacity_estimate_bps_ = 0;\n  Timestamp last_link_capacity_update_ = Timestamp::MinusInfinity();\n};\n\nclass RttBasedBackoff {\n public:\n  RttBasedBackoff();\n  ~RttBasedBackoff();\n  void OnRouteChange();\n  void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt);\n  TimeDelta CorrectedRtt(Timestamp at_time) const;\n\n  FieldTrialParameter<TimeDelta> rtt_limit_;\n  FieldTrialParameter<double> drop_fraction_;\n  FieldTrialParameter<TimeDelta> drop_interval_;\n  FieldTrialFlag persist_on_route_change_;\n  FieldTrialParameter<bool> safe_timeout_;\n  FieldTrialParameter<DataRate> bandwidth_floor_;\n\n public:\n  Timestamp last_propagation_rtt_update_;\n  TimeDelta last_propagation_rtt_;\n  Timestamp last_packet_sent_;\n};\n\nclass SendSideBandwidthEstimation {\n public:\n  SendSideBandwidthEstimation();\n  ~SendSideBandwidthEstimation();\n\n  void OnRouteChange();\n  void CurrentEstimate(int* bitrate, uint8_t* loss, int64_t* rtt) const;\n  DataRate GetEstimatedLinkCapacity() const;\n  // Call periodically to update estimate.\n  void UpdateEstimate(Timestamp at_time);\n  void OnSentPacket(const SentPacket& sent_packet);\n  void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt);\n\n  // Call when we receive a RTCP message with TMMBR or REMB.\n  void UpdateReceiverEstimate(Timestamp at_time, DataRate bandwidth);\n\n  // Call when a new delay-based estimate is available.\n  void UpdateDelayBasedEstimate(Timestamp at_time, DataRate bitrate);\n\n  // Call when we receive a RTCP message with a ReceiveBlock.\n  void UpdateReceiverBlock(uint8_t fraction_loss,\n                           TimeDelta rtt_ms,\n                           int number_of_packets,\n                           Timestamp at_time);\n\n  // Call when we receive a RTCP message with a ReceiveBlock.\n  void UpdatePacketsLost(int packets_lost,\n                         int number_of_packets,\n                         Timestamp at_time);\n\n  // Call when we receive a RTCP message with a ReceiveBlock.\n  void UpdateRtt(TimeDelta rtt, Timestamp at_time);\n\n  void SetBitrates(absl::optional<DataRate> send_bitrate,\n                   DataRate min_bitrate,\n                   DataRate max_bitrate,\n                   Timestamp at_time);\n  void SetSendBitrate(DataRate bitrate, Timestamp at_time);\n  void SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate);\n  int GetMinBitrate() const;\n  void SetAcknowledgedRate(absl::optional<DataRate> acknowledged_rate,\n                           Timestamp at_time);\n  void IncomingPacketFeedbackVector(const TransportPacketsFeedback& report);\n\n private:\n  friend class GoogCcStatePrinter;\n\n  enum UmaState { kNoUpdate, kFirstDone, kDone };\n\n  bool IsInStartPhase(Timestamp at_time) const;\n\n  void UpdateUmaStatsPacketsLost(Timestamp at_time, int packets_lost);\n\n  // Updates history of min bitrates.\n  // After this method returns min_bitrate_history_.front().second contains the\n  // min bitrate used during last kBweIncreaseIntervalMs.\n  void UpdateMinHistory(Timestamp at_time);\n\n  DataRate MaybeRampupOrBackoff(DataRate new_bitrate, Timestamp at_time);\n\n  // Cap |bitrate| to [min_bitrate_configured_, max_bitrate_configured_] and\n  // set |current_bitrate_| to the capped value and updates the event log.\n  void CapBitrateToThresholds(Timestamp at_time, DataRate bitrate);\n\n  RttBasedBackoff rtt_backoff_;\n  LinkCapacityTracker link_capacity_;\n\n  std::deque<std::pair<Timestamp, DataRate> > min_bitrate_history_;\n\n  // incoming filters\n  int lost_packets_since_last_loss_update_;\n  int expected_packets_since_last_loss_update_;\n\n  absl::optional<DataRate> acknowledged_rate_;\n  DataRate current_bitrate_;\n  DataRate min_bitrate_configured_;\n  DataRate max_bitrate_configured_;\n  Timestamp last_low_bitrate_log_;\n\n  bool has_decreased_since_last_fraction_loss_;\n  Timestamp last_loss_feedback_;\n  Timestamp last_loss_packet_report_;\n  Timestamp last_timeout_;\n  uint8_t last_fraction_loss_;\n  uint8_t last_logged_fraction_loss_;\n  TimeDelta last_round_trip_time_;\n\n  DataRate bwe_incoming_;\n  DataRate delay_based_bitrate_;\n  Timestamp time_last_decrease_;\n  Timestamp first_report_time_;\n  int initially_lost_packets_;\n  DataRate bitrate_at_2_seconds_;\n  UmaState uma_update_state_;\n  UmaState uma_rtt_state_;\n  std::vector<bool> rampup_uma_stats_updated_;\n  Timestamp last_rtc_event_log_;\n  bool in_timeout_experiment_;\n  float low_loss_threshold_;\n  float high_loss_threshold_;\n  DataRate bitrate_threshold_;\n  LossBasedBandwidthEstimation loss_based_bandwidth_estimation_;\n};\n}  // namespace webrtc\n#endif  // MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include <absl/memory/memory.h>\n#include <stddef.h>\n#include <algorithm>\n#include <utility>\n\nnamespace webrtc {\n\nAcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(\n    const WebRtcKeyValueConfig* key_value_config)\n    : AcknowledgedBitrateEstimator(\n          key_value_config,\n          absl::make_unique<BitrateEstimator>(key_value_config)) {}\n\nAcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {}\n\nAcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator(\n    const WebRtcKeyValueConfig* key_value_config,\n    std::unique_ptr<BitrateEstimator> bitrate_estimator)\n    : in_alr_(false), bitrate_estimator_(std::move(bitrate_estimator)) {}\n\nvoid AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector(\n    const std::vector<PacketResult>& packet_feedback_vector) {\n  // RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(),\n                            // packet_feedback_vector.end(),\n                            // PacketResult::ReceiveTimeOrder()));\n  for (const auto& packet : packet_feedback_vector) {\n    if (alr_ended_time_ && packet.sent_packet.send_time > *alr_ended_time_) {\n      bitrate_estimator_->ExpectFastRateChange();\n      alr_ended_time_.reset();\n    }\n    DataSize acknowledged_estimate = packet.sent_packet.size;\n    acknowledged_estimate += packet.sent_packet.prior_unacked_data;\n    bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate,\n                               in_alr_);\n  }\n}\n\nabsl::optional<DataRate> AcknowledgedBitrateEstimator::bitrate() const {\n  return bitrate_estimator_->bitrate();\n}\n\nabsl::optional<DataRate> AcknowledgedBitrateEstimator::PeekRate() const {\n  return bitrate_estimator_->PeekRate();\n}\n\nvoid AcknowledgedBitrateEstimator::SetAlrEndedTime(Timestamp alr_ended_time) {\n  alr_ended_time_.emplace(alr_ended_time);\n}\n\nvoid AcknowledgedBitrateEstimator::SetAlr(bool in_alr) {\n  in_alr_ = in_alr;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"api/units/data_rate.h\"\n#include \"modules/congestion_controller/goog_cc/bitrate_estimator.h\"\n\n#include <absl/types/optional.h>\n#include <memory>\n#include <vector>\n\nnamespace webrtc {\n\nclass AcknowledgedBitrateEstimator {\n public:\n  AcknowledgedBitrateEstimator(\n      const WebRtcKeyValueConfig* key_value_config,\n      std::unique_ptr<BitrateEstimator> bitrate_estimator);\n\n  explicit AcknowledgedBitrateEstimator(\n      const WebRtcKeyValueConfig* key_value_config);\n  ~AcknowledgedBitrateEstimator();\n\n  void IncomingPacketFeedbackVector(\n      const std::vector<PacketResult>& packet_feedback_vector);\n  absl::optional<DataRate> bitrate() const;\n  absl::optional<DataRate> PeekRate() const;\n  void SetAlr(bool in_alr);\n  void SetAlrEndedTime(Timestamp alr_ended_time);\n\n private:\n  absl::optional<Timestamp> alr_ended_time_;\n  bool in_alr_;\n  std::unique_ptr<BitrateEstimator> bitrate_estimator_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::AlrDetector\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/alr_detector.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <cstdint>\n#include <cstdio>\n\nnamespace webrtc {\n\nnamespace {\nabsl::optional<AlrExperimentSettings> GetExperimentSettings(\n    const WebRtcKeyValueConfig* key_value_config) {\n  // RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled(*key_value_config));\n  absl::optional<AlrExperimentSettings> experiment_settings =\n      AlrExperimentSettings::CreateFromFieldTrial(\n          *key_value_config,\n          AlrExperimentSettings::kScreenshareProbingBweExperimentName);\n  if (!experiment_settings) {\n    experiment_settings = AlrExperimentSettings::CreateFromFieldTrial(\n        *key_value_config,\n        AlrExperimentSettings::kStrictPacingAndProbingExperimentName);\n  }\n  return experiment_settings;\n}\n}  //  namespace\n\nAlrDetector::AlrDetector(const WebRtcKeyValueConfig* key_value_config)\n    : AlrDetector(key_value_config,\n                  GetExperimentSettings(key_value_config)) {}\n\nAlrDetector::AlrDetector(\n    const WebRtcKeyValueConfig* key_value_config,\n    absl::optional<AlrExperimentSettings> experiment_settings)\n    : bandwidth_usage_ratio_(\n          \"bw_usage\",\n          experiment_settings\n              ? experiment_settings->alr_bandwidth_usage_percent / 100.0\n              : kDefaultBandwidthUsageRatio),\n      start_budget_level_ratio_(\n          \"start\",\n          experiment_settings\n              ? experiment_settings->alr_start_budget_level_percent / 100.0\n              : kDefaultStartBudgetLevelRatio),\n      stop_budget_level_ratio_(\n          \"stop\",\n          experiment_settings\n              ? experiment_settings->alr_stop_budget_level_percent / 100.0\n              : kDefaultStopBudgetLevelRatio),\n      alr_timeout_(\n          \"alr_timeout\",\n          experiment_settings\n              ? experiment_settings->alr_timeout\n              : kDefaultAlrTimeout),\n      alr_budget_(0, true) {\n  ParseFieldTrial({&bandwidth_usage_ratio_, &start_budget_level_ratio_,\n                   &stop_budget_level_ratio_},\n                  key_value_config->Lookup(\"WebRTC-AlrDetectorParameters\"));\n}\n\nAlrDetector::~AlrDetector() {}\n\nvoid AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) {\n  if (!last_send_time_ms_.has_value()) {\n    last_send_time_ms_ = send_time_ms;\n    // Since the duration for sending the bytes is unknown, return without\n    // updating alr state.\n    return;\n  }\n  int64_t delta_time_ms = send_time_ms - *last_send_time_ms_;\n  last_send_time_ms_ = send_time_ms;\n\n  alr_budget_.UseBudget(bytes_sent);\n  alr_budget_.IncreaseBudget(delta_time_ms);\n  bool state_changed = false;\n  if (alr_budget_.budget_ratio() > start_budget_level_ratio_ &&\n      !alr_started_time_ms_) {\n    alr_started_time_ms_.emplace(DepLibUV::GetTimeMsInt64());\n    state_changed = true;\n  } else if (alr_budget_.budget_ratio() < stop_budget_level_ratio_ &&\n             alr_started_time_ms_) {\n    state_changed = true;\n    alr_started_time_ms_.reset();\n  }\n\n  if (state_changed)\n    MS_DEBUG_DEV(\"state changed\");\n}\n\nvoid AlrDetector::SetEstimatedBitrate(int bitrate_bps) {\n  //RTC_DCHECK(bitrate_bps);\n  int target_rate_kbps =\n      static_cast<double>(bitrate_bps) * bandwidth_usage_ratio_ / 1000;\n  alr_budget_.set_target_rate_kbps(target_rate_kbps);\n}\n\nabsl::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime()\n    const {\n  return alr_started_time_ms_;\n}\n\nabsl::optional<int64_t> AlrDetector::GetApplicationLimitedRegionStartTime(\n    int64_t at_time_ms) {\n  if (!alr_started_time_ms_ && last_send_time_ms_.has_value()) {\n    int64_t delta_time_ms = at_time_ms - *last_send_time_ms_;\n    // If ALR is stopped and we haven't sent any packets for a while, force start.\n    if (delta_time_ms > alr_timeout_) {\n      MS_WARN_TAG(bwe, \"large delta_time_ms: %\" PRIi64 \", forcing alr state change\",\n        delta_time_ms);\n      alr_started_time_ms_.emplace(at_time_ms);\n    }\n  }\n  return alr_started_time_ms_;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/alr_detector.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"modules/pacing/interval_budget.h\"\n#include \"rtc_base/experiments/alr_experiment.h\"\n#include \"rtc_base/experiments/field_trial_units.h\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n\n\nnamespace webrtc {\n\n// Application limited region detector is a class that utilizes signals of\n// elapsed time and bytes sent to estimate whether network traffic is\n// currently limited by the application's ability to generate traffic.\n//\n// AlrDetector provides a signal that can be utilized to adjust\n// estimate bandwidth.\n// Note: This class is not thread-safe.\nclass AlrDetector {\n public:\n  explicit AlrDetector(const WebRtcKeyValueConfig* key_value_config);\n  ~AlrDetector();\n\n  void OnBytesSent(size_t bytes_sent, int64_t send_time_ms);\n\n  // Set current estimated bandwidth.\n  void SetEstimatedBitrate(int bitrate_bps);\n\n  // Returns time in milliseconds when the current application-limited region\n  // started or empty result if the sender is currently not application-limited.\n  absl::optional<int64_t> GetApplicationLimitedRegionStartTime() const;\n  absl::optional<int64_t> GetApplicationLimitedRegionStartTime(int64_t at_time_ms);\n\n  void UpdateBudgetWithElapsedTime(int64_t delta_time_ms);\n  void UpdateBudgetWithBytesSent(size_t bytes_sent);\n\n private:\n  // Sent traffic ratio as a function of network capacity used to determine\n  // application-limited region. ALR region start when bandwidth usage drops\n  // below kAlrStartUsageRatio and ends when it raises above\n  // kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment\n  // until BW adjustments of application limited region is fine tuned.\n  static constexpr double kDefaultBandwidthUsageRatio = 0.65;\n  static constexpr double kDefaultStartBudgetLevelRatio = 0.80;\n  static constexpr double kDefaultStopBudgetLevelRatio = 0.50;\n  static constexpr int    kDefaultAlrTimeout = 3000;\n\n  AlrDetector(const WebRtcKeyValueConfig* key_value_config,\n              absl::optional<AlrExperimentSettings> experiment_settings);\n\n  friend class GoogCcStatePrinter;\n  FieldTrialParameter<double>  bandwidth_usage_ratio_;\n  FieldTrialParameter<double>  start_budget_level_ratio_;\n  FieldTrialParameter<double>  stop_budget_level_ratio_;\n  FieldTrialParameter<int>     alr_timeout_;\n\n  absl::optional<int64_t> last_send_time_ms_;\n\n  IntervalBudget alr_budget_;\n  absl::optional<int64_t> alr_started_time_ms_;\n};\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::BitrateEstimator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/bitrate_estimator.h\"\n#include \"api/units/data_rate.h\"\n\n#include \"Logger.hpp\"\n\n#include <stdio.h>\n#include <algorithm>\n#include <cmath>\n#include <string>\n\nnamespace webrtc {\n\nnamespace {\nconstexpr int kInitialRateWindowMs = 500;\nconstexpr int kRateWindowMs = 150;\nconstexpr int kMinRateWindowMs = 150;\nconstexpr int kMaxRateWindowMs = 1000;\n\nconst char kBweThroughputWindowConfig[] = \"WebRTC-BweThroughputWindowConfig\";\n\n}  // namespace\n\nBitrateEstimator::BitrateEstimator(const WebRtcKeyValueConfig* key_value_config)\n    : sum_(0),\n      initial_window_ms_(\"initial_window_ms\",\n                         kInitialRateWindowMs,\n                         kMinRateWindowMs,\n                         kMaxRateWindowMs),\n      noninitial_window_ms_(\"window_ms\",\n                            kRateWindowMs,\n                            kMinRateWindowMs,\n                            kMaxRateWindowMs),\n      uncertainty_scale_(\"scale\", 10.0),\n      uncertainty_scale_in_alr_(\"scale_alr\", 10.0),\n      uncertainty_symmetry_cap_(\"symmetry_cap\", DataRate::Zero()),\n      estimate_floor_(\"floor\", DataRate::Zero()),\n      current_window_ms_(0),\n      prev_time_ms_(-1),\n      bitrate_estimate_kbps_(-1.0f),\n      bitrate_estimate_var_(50.0f) {\n  // E.g WebRTC-BweThroughputWindowConfig/initial_window_ms:350,window_ms:250/\n  ParseFieldTrial({&initial_window_ms_, &noninitial_window_ms_,\n                   &uncertainty_scale_, &uncertainty_scale_in_alr_,\n                   &uncertainty_symmetry_cap_, &estimate_floor_},\n                  key_value_config->Lookup(kBweThroughputWindowConfig));\n}\n\nBitrateEstimator::~BitrateEstimator() = default;\n\nvoid BitrateEstimator::Update(Timestamp at_time, DataSize amount, bool in_alr) {\n  int rate_window_ms = noninitial_window_ms_;\n  // We use a larger window at the beginning to get a more stable sample that\n  // we can use to initialize the estimate.\n  if (bitrate_estimate_kbps_ < 0.f)\n    rate_window_ms = initial_window_ms_;\n  float bitrate_sample_kbps =\n      UpdateWindow(at_time.ms(), amount.bytes(), rate_window_ms);\n  if (bitrate_sample_kbps < 0.0f)\n    return;\n  if (bitrate_estimate_kbps_ < 0.0f) {\n    // This is the very first sample we get. Use it to initialize the estimate.\n    bitrate_estimate_kbps_ = bitrate_sample_kbps;\n    return;\n  }\n  // Define the sample uncertainty as a function of how far away it is from the\n  // current estimate. With low values of uncertainty_symmetry_cap_ we add more\n  // uncertainty to increases than to decreases. For higher values we approach\n  // symmetry.\n  float scale = uncertainty_scale_;\n  if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) {\n    // Optionally use higher uncertainty for samples obtained during ALR.\n    scale = uncertainty_scale_in_alr_;\n  }\n  float sample_uncertainty =\n      scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) /\n      (bitrate_estimate_kbps_ +\n       std::min(bitrate_sample_kbps,\n                uncertainty_symmetry_cap_.Get().kbps<float>()));\n\n  float sample_var = sample_uncertainty * sample_uncertainty;\n  // Update a bayesian estimate of the rate, weighting it lower if the sample\n  // uncertainty is large.\n  // The bitrate estimate uncertainty is increased with each update to model\n  // that the bitrate changes over time.\n  float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;\n  bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ +\n                            pred_bitrate_estimate_var * bitrate_sample_kbps) /\n                           (sample_var + pred_bitrate_estimate_var);\n  bitrate_estimate_kbps_ =\n      std::max(bitrate_estimate_kbps_, estimate_floor_.Get().kbps<float>());\n  bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /\n                          (sample_var + pred_bitrate_estimate_var);\n  MS_DEBUG_DEV(\n    \"acknowledged_bitrate %\" PRIu64\", %f\",\n    at_time.ms(), bitrate_estimate_kbps_ * 1000);\n}\n\nfloat BitrateEstimator::UpdateWindow(int64_t now_ms,\n                                     int bytes,\n                                     int rate_window_ms) {\n  // Reset if time moves backwards.\n  if (now_ms < prev_time_ms_) {\n    prev_time_ms_ = -1;\n    sum_ = 0;\n    current_window_ms_ = 0;\n  }\n  if (prev_time_ms_ >= 0) {\n    current_window_ms_ += now_ms - prev_time_ms_;\n    // Reset if nothing has been received for more than a full window.\n    if (now_ms - prev_time_ms_ > rate_window_ms) {\n      sum_ = 0;\n      current_window_ms_ %= rate_window_ms;\n    }\n  }\n  prev_time_ms_ = now_ms;\n  float bitrate_sample = -1.0f;\n  if (current_window_ms_ >= rate_window_ms) {\n    bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);\n    current_window_ms_ -= rate_window_ms;\n    sum_ = 0;\n  }\n  sum_ += bytes;\n  return bitrate_sample;\n}\n\nabsl::optional<DataRate> BitrateEstimator::bitrate() const {\n  if (bitrate_estimate_kbps_ < 0.f)\n    return absl::nullopt;\n  return DataRate::kbps(bitrate_estimate_kbps_);\n}\n\nabsl::optional<DataRate> BitrateEstimator::PeekRate() const {\n  if (current_window_ms_ > 0)\n    return DataSize::bytes(sum_) / TimeDelta::ms(current_window_ms_);\n  return absl::nullopt;\n}\n\nvoid BitrateEstimator::ExpectFastRateChange() {\n  // By setting the bitrate-estimate variance to a higher value we allow the\n  // bitrate to change fast for the next few samples.\n  bitrate_estimate_var_ += 200;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.h",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/timestamp.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n\nnamespace webrtc {\n\n// Computes a bayesian estimate of the throughput given acks containing\n// the arrival time and payload size. Samples which are far from the current\n// estimate or are based on few packets are given a smaller weight, as they\n// are considered to be more likely to have been caused by, e.g., delay spikes\n// unrelated to congestion.\nclass BitrateEstimator {\n public:\n  explicit BitrateEstimator(const WebRtcKeyValueConfig* key_value_config);\n  virtual ~BitrateEstimator();\n  virtual void Update(Timestamp at_time, DataSize amount, bool in_alr);\n\n  virtual absl::optional<DataRate> bitrate() const;\n  absl::optional<DataRate> PeekRate() const;\n\n  virtual void ExpectFastRateChange();\n\n private:\n  float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms);\n  int sum_;\n  FieldTrialConstrained<int> initial_window_ms_;\n  FieldTrialConstrained<int> noninitial_window_ms_;\n  FieldTrialParameter<double> uncertainty_scale_;\n  FieldTrialParameter<double> uncertainty_scale_in_alr_;\n  FieldTrialParameter<DataRate> uncertainty_symmetry_cap_;\n  FieldTrialParameter<DataRate> estimate_floor_;\n  int64_t current_window_ms_;\n  int64_t prev_time_ms_;\n  float bitrate_estimate_kbps_;\n  float bitrate_estimate_var_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h\"\n#include \"rtc_base/experiments/rate_control_settings.h\"\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <algorithm>\n#include <string>\n\nnamespace webrtc {\n\nCongestionWindowPushbackController::CongestionWindowPushbackController(\n    const WebRtcKeyValueConfig* key_value_config)\n    : add_pacing_(\n          key_value_config->Lookup(\"WebRTC-AddPacingToCongestionWindowPushback\")\n              .find(\"Enabled\") == 0),\n      min_pushback_target_bitrate_bps_(\n          RateControlSettings::ParseFromKeyValueConfig(key_value_config)\n              .CongestionWindowMinPushbackTargetBitrateBps()) {}\n\nCongestionWindowPushbackController::CongestionWindowPushbackController(\n    const WebRtcKeyValueConfig* key_value_config,\n    uint32_t min_pushback_target_bitrate_bps)\n    : add_pacing_(\n          key_value_config->Lookup(\"WebRTC-AddPacingToCongestionWindowPushback\")\n              .find(\"Enabled\") == 0),\n      min_pushback_target_bitrate_bps_(min_pushback_target_bitrate_bps) {}\n\nvoid CongestionWindowPushbackController::UpdateOutstandingData(\n    int64_t outstanding_bytes) {\n  outstanding_bytes_ = outstanding_bytes;\n}\nvoid CongestionWindowPushbackController::UpdatePacingQueue(\n    int64_t pacing_bytes) {\n  pacing_bytes_ = pacing_bytes;\n}\n\nvoid CongestionWindowPushbackController::UpdateMaxOutstandingData(\n    size_t max_outstanding_bytes) {\n  DataSize data_window = DataSize::bytes(max_outstanding_bytes);\n  if (current_data_window_) {\n    data_window = (data_window + current_data_window_.value()) / 2;\n  }\n  current_data_window_ = data_window;\n}\n\nvoid CongestionWindowPushbackController::SetDataWindow(DataSize data_window) {\n  current_data_window_ = data_window;\n}\n\nuint32_t CongestionWindowPushbackController::UpdateTargetBitrate(\n    uint32_t bitrate_bps) {\n  if (!current_data_window_ || current_data_window_->IsZero())\n    return bitrate_bps;\n  int64_t total_bytes = outstanding_bytes_;\n  if (add_pacing_)\n    total_bytes += pacing_bytes_;\n  double fill_ratio =\n      total_bytes / static_cast<double>(current_data_window_->bytes());\n  if (fill_ratio > 1.5) {\n    encoding_rate_ratio_ *= 0.9;\n  } else if (fill_ratio > 1) {\n    encoding_rate_ratio_ *= 0.95;\n  } else if (fill_ratio < 0.1) {\n    encoding_rate_ratio_ = 1.0;\n  } else {\n    encoding_rate_ratio_ *= 1.05;\n    encoding_rate_ratio_ = std::min(encoding_rate_ratio_, 1.0);\n  }\n  uint32_t adjusted_target_bitrate_bps =\n      static_cast<uint32_t>(bitrate_bps * encoding_rate_ratio_);\n\n  // Do not adjust below the minimum pushback bitrate but do obey if the\n  // original estimate is below it.\n  bitrate_bps = adjusted_target_bitrate_bps < min_pushback_target_bitrate_bps_\n                    ? std::min(bitrate_bps, min_pushback_target_bitrate_bps_)\n                    : adjusted_target_bitrate_bps;\n  return bitrate_bps;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"api/units/data_size.h\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace webrtc {\n\n// This class enables pushback from congestion window directly to video encoder.\n// When the congestion window is filling up, the video encoder target bitrate\n// will be reduced accordingly to accommodate the network changes. To avoid\n// pausing video too frequently, a minimum encoder target bitrate threshold is\n// used to prevent video pause due to a full congestion window.\nclass CongestionWindowPushbackController {\n public:\n  explicit CongestionWindowPushbackController(\n      const WebRtcKeyValueConfig* key_value_config);\n  CongestionWindowPushbackController(\n      const WebRtcKeyValueConfig* key_value_config,\n      uint32_t min_pushback_target_bitrate_bps);\n  void UpdateOutstandingData(int64_t outstanding_bytes);\n  void UpdatePacingQueue(int64_t pacing_bytes);\n  void UpdateMaxOutstandingData(size_t max_outstanding_bytes);\n  uint32_t UpdateTargetBitrate(uint32_t bitrate_bps);\n  void SetDataWindow(DataSize data_window);\n\n private:\n  absl::optional<DataSize> current_data_window_;\n  int64_t outstanding_bytes_ = 0;\n  int64_t pacing_bytes_ = 0;\n  const bool add_pacing_;\n  const uint32_t min_pushback_target_bitrate_bps_;\n  double encoding_rate_ratio_ = 1.0;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::DelayBasedBwe\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/delay_based_bwe.h\"\n#include \"modules/congestion_controller/goog_cc/trendline_estimator.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n#include <cstdint>\n#include <cstdio>\n#include <string>\n#include <utility>\n\nnamespace webrtc {\nnamespace {\nconstexpr TimeDelta kStreamTimeOut = TimeDelta::Seconds<2>();\nconstexpr int kTimestampGroupLengthMs = 5;\nconstexpr int kAbsSendTimeFraction = 18;\nconstexpr int kAbsSendTimeInterArrivalUpshift = 8;\nconstexpr int kInterArrivalShift =\n    kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift;\nconstexpr double kTimestampToMs =\n    1000.0 / static_cast<double>(1 << kInterArrivalShift);\n// This ssrc is used to fulfill the current API but will be removed\n// after the API has been changed.\nconstexpr uint32_t kFixedSsrc = 0;\n\n}  // namespace\n\nDelayBasedBwe::Result::Result()\n    : updated(false),\n      probe(false),\n      target_bitrate(DataRate::Zero()),\n      recovered_from_overuse(false),\n      backoff_in_alr(false) {}\n\nDelayBasedBwe::Result::Result(bool probe, DataRate target_bitrate)\n    : updated(true),\n      probe(probe),\n      target_bitrate(target_bitrate),\n      recovered_from_overuse(false),\n      backoff_in_alr(false) {}\n\nDelayBasedBwe::Result::~Result() {}\n\nDelayBasedBwe::DelayBasedBwe(const WebRtcKeyValueConfig* key_value_config,\n                             NetworkStatePredictor* network_state_predictor)\n    : key_value_config_(key_value_config),\n      network_state_predictor_(network_state_predictor),\n      inter_arrival_(),\n      delay_detector_(\n          new TrendlineEstimator(network_state_predictor_)),\n      last_seen_packet_(Timestamp::MinusInfinity()),\n      uma_recorded_(false),\n      rate_control_(key_value_config, /*send_side=*/true),\n      prev_bitrate_(DataRate::Zero()),\n      prev_state_(BandwidthUsage::kBwNormal),\n      alr_limited_backoff_enabled_(\n          key_value_config->Lookup(\"WebRTC-Bwe-AlrLimitedBackoff\")\n              .find(\"Enabled\") == 0) {}\n\nDelayBasedBwe::~DelayBasedBwe() {}\n\nDelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector(\n    const TransportPacketsFeedback& msg,\n    absl::optional<DataRate> acked_bitrate,\n    absl::optional<DataRate> probe_bitrate,\n    absl::optional<NetworkStateEstimate> network_estimate,\n    bool in_alr) {\n  //RTC_DCHECK_RUNS_SERIALIZED(&network_race_);\n\n  auto packet_feedback_vector = msg.SortedByReceiveTime();\n  // TODO(holmer): An empty feedback vector here likely means that\n  // all acks were too late and that the send time history had\n  // timed out. We should reduce the rate when this occurs.\n  if (packet_feedback_vector.empty()) {\n    MS_WARN_DEV(\"very late feedback received\");\n    return DelayBasedBwe::Result();\n  }\n\n  if (!uma_recorded_) {\n    uma_recorded_ = true;\n  }\n  bool delayed_feedback = true;\n  bool recovered_from_overuse = false;\n  BandwidthUsage prev_detector_state = delay_detector_->State();\n  for (const auto& packet_feedback : packet_feedback_vector) {\n    delayed_feedback = false;\n    IncomingPacketFeedback(packet_feedback, msg.feedback_time);\n    if (prev_detector_state == BandwidthUsage::kBwUnderusing &&\n        delay_detector_->State() == BandwidthUsage::kBwNormal) {\n      recovered_from_overuse = true;\n    }\n    prev_detector_state = delay_detector_->State();\n  }\n\n  if (delayed_feedback) {\n    // TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard\n    // against building very large network queues.\n    return Result();\n  }\n  rate_control_.SetInApplicationLimitedRegion(in_alr);\n  rate_control_.SetNetworkStateEstimate(network_estimate);\n  return MaybeUpdateEstimate(acked_bitrate, probe_bitrate,\n                             std::move(network_estimate),\n                             recovered_from_overuse, in_alr, msg.feedback_time);\n}\n\nvoid DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback,\n                                           Timestamp at_time) {\n  // Reset if the stream has timed out.\n  if (last_seen_packet_.IsInfinite() ||\n      at_time - last_seen_packet_ > kStreamTimeOut) {\n    inter_arrival_.reset(\n        new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,\n                         kTimestampToMs, true));\n    delay_detector_.reset(\n        new TrendlineEstimator(network_state_predictor_));\n  }\n  last_seen_packet_ = at_time;\n\n  uint32_t send_time_24bits =\n      static_cast<uint32_t>(\n          ((static_cast<uint64_t>(packet_feedback.sent_packet.send_time.ms())\n            << kAbsSendTimeFraction) +\n           500) /\n          1000) &\n      0x00FFFFFF;\n  // Shift up send time to use the full 32 bits that inter_arrival works with,\n  // so wrapping works properly.\n  uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift;\n\n  uint32_t ts_delta = 0;\n  int64_t t_delta = 0;\n  int size_delta = 0;\n  bool calculated_deltas = inter_arrival_->ComputeDeltas(\n      timestamp, packet_feedback.receive_time.ms(), at_time.ms(),\n      packet_feedback.sent_packet.size.bytes(), &ts_delta, &t_delta,\n      &size_delta);\n  double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift);\n  delay_detector_->Update(t_delta, ts_delta_ms,\n                          packet_feedback.sent_packet.send_time.ms(),\n                          packet_feedback.receive_time.ms(), calculated_deltas);\n}\n\nDataRate DelayBasedBwe::TriggerOveruse(Timestamp at_time,\n                                       absl::optional<DataRate> link_capacity) {\n  RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity);\n  return rate_control_.Update(&input, at_time);\n}\n\nDelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(\n    absl::optional<DataRate> acked_bitrate,\n    absl::optional<DataRate> probe_bitrate,\n    absl::optional<NetworkStateEstimate> state_estimate,\n    bool recovered_from_overuse,\n    bool in_alr,\n    Timestamp at_time) {\n  Result result;\n\n  // Currently overusing the bandwidth.\n  if (delay_detector_->State() == BandwidthUsage::kBwOverusing) {\n    MS_DEBUG_DEV(\"delay_detector_->State() == BandwidthUsage::kBwOverusing\");\n    MS_DEBUG_DEV(\"in_alr: %s\", in_alr ? \"true\" : \"false\");\n    if (in_alr && alr_limited_backoff_enabled_) {\n      if (rate_control_.TimeToReduceFurther(at_time, prev_bitrate_)) {\n        MS_DEBUG_DEV(\"alr_limited_backoff_enabled_ is true, prev_bitrate:%lld, result.target_bitrate:%lld\",\n            prev_bitrate_.bps(),\n            result.target_bitrate.bps());\n\n        result.updated =\n            UpdateEstimate(at_time, prev_bitrate_, &result.target_bitrate);\n        result.backoff_in_alr = true;\n      }\n    } else if (acked_bitrate &&\n               rate_control_.TimeToReduceFurther(at_time, *acked_bitrate)) {\n      MS_DEBUG_DEV(\"acked_bitrate:%lld, result.target_bitrate:%lld\",\n            acked_bitrate.value().bps(),\n            result.target_bitrate.bps());\n      result.updated =\n          UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);\n    } else if (!acked_bitrate && rate_control_.ValidEstimate() &&\n               rate_control_.InitialTimeToReduceFurther(at_time)) {\n      // Overusing before we have a measured acknowledged bitrate. Reduce send\n      // rate by 50% every 200 ms.\n      // TODO(tschumim): Improve this and/or the acknowledged bitrate estimator\n      // so that we (almost) always have a bitrate estimate.\n      MS_DEBUG_DEV(\"reducing send rate by 50%% every 200 ms\");\n      rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, at_time);\n      result.updated = true;\n      result.probe = false;\n      result.target_bitrate = rate_control_.LatestEstimate();\n    }\n  } else {\n    if (probe_bitrate) {\n      MS_DEBUG_DEV(\"probe bitrate: %lld\", probe_bitrate.value().bps());\n      result.probe = true;\n      result.updated = true;\n      result.target_bitrate = *probe_bitrate;\n      rate_control_.SetEstimate(*probe_bitrate, at_time);\n    } else {\n      result.updated =\n          UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);\n      result.recovered_from_overuse = recovered_from_overuse;\n    }\n  }\n  BandwidthUsage detector_state = delay_detector_->State();\n  if ((result.updated && prev_bitrate_ != result.target_bitrate) ||\n      detector_state != prev_state_) {\n    DataRate bitrate = result.updated ? result.target_bitrate : prev_bitrate_;\n\n    prev_bitrate_ = bitrate;\n\n    MS_DEBUG_DEV(\"setting prev_bitrate to: %lld, result.updated:%s\",\n        prev_bitrate_.bps(),\n        result.updated ? \"true\" : \"false\");\n\n    prev_state_ = detector_state;\n  }\n  return result;\n}\n\nbool DelayBasedBwe::UpdateEstimate(Timestamp at_time,\n                                   absl::optional<DataRate> acked_bitrate,\n                                   DataRate* target_rate) {\n  const RateControlInput input(delay_detector_->State(), acked_bitrate);\n  *target_rate = rate_control_.Update(&input, at_time);\n  return rate_control_.ValidEstimate();\n}\n\nvoid DelayBasedBwe::OnRttUpdate(TimeDelta avg_rtt) {\n  rate_control_.SetRtt(avg_rtt);\n}\n\nbool DelayBasedBwe::LatestEstimate(std::vector<uint32_t>* ssrcs,\n                                   DataRate* bitrate) const {\n  // Currently accessed from both the process thread (see\n  // ModuleRtpRtcpImpl::Process()) and the configuration thread (see\n  // Call::GetStats()). Should in the future only be accessed from a single\n  // thread.\n  //RTC_DCHECK(ssrcs);\n  //RTC_DCHECK(bitrate);\n  if (!ssrcs) {\n    MS_ERROR(\"ssrcs must be != null\");\n    return false;\n  }\n  if (!bitrate) {\n    MS_ERROR(\"bitrate must be != null\");\n    return false;\n  }\n\n  if (!rate_control_.ValidEstimate())\n    return false;\n\n  *ssrcs = {kFixedSsrc};\n  *bitrate = rate_control_.LatestEstimate();\n  return true;\n}\n\nvoid DelayBasedBwe::SetStartBitrate(DataRate start_bitrate) {\n  MS_DEBUG_DEV(\"BWE setting start bitrate to: %s\",\n               ToString(start_bitrate).c_str());\n  rate_control_.SetStartBitrate(start_bitrate);\n}\n\nvoid DelayBasedBwe::SetMinBitrate(DataRate min_bitrate) {\n  // Called from both the configuration thread and the network thread. Shouldn't\n  // be called from the network thread in the future.\n  rate_control_.SetMinBitrate(min_bitrate);\n}\n\nTimeDelta DelayBasedBwe::GetExpectedBwePeriod() const {\n  return rate_control_.GetExpectedBandwidthPeriod();\n}\n\nvoid DelayBasedBwe::SetAlrLimitedBackoffExperiment(bool enabled) {\n  alr_limited_backoff_enabled_ = enabled;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_\n\n#include \"api/network_state_predictor.h\"\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"modules/congestion_controller/goog_cc/delay_increase_detector_interface.h\"\n#include \"modules/congestion_controller/goog_cc/probe_bitrate_estimator.h\"\n#include \"modules/remote_bitrate_estimator/aimd_rate_control.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"modules/remote_bitrate_estimator/inter_arrival.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <memory>\n#include <vector>\n\n\nnamespace webrtc {\nclass RtcEventLog;\n\nclass DelayBasedBwe {\n public:\n  struct Result {\n    Result();\n    Result(bool probe, DataRate target_bitrate);\n    ~Result();\n    bool updated;\n    bool probe;\n    DataRate target_bitrate = DataRate::Zero();\n    bool recovered_from_overuse;\n    bool backoff_in_alr;\n  };\n\n  explicit DelayBasedBwe(const WebRtcKeyValueConfig* key_value_config,\n                         NetworkStatePredictor* network_state_predictor);\n  virtual ~DelayBasedBwe();\n\n  Result IncomingPacketFeedbackVector(\n      const TransportPacketsFeedback& msg,\n      absl::optional<DataRate> acked_bitrate,\n      absl::optional<DataRate> probe_bitrate,\n      absl::optional<NetworkStateEstimate> network_estimate,\n      bool in_alr);\n  void OnRttUpdate(TimeDelta avg_rtt);\n  bool LatestEstimate(std::vector<uint32_t>* ssrcs, DataRate* bitrate) const;\n  void SetStartBitrate(DataRate start_bitrate);\n  void SetMinBitrate(DataRate min_bitrate);\n  TimeDelta GetExpectedBwePeriod() const;\n  void SetAlrLimitedBackoffExperiment(bool enabled);\n\n  DataRate TriggerOveruse(Timestamp at_time,\n                          absl::optional<DataRate> link_capacity);\n\n private:\n  friend class GoogCcStatePrinter;\n  void IncomingPacketFeedback(const PacketResult& packet_feedback,\n                              Timestamp at_time);\n  Result MaybeUpdateEstimate(\n      absl::optional<DataRate> acked_bitrate,\n      absl::optional<DataRate> probe_bitrate,\n      absl::optional<NetworkStateEstimate> state_estimate,\n      bool recovered_from_overuse,\n      bool in_alr,\n      Timestamp at_time);\n  // Updates the current remote rate estimate and returns true if a valid\n  // estimate exists.\n  bool UpdateEstimate(Timestamp now,\n                      absl::optional<DataRate> acked_bitrate,\n                      DataRate* target_bitrate);\n\n  const WebRtcKeyValueConfig* const key_value_config_;\n  NetworkStatePredictor* network_state_predictor_;\n  std::unique_ptr<InterArrival> inter_arrival_;\n  std::unique_ptr<DelayIncreaseDetectorInterface> delay_detector_;\n  Timestamp last_seen_packet_;\n  bool uma_recorded_;\n  AimdRateControl rate_control_;\n  DataRate prev_bitrate_;\n  BandwidthUsage prev_state_;\n  bool alr_limited_backoff_enabled_;\n\n  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_\n\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include <stdint.h>\n\nnamespace webrtc {\n\nclass DelayIncreaseDetectorInterface {\n public:\n  DelayIncreaseDetectorInterface() {}\n  virtual ~DelayIncreaseDetectorInterface() {}\n\n  // Update the detector with a new sample. The deltas should represent deltas\n  // between timestamp groups as defined by the InterArrival class.\n  virtual void Update(double recv_delta_ms,\n                      double send_delta_ms,\n                      int64_t send_time_ms,\n                      int64_t arrival_time_ms,\n                      bool calculated_deltas) = 0;\n\n  virtual BandwidthUsage State() const = 0;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(DelayIncreaseDetectorInterface);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::GoogCcNetworkController\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/goog_cc_network_control.h\"\n#include \"api/units/time_delta.h\"\n#include \"modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h\"\n#include \"modules/congestion_controller/goog_cc/alr_detector.h\"\n#include \"modules/congestion_controller/goog_cc/probe_controller.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <inttypes.h>\n#include <stdio.h>\n#include <algorithm>\n#include <cstdint>\n#include <memory>\n#include <numeric>\n#include <string>\n#include <utility>\n#include <vector>\n\nnamespace webrtc {\nnamespace {\n// From RTCPSender video report interval.\nconstexpr TimeDelta kLossUpdateInterval = TimeDelta::Millis<1000>();\n\n// Pacing-rate relative to our target send rate.\n// Multiplicative factor that is applied to the target bitrate to calculate\n// the number of bytes that can be transmitted per interval.\n// Increasing this factor will result in lower delays in cases of bitrate\n// overshoots from the encoder.\nconst float kDefaultPaceMultiplier = 2.5f;\n\nint64_t GetBpsOrDefault(const absl::optional<DataRate>& rate,\n                        int64_t fallback_bps) {\n  if (rate && rate->IsFinite()) {\n    return rate->bps();\n  } else {\n    return fallback_bps;\n  }\n}\nbool IsEnabled(const WebRtcKeyValueConfig* config, absl::string_view key) {\n  return config->Lookup(key).find(\"Enabled\") == 0;\n}\nbool IsNotDisabled(const WebRtcKeyValueConfig* config, absl::string_view key) {\n  return config->Lookup(key).find(\"Disabled\") != 0;\n}\n}  // namespace\n\nGoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config,\n                                                 GoogCcConfig congestion_controller_config)\n    : key_value_config_(config.key_value_config ? config.key_value_config\n                                                : &trial_based_config_),\n      packet_feedback_only_(congestion_controller_config.feedback_only),\n      safe_reset_on_route_change_(\"Enabled\"),\n      safe_reset_acknowledged_rate_(\"ack\"),\n      use_stable_bandwidth_estimate_(\n          IsEnabled(key_value_config_, \"WebRTC-Bwe-StableBandwidthEstimate\")),\n      use_downlink_delay_for_congestion_window_(\n          IsEnabled(key_value_config_,\n                    \"WebRTC-Bwe-CongestionWindowDownlinkDelay\")),\n      fall_back_to_probe_rate_(\n          IsEnabled(key_value_config_, \"WebRTC-Bwe-ProbeRateFallback\")),\n      use_min_allocatable_as_lower_bound_(\n          IsNotDisabled(key_value_config_, \"WebRTC-Bwe-MinAllocAsLowerBound\")),\n      rate_control_settings_(\n          RateControlSettings::ParseFromKeyValueConfig(key_value_config_)),\n      probe_controller_(\n          new ProbeController(key_value_config_)),\n      congestion_window_pushback_controller_(\n          rate_control_settings_.UseCongestionWindowPushback()\n              ? absl::make_unique<CongestionWindowPushbackController>(\n                    key_value_config_)\n              : nullptr),\n      bandwidth_estimation_(\n          absl::make_unique<SendSideBandwidthEstimation>()),\n      alr_detector_(\n          absl::make_unique<AlrDetector>(key_value_config_)),\n      probe_bitrate_estimator_(new ProbeBitrateEstimator()),\n      network_estimator_(std::move(congestion_controller_config.network_state_estimator)),\n      network_state_predictor_(\n          std::move(congestion_controller_config.network_state_predictor)),\n      delay_based_bwe_(new DelayBasedBwe(key_value_config_,\n                                         network_state_predictor_.get())),\n      acknowledged_bitrate_estimator_(\n          absl::make_unique<AcknowledgedBitrateEstimator>(key_value_config_)),\n      initial_config_(config),\n      last_raw_target_rate_(*config.constraints.starting_rate),\n      last_pushback_target_rate_(last_raw_target_rate_),\n      pacing_factor_(config.stream_based_config.pacing_factor.value_or(\n          kDefaultPaceMultiplier)),\n      min_total_allocated_bitrate_(\n          config.stream_based_config.min_total_allocated_bitrate.value_or(\n              DataRate::Zero())),\n      max_padding_rate_(config.stream_based_config.max_padding_rate.value_or(\n          DataRate::Zero())),\n      max_total_allocated_bitrate_(DataRate::Zero()) {\n  //RTC_DCHECK(config.constraints.at_time.IsFinite());\n  ParseFieldTrial(\n      {&safe_reset_on_route_change_, &safe_reset_acknowledged_rate_},\n      key_value_config_->Lookup(\"WebRTC-Bwe-SafeResetOnRouteChange\"));\n  if (delay_based_bwe_)\n    delay_based_bwe_->SetMinBitrate(congestion_controller::GetMinBitrate());\n}\n\nGoogCcNetworkController::~GoogCcNetworkController() {}\n\nNetworkControlUpdate GoogCcNetworkController::OnNetworkAvailability(\n    NetworkAvailability msg) {\n  NetworkControlUpdate update;\n  update.probe_cluster_configs = probe_controller_->OnNetworkAvailability(msg);\n  return update;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnNetworkRouteChange(\n    NetworkRouteChange msg) {\n  if (safe_reset_on_route_change_) {\n    absl::optional<DataRate> estimated_bitrate;\n    if (safe_reset_acknowledged_rate_) {\n      estimated_bitrate = acknowledged_bitrate_estimator_->bitrate();\n      if (!estimated_bitrate)\n        estimated_bitrate = acknowledged_bitrate_estimator_->PeekRate();\n    } else {\n      int32_t target_bitrate_bps;\n      uint8_t fraction_loss;\n      int64_t rtt_ms;\n      bandwidth_estimation_->CurrentEstimate(&target_bitrate_bps,\n                                             &fraction_loss, &rtt_ms);\n      estimated_bitrate = DataRate::bps(target_bitrate_bps);\n    }\n    if (estimated_bitrate) {\n      if (msg.constraints.starting_rate) {\n        msg.constraints.starting_rate =\n            std::min(*msg.constraints.starting_rate, *estimated_bitrate);\n      } else {\n        msg.constraints.starting_rate = estimated_bitrate;\n      }\n    }\n  }\n\n  acknowledged_bitrate_estimator_.reset(\n      new AcknowledgedBitrateEstimator(key_value_config_));\n  probe_bitrate_estimator_.reset(new ProbeBitrateEstimator());\n  if (network_estimator_)\n    network_estimator_->OnRouteChange(msg);\n  delay_based_bwe_.reset(new DelayBasedBwe(key_value_config_,\n                                           network_state_predictor_.get()));\n  bandwidth_estimation_->OnRouteChange();\n  probe_controller_->Reset(msg.at_time.ms());\n  NetworkControlUpdate update;\n  update.probe_cluster_configs = ResetConstraints(msg.constraints);\n  MaybeTriggerOnNetworkChanged(&update, msg.at_time);\n  return update;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnProcessInterval(\n    ProcessInterval msg) {\n  NetworkControlUpdate update;\n  if (initial_config_) {\n    update.probe_cluster_configs =\n        ResetConstraints(initial_config_->constraints);\n    update.pacer_config = GetPacingRates(msg.at_time);\n\n    if (initial_config_->stream_based_config.requests_alr_probing) {\n      probe_controller_->EnablePeriodicAlrProbing(\n          *initial_config_->stream_based_config.requests_alr_probing);\n    }\n    absl::optional<DataRate> total_bitrate =\n        initial_config_->stream_based_config.max_total_allocated_bitrate;\n    if (total_bitrate) {\n      auto probes = probe_controller_->OnMaxTotalAllocatedBitrate(\n          total_bitrate->bps(), msg.at_time.ms());\n      update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),\n                                          probes.begin(), probes.end());\n\n      max_total_allocated_bitrate_ = *total_bitrate;\n    }\n    initial_config_.reset();\n  }\n  if (congestion_window_pushback_controller_ && msg.pacer_queue) {\n    congestion_window_pushback_controller_->UpdatePacingQueue(\n        msg.pacer_queue->bytes());\n  }\n  bandwidth_estimation_->UpdateEstimate(msg.at_time);\n  absl::optional<int64_t> start_time_ms =\n      alr_detector_->GetApplicationLimitedRegionStartTime(msg.at_time.ms());\n  probe_controller_->SetAlrStartTimeMs(start_time_ms);\n\n  auto probes = probe_controller_->Process(msg.at_time.ms());\n  update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),\n                                      probes.begin(), probes.end());\n\n  if (congestion_window_pushback_controller_ && current_data_window_) {\n    congestion_window_pushback_controller_->SetDataWindow(\n        *current_data_window_);\n  } else {\n    update.congestion_window = current_data_window_;\n  }\n  MaybeTriggerOnNetworkChanged(&update, msg.at_time);\n  return update;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnRemoteBitrateReport(\n    RemoteBitrateReport msg) {\n  if (packet_feedback_only_) {\n    MS_ERROR(\"Received REMB for packet feedback only GoogCC\");\n    return NetworkControlUpdate();\n  }\n  bandwidth_estimation_->UpdateReceiverEstimate(msg.receive_time,\n                                                msg.bandwidth);\n  // BWE_TEST_LOGGING_PLOT(1, \"REMB_kbps\", msg.receive_time.ms(),\n                        // msg.bandwidth.bps() / 1000);\n  return NetworkControlUpdate();\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnRoundTripTimeUpdate(\n    RoundTripTimeUpdate msg) {\n  if (packet_feedback_only_ || msg.smoothed)\n    return NetworkControlUpdate();\n  //RTC_DCHECK(!msg.round_trip_time.IsZero());\n  if (delay_based_bwe_)\n    delay_based_bwe_->OnRttUpdate(msg.round_trip_time);\n  bandwidth_estimation_->UpdateRtt(msg.round_trip_time, msg.receive_time);\n  return NetworkControlUpdate();\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnSentPacket(\n    SentPacket sent_packet) {\n  alr_detector_->OnBytesSent(sent_packet.size.bytes(),\n                             sent_packet.send_time.ms());\n  acknowledged_bitrate_estimator_->SetAlr(\n      alr_detector_->GetApplicationLimitedRegionStartTime().has_value());\n\n  if (!first_packet_sent_) {\n    first_packet_sent_ = true;\n    // Initialize feedback time to send time to allow estimation of RTT until\n    // first feedback is received.\n    bandwidth_estimation_->UpdatePropagationRtt(sent_packet.send_time,\n                                                TimeDelta::Zero());\n  }\n  bandwidth_estimation_->OnSentPacket(sent_packet);\n  bool network_changed = false;\n\n  if (congestion_window_pushback_controller_) {\n    congestion_window_pushback_controller_->UpdateOutstandingData(\n        sent_packet.data_in_flight.bytes());\n    network_changed = true;\n  }\n  if (network_changed) {\n    NetworkControlUpdate update;\n    MaybeTriggerOnNetworkChanged(&update, sent_packet.send_time);\n    return update;\n  } else {\n    return NetworkControlUpdate();\n  }\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnStreamsConfig(\n    StreamsConfig msg) {\n  NetworkControlUpdate update;\n  if (msg.requests_alr_probing) {\n    probe_controller_->EnablePeriodicAlrProbing(*msg.requests_alr_probing);\n  }\n  if (msg.max_total_allocated_bitrate &&\n      *msg.max_total_allocated_bitrate != max_total_allocated_bitrate_) {\n    if (rate_control_settings_.TriggerProbeOnMaxAllocatedBitrateChange()) {\n      update.probe_cluster_configs =\n          probe_controller_->OnMaxTotalAllocatedBitrate(\n              msg.max_total_allocated_bitrate->bps(), msg.at_time.ms());\n    } else {\n      probe_controller_->SetMaxBitrate(msg.max_total_allocated_bitrate->bps());\n    }\n    max_total_allocated_bitrate_ = *msg.max_total_allocated_bitrate;\n  }\n  bool pacing_changed = false;\n  if (msg.pacing_factor && *msg.pacing_factor != pacing_factor_) {\n    pacing_factor_ = *msg.pacing_factor;\n    pacing_changed = true;\n  }\n  if (msg.min_total_allocated_bitrate &&\n      *msg.min_total_allocated_bitrate != min_total_allocated_bitrate_) {\n    min_total_allocated_bitrate_ = *msg.min_total_allocated_bitrate;\n    pacing_changed = true;\n\n    if (use_min_allocatable_as_lower_bound_) {\n      ClampConstraints();\n      delay_based_bwe_->SetMinBitrate(min_data_rate_);\n      bandwidth_estimation_->SetMinMaxBitrate(min_data_rate_, max_data_rate_);\n    }\n  }\n  if (msg.max_padding_rate && *msg.max_padding_rate != max_padding_rate_) {\n    max_padding_rate_ = *msg.max_padding_rate;\n    pacing_changed = true;\n  }\n\n  if (pacing_changed)\n    update.pacer_config = GetPacingRates(msg.at_time);\n  return update;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnTargetRateConstraints(\n    TargetRateConstraints constraints) {\n  NetworkControlUpdate update;\n  update.probe_cluster_configs = ResetConstraints(constraints);\n  MaybeTriggerOnNetworkChanged(&update, constraints.at_time);\n  return update;\n}\n\nvoid GoogCcNetworkController::ClampConstraints() {\n  // TODO (ibc): Remove.\n  // MS_WARN_DEV(\n  //   \"[min_data_rate_:%\" PRIi64 \", min_total_allocated_bitrate_:%\" PRIi64 \", max_data_rate_:%\" PRIi64 \", starting_rate_:%\" PRIi64 \"]\",\n  //   min_data_rate_.bps(),\n  //   min_total_allocated_bitrate_.bps(),\n  //   max_data_rate_.bps(),\n  //   (*starting_rate_).bps());\n\n  // TODO(holmer): We should make sure the default bitrates are set to 10 kbps,\n  // and that we don't try to set the min bitrate to 0 from any applications.\n  // The congestion controller should allow a min bitrate of 0.\n  min_data_rate_ =\n      std::max(min_data_rate_, congestion_controller::GetMinBitrate());\n  if (use_min_allocatable_as_lower_bound_)\n    min_data_rate_ = std::max(min_data_rate_, min_total_allocated_bitrate_);\n  if (max_data_rate_ < min_data_rate_) {\n    MS_ERROR(\n      \"max bitrate smaller than min bitrate [max_data_rate_:%\" PRIi64 \", min_data_rate_:%\" PRIi64 \"]\",\n      max_data_rate_.bps(), min_data_rate_.bps());\n    max_data_rate_ = min_data_rate_;\n  }\n  if (starting_rate_ && starting_rate_ < min_data_rate_) {\n    MS_ERROR(\n      \"start bitrate smaller than min bitrate [starting_rate_:%\" PRIi64 \", min_data_rate_:%\" PRIi64 \"]\",\n      starting_rate_->bps(), min_data_rate_.bps());\n    starting_rate_ = min_data_rate_;\n  }\n}\n\nstd::vector<ProbeClusterConfig> GoogCcNetworkController::ResetConstraints(\n    TargetRateConstraints new_constraints) {\n  min_data_rate_ = new_constraints.min_data_rate.value_or(DataRate::Zero());\n  max_data_rate_ =\n      new_constraints.max_data_rate.value_or(DataRate::PlusInfinity());\n  starting_rate_ = new_constraints.starting_rate;\n  ClampConstraints();\n\n  MS_DEBUG_DEV(\n    \"calling bandwidth_estimation_->SetBitrates() [max_data_rate_.bps_or(-1):%\" PRIi64 \"]\",\n    max_data_rate_.bps_or(-1));\n\n  bandwidth_estimation_->SetBitrates(starting_rate_, min_data_rate_,\n                                     max_data_rate_, new_constraints.at_time);\n\n  if (starting_rate_)\n    delay_based_bwe_->SetStartBitrate(*starting_rate_);\n  delay_based_bwe_->SetMinBitrate(min_data_rate_);\n\n  return probe_controller_->SetBitrates(\n      min_data_rate_.bps(), GetBpsOrDefault(starting_rate_, -1),\n      max_data_rate_.bps_or(-1), new_constraints.at_time.ms());\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnTransportLossReport(\n    TransportLossReport msg) {\n  if (packet_feedback_only_)\n    return NetworkControlUpdate();\n  int64_t total_packets_delta =\n      msg.packets_received_delta + msg.packets_lost_delta;\n  bandwidth_estimation_->UpdatePacketsLost(\n      msg.packets_lost_delta, total_packets_delta, msg.receive_time);\n  return NetworkControlUpdate();\n}\n\nvoid GoogCcNetworkController::UpdateCongestionWindowSize(\n    TimeDelta time_since_last_packet) {\n  TimeDelta min_feedback_max_rtt = TimeDelta::ms(\n      *std::min_element(feedback_max_rtts_.begin(), feedback_max_rtts_.end()));\n\n  const DataSize kMinCwnd = DataSize::bytes(2 * 1500);\n  TimeDelta time_window =\n      min_feedback_max_rtt +\n      TimeDelta::ms(\n          rate_control_settings_.GetCongestionWindowAdditionalTimeMs());\n\n  if (use_downlink_delay_for_congestion_window_) {\n    time_window += time_since_last_packet;\n  }\n\n  DataSize data_window = last_raw_target_rate_ * time_window;\n  if (current_data_window_) {\n    data_window =\n        std::max(kMinCwnd, (data_window + current_data_window_.value()) / 2);\n  } else {\n    data_window = std::max(kMinCwnd, data_window);\n  }\n  current_data_window_ = data_window;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback(\n    TransportPacketsFeedback report) {\n  if (report.packet_feedbacks.empty()) {\n    // TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard\n    // against building very large network queues.\n    return NetworkControlUpdate();\n  }\n\n  if (congestion_window_pushback_controller_) {\n    congestion_window_pushback_controller_->UpdateOutstandingData(\n        report.data_in_flight.bytes());\n  }\n  TimeDelta max_feedback_rtt = TimeDelta::MinusInfinity();\n  TimeDelta min_propagation_rtt = TimeDelta::PlusInfinity();\n  Timestamp max_recv_time = Timestamp::MinusInfinity();\n\n  std::vector<PacketResult> feedbacks = report.ReceivedWithSendInfo();\n  for (const auto& feedback : feedbacks)\n    max_recv_time = std::max(max_recv_time, feedback.receive_time);\n\n  for (const auto& feedback : feedbacks) {\n    TimeDelta feedback_rtt =\n        report.feedback_time - feedback.sent_packet.send_time;\n    TimeDelta min_pending_time = max_recv_time - feedback.receive_time;\n    TimeDelta propagation_rtt = feedback_rtt - min_pending_time;\n    max_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt);\n    min_propagation_rtt = std::min(min_propagation_rtt, propagation_rtt);\n  }\n\n  if (max_feedback_rtt.IsFinite()) {\n    feedback_max_rtts_.push_back(max_feedback_rtt.ms());\n    const size_t kMaxFeedbackRttWindow = 32;\n    if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow)\n      feedback_max_rtts_.pop_front();\n    // TODO(srte): Use time since last unacknowledged packet.\n    bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time,\n                                                min_propagation_rtt);\n  }\n  if (packet_feedback_only_) {\n    if (!feedback_max_rtts_.empty()) {\n      int64_t sum_rtt_ms = std::accumulate(feedback_max_rtts_.begin(),\n                                           feedback_max_rtts_.end(), 0);\n      int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size();\n      if (delay_based_bwe_)\n        delay_based_bwe_->OnRttUpdate(TimeDelta::ms(mean_rtt_ms));\n    }\n\n    TimeDelta feedback_min_rtt = TimeDelta::PlusInfinity();\n    for (const auto& packet_feedback : feedbacks) {\n      TimeDelta pending_time = packet_feedback.receive_time - max_recv_time;\n      TimeDelta rtt = report.feedback_time -\n                      packet_feedback.sent_packet.send_time - pending_time;\n      // Value used for predicting NACK round trip time in FEC controller.\n      feedback_min_rtt = std::min(rtt, feedback_min_rtt);\n    }\n    if (feedback_min_rtt.IsFinite()) {\n      bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time);\n    }\n\n    expected_packets_since_last_loss_update_ +=\n        report.PacketsWithFeedback().size();\n    for (const auto& packet_feedback : report.PacketsWithFeedback()) {\n      if (packet_feedback.receive_time.IsInfinite())\n        lost_packets_since_last_loss_update_ += 1;\n    }\n    if (report.feedback_time > next_loss_update_) {\n      next_loss_update_ = report.feedback_time + kLossUpdateInterval;\n      bandwidth_estimation_->UpdatePacketsLost(\n          lost_packets_since_last_loss_update_,\n          expected_packets_since_last_loss_update_, report.feedback_time);\n      expected_packets_since_last_loss_update_ = 0;\n      lost_packets_since_last_loss_update_ = 0;\n    }\n  }\n  absl::optional<int64_t> alr_start_time =\n      alr_detector_->GetApplicationLimitedRegionStartTime();\n\n  if (previously_in_alr_ && !alr_start_time.has_value()) {\n    int64_t now_ms = report.feedback_time.ms();\n    acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time);\n    probe_controller_->SetAlrEndedTimeMs(now_ms);\n  }\n  previously_in_alr_ = alr_start_time.has_value();\n  acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector(\n      report.SortedByReceiveTime());\n  auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate();\n  for (const auto& feedback : report.SortedByReceiveTime()) {\n    if (feedback.sent_packet.pacing_info.probe_cluster_id !=\n        PacedPacketInfo::kNotAProbe) {\n      probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback);\n    }\n  }\n\n  absl::optional<DataRate> probe_bitrate =\n      probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate();\n  if (fall_back_to_probe_rate_ && !acknowledged_bitrate)\n    acknowledged_bitrate = probe_bitrate_estimator_->last_estimate();\n  bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate,\n                                             report.feedback_time);\n  bandwidth_estimation_->IncomingPacketFeedbackVector(report);\n\n  if (network_estimator_) {\n    network_estimator_->OnTransportPacketsFeedback(report);\n    estimate_ = network_estimator_->GetCurrentEstimate();\n  }\n\n  NetworkControlUpdate update;\n  bool recovered_from_overuse = false;\n  bool backoff_in_alr = false;\n\n  DelayBasedBwe::Result result;\n  result = delay_based_bwe_->IncomingPacketFeedbackVector(\n      report, acknowledged_bitrate, probe_bitrate, estimate_,\n      alr_start_time.has_value());\n\n  if (result.updated) {\n    if (result.probe) {\n      bandwidth_estimation_->SetSendBitrate(result.target_bitrate,\n                                            report.feedback_time);\n    }\n    // Since SetSendBitrate now resets the delay-based estimate, we have to\n    // call UpdateDelayBasedEstimate after SetSendBitrate.\n    bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time,\n                                                    result.target_bitrate);\n    // Update the estimate in the ProbeController, in case we want to probe.\n    MaybeTriggerOnNetworkChanged(&update, report.feedback_time);\n  }\n  recovered_from_overuse = result.recovered_from_overuse;\n  backoff_in_alr = result.backoff_in_alr;\n\n  if (recovered_from_overuse) {\n    probe_controller_->SetAlrStartTimeMs(alr_start_time);\n    auto probes = probe_controller_->RequestProbe(report.feedback_time.ms());\n    update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),\n                                        probes.begin(), probes.end());\n  } else if (backoff_in_alr) {\n    // If we just backed off during ALR, request a new probe.\n    auto probes = probe_controller_->RequestProbe(report.feedback_time.ms());\n    update.probe_cluster_configs.insert(update.probe_cluster_configs.end(),\n                                        probes.begin(), probes.end());\n  }\n\n  // No valid RTT could be because send-side BWE isn't used, in which case\n  // we don't try to limit the outstanding packets.\n  if (rate_control_settings_.UseCongestionWindow() &&\n      max_feedback_rtt.IsFinite()) {\n    UpdateCongestionWindowSize(/*time_since_last_packet*/ TimeDelta::Zero());\n  }\n  if (congestion_window_pushback_controller_ && current_data_window_) {\n    congestion_window_pushback_controller_->SetDataWindow(\n        *current_data_window_);\n  } else {\n    update.congestion_window = current_data_window_;\n  }\n\n  return update;\n}\n\nNetworkControlUpdate GoogCcNetworkController::OnNetworkStateEstimate(\n    NetworkStateEstimate msg) {\n  estimate_ = msg;\n  return NetworkControlUpdate();\n}\n\nNetworkControlUpdate GoogCcNetworkController::GetNetworkState(\n    Timestamp at_time) const {\n  DataRate bandwidth = use_stable_bandwidth_estimate_\n                           ? bandwidth_estimation_->GetEstimatedLinkCapacity()\n                           : last_raw_target_rate_;\n  TimeDelta rtt = TimeDelta::ms(last_estimated_rtt_ms_);\n  NetworkControlUpdate update;\n  update.target_rate = TargetTransferRate();\n  update.target_rate->network_estimate.at_time = at_time;\n  update.target_rate->network_estimate.bandwidth = bandwidth;\n  update.target_rate->network_estimate.loss_rate_ratio =\n      last_estimated_fraction_loss_ / 255.0;\n  update.target_rate->network_estimate.round_trip_time = rtt;\n  update.target_rate->network_estimate.bwe_period =\n      delay_based_bwe_->GetExpectedBwePeriod();\n\n  update.target_rate->at_time = at_time;\n  update.target_rate->target_rate = bandwidth;\n  update.pacer_config = GetPacingRates(at_time);\n  update.congestion_window = current_data_window_;\n  return update;\n}\n\nvoid GoogCcNetworkController::MaybeTriggerOnNetworkChanged(\n    NetworkControlUpdate* update,\n    Timestamp at_time) {\n  int32_t estimated_bitrate_bps;\n  uint8_t fraction_loss;\n  int64_t rtt_ms;\n  bandwidth_estimation_->CurrentEstimate(&estimated_bitrate_bps, &fraction_loss,\n                                         &rtt_ms);\n\n  // BWE_TEST_LOGGING_PLOT(1, \"fraction_loss_%\", at_time.ms(),\n                        // (fraction_loss * 100) / 256);\n  // BWE_TEST_LOGGING_PLOT(1, \"rtt_ms\", at_time.ms(), rtt_ms);\n  // BWE_TEST_LOGGING_PLOT(1, \"Target_bitrate_kbps\", at_time.ms(),\n                        // estimated_bitrate_bps / 1000);\n\n  DataRate target_rate = DataRate::bps(estimated_bitrate_bps);\n  if (congestion_window_pushback_controller_) {\n    int64_t pushback_rate =\n        congestion_window_pushback_controller_->UpdateTargetBitrate(\n            target_rate.bps());\n    pushback_rate = std::max<int64_t>(bandwidth_estimation_->GetMinBitrate(),\n                                      pushback_rate);\n    target_rate = DataRate::bps(pushback_rate);\n  }\n\n  if ((estimated_bitrate_bps != last_estimated_bitrate_bps_) ||\n      (fraction_loss != last_estimated_fraction_loss_) ||\n      (rtt_ms != last_estimated_rtt_ms_) ||\n      (target_rate != last_pushback_target_rate_)) {\n    last_pushback_target_rate_ = target_rate;\n    last_estimated_bitrate_bps_ = estimated_bitrate_bps;\n    last_estimated_fraction_loss_ = fraction_loss;\n    last_estimated_rtt_ms_ = rtt_ms;\n\n    alr_detector_->SetEstimatedBitrate(estimated_bitrate_bps);\n\n    last_raw_target_rate_ = DataRate::bps(estimated_bitrate_bps);\n    DataRate bandwidth = use_stable_bandwidth_estimate_\n                             ? bandwidth_estimation_->GetEstimatedLinkCapacity()\n                             : last_raw_target_rate_;\n\n    TimeDelta bwe_period = delay_based_bwe_->GetExpectedBwePeriod();\n\n    TargetTransferRate target_rate_msg;\n    target_rate_msg.at_time = at_time;\n    target_rate_msg.target_rate = target_rate;\n    target_rate_msg.network_estimate.at_time = at_time;\n    target_rate_msg.network_estimate.round_trip_time = TimeDelta::ms(rtt_ms);\n    target_rate_msg.network_estimate.bandwidth = bandwidth;\n    target_rate_msg.network_estimate.loss_rate_ratio = fraction_loss / 255.0f;\n    target_rate_msg.network_estimate.bwe_period = bwe_period;\n\n    update->target_rate = target_rate_msg;\n\n    auto probes = probe_controller_->SetEstimatedBitrate(\n        last_raw_target_rate_.bps(), at_time.ms());\n    update->probe_cluster_configs.insert(update->probe_cluster_configs.end(),\n                                         probes.begin(), probes.end());\n    update->pacer_config = GetPacingRates(at_time);\n\n    MS_DEBUG_DEV(\"bwe [at_time:%\" PRIu64\", pushback_target_bps:%lld, estimate_bps:%lld]\",\n                 at_time.ms(),\n                 last_pushback_target_rate_.bps(),\n                 last_raw_target_rate_.bps());\n  }\n}\n\nPacerConfig GoogCcNetworkController::GetPacingRates(Timestamp at_time) const {\n  // Pacing rate is based on target rate before congestion window pushback,\n  // because we don't want to build queues in the pacer when pushback occurs.\n  DataRate pacing_rate =\n      std::max(min_total_allocated_bitrate_, last_raw_target_rate_) *\n      pacing_factor_;\n  DataRate padding_rate =\n      std::min(max_padding_rate_, last_pushback_target_rate_);\n  PacerConfig msg;\n  msg.at_time = at_time;\n  msg.time_window = TimeDelta::seconds(1);\n  msg.data_window = pacing_rate * msg.time_window;\n  msg.pad_window = padding_rate * msg.time_window;\n  return msg;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_\n\n#include \"api/network_state_predictor.h\"\n#include \"api/transport/field_trial_based_config.h\"\n#include \"api/transport/network_control.h\"\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/data_size.h\"\n#include \"api/units/timestamp.h\"\n#include \"modules/bitrate_controller/send_side_bandwidth_estimation.h\"\n#include \"modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h\"\n#include \"modules/congestion_controller/goog_cc/alr_detector.h\"\n#include \"modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h\"\n#include \"modules/congestion_controller/goog_cc/delay_based_bwe.h\"\n#include \"modules/congestion_controller/goog_cc/probe_controller.h\"\n#include \"rtc_base/constructor_magic.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n#include \"rtc_base/experiments/rate_control_settings.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n#include <deque>\n#include <memory>\n#include <vector>\n\nnamespace webrtc {\nstruct GoogCcConfig {\n  std::unique_ptr<NetworkStateEstimator> network_state_estimator = nullptr;\n  std::unique_ptr<NetworkStatePredictor> network_state_predictor = nullptr;\n  bool feedback_only = false;\n};\n\nclass GoogCcNetworkController : public NetworkControllerInterface {\n public:\n  GoogCcNetworkController(NetworkControllerConfig config,\n                          GoogCcConfig congestion_controller_config);\n  ~GoogCcNetworkController() override;\n\n  // NetworkControllerInterface\n  NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override;\n  NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override;\n  NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override;\n  NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override;\n  NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override;\n  NetworkControlUpdate OnSentPacket(SentPacket msg) override;\n  NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override;\n  NetworkControlUpdate OnTargetRateConstraints(\n      TargetRateConstraints msg) override;\n  NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override;\n  NetworkControlUpdate OnTransportPacketsFeedback(\n      TransportPacketsFeedback msg) override;\n  NetworkControlUpdate OnNetworkStateEstimate(\n      NetworkStateEstimate msg) override;\n\n  NetworkControlUpdate GetNetworkState(Timestamp at_time) const;\n\n private:\n  friend class GoogCcStatePrinter;\n  std::vector<ProbeClusterConfig> ResetConstraints(\n      TargetRateConstraints new_constraints);\n  void ClampConstraints();\n  void MaybeTriggerOnNetworkChanged(NetworkControlUpdate* update,\n                                    Timestamp at_time);\n  void UpdateCongestionWindowSize(TimeDelta time_since_last_packet);\n  PacerConfig GetPacingRates(Timestamp at_time) const;\n  const FieldTrialBasedConfig trial_based_config_;\n\n  const WebRtcKeyValueConfig* const key_value_config_;\n  // RtcEventLog* const event_log_;\n  const bool packet_feedback_only_;\n  FieldTrialFlag safe_reset_on_route_change_;\n  FieldTrialFlag safe_reset_acknowledged_rate_;\n  const bool use_stable_bandwidth_estimate_;\n  const bool use_downlink_delay_for_congestion_window_;\n  const bool fall_back_to_probe_rate_;\n  const bool use_min_allocatable_as_lower_bound_;\n  const RateControlSettings rate_control_settings_;\n\n  const std::unique_ptr<ProbeController> probe_controller_;\n  const std::unique_ptr<CongestionWindowPushbackController>\n      congestion_window_pushback_controller_;\n\n  std::unique_ptr<SendSideBandwidthEstimation> bandwidth_estimation_;\n  std::unique_ptr<AlrDetector> alr_detector_;\n  std::unique_ptr<ProbeBitrateEstimator> probe_bitrate_estimator_;\n  std::unique_ptr<NetworkStateEstimator> network_estimator_;\n  std::unique_ptr<NetworkStatePredictor> network_state_predictor_;\n  std::unique_ptr<DelayBasedBwe> delay_based_bwe_;\n  std::unique_ptr<AcknowledgedBitrateEstimator> acknowledged_bitrate_estimator_;\n\n  absl::optional<NetworkControllerConfig> initial_config_;\n\n  DataRate min_data_rate_ = DataRate::Zero();\n  DataRate max_data_rate_ = DataRate::PlusInfinity();\n  absl::optional<DataRate> starting_rate_;\n\n  bool first_packet_sent_ = false;\n\n  absl::optional<NetworkStateEstimate> estimate_;\n\n  Timestamp next_loss_update_ = Timestamp::MinusInfinity();\n  int lost_packets_since_last_loss_update_ = 0;\n  int expected_packets_since_last_loss_update_ = 0;\n\n  std::deque<int64_t> feedback_max_rtts_;\n\n  DataRate last_raw_target_rate_;\n  DataRate last_pushback_target_rate_;\n\n  int32_t last_estimated_bitrate_bps_ = 0;\n  uint8_t last_estimated_fraction_loss_ = 0;\n  int64_t last_estimated_rtt_ms_ = 0;\n\n  double pacing_factor_;\n  DataRate min_total_allocated_bitrate_;\n  DataRate max_padding_rate_;\n  DataRate max_total_allocated_bitrate_;\n\n  bool previously_in_alr_ = false;\n\n  absl::optional<DataSize> current_data_window_;\n\n  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#include \"modules/congestion_controller/goog_cc/link_capacity_estimator.h\"\n#include \"rtc_base/numerics/safe_minmax.h\"\n\n#include <algorithm>\n\nnamespace webrtc {\nLinkCapacityEstimator::LinkCapacityEstimator() {}\n\nDataRate LinkCapacityEstimator::UpperBound() const {\n  if (estimate_kbps_.has_value())\n    return DataRate::kbps(estimate_kbps_.value() +\n                          3 * deviation_estimate_kbps());\n  return DataRate::Infinity();\n}\n\nDataRate LinkCapacityEstimator::LowerBound() const {\n  if (estimate_kbps_.has_value())\n    return DataRate::kbps(\n        std::max(0.0, estimate_kbps_.value() - 3 * deviation_estimate_kbps()));\n  return DataRate::Zero();\n}\n\nvoid LinkCapacityEstimator::Reset() {\n  estimate_kbps_.reset();\n}\n\nvoid LinkCapacityEstimator::OnOveruseDetected(DataRate acknowledged_rate) {\n  Update(acknowledged_rate, 0.05);\n}\n\nvoid LinkCapacityEstimator::OnProbeRate(DataRate probe_rate) {\n  Update(probe_rate, 0.5);\n}\n\nvoid LinkCapacityEstimator::Update(DataRate capacity_sample, double alpha) {\n  double sample_kbps = capacity_sample.kbps();\n  if (!estimate_kbps_.has_value()) {\n    estimate_kbps_ = sample_kbps;\n  } else {\n    estimate_kbps_ = (1 - alpha) * estimate_kbps_.value() + alpha * sample_kbps;\n  }\n  // Estimate the variance of the link capacity estimate and normalize the\n  // variance with the link capacity estimate.\n  const double norm = std::max(estimate_kbps_.value(), 1.0);\n  double error_kbps = estimate_kbps_.value() - sample_kbps;\n  deviation_kbps_ =\n      (1 - alpha) * deviation_kbps_ + alpha * error_kbps * error_kbps / norm;\n  // 0.4 ~= 14 kbit/s at 500 kbit/s\n  // 2.5f ~= 35 kbit/s at 500 kbit/s\n  deviation_kbps_ = rtc::SafeClamp(deviation_kbps_, 0.4f, 2.5f);\n}\n\nbool LinkCapacityEstimator::has_estimate() const {\n  return estimate_kbps_.has_value();\n}\n\nDataRate LinkCapacityEstimator::estimate() const {\n  return DataRate::kbps(*estimate_kbps_);\n}\n\ndouble LinkCapacityEstimator::deviation_estimate_kbps() const {\n  // Calculate the max bit rate std dev given the normalized\n  // variance and the current throughput bitrate. The standard deviation will\n  // only be used if estimate_kbps_ has a value.\n  return sqrt(deviation_kbps_ * estimate_kbps_.value());\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.h",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_\n\n#include \"api/units/data_rate.h\"\n\n#include <absl/types/optional.h>\n\nnamespace webrtc {\nclass LinkCapacityEstimator {\n public:\n  LinkCapacityEstimator();\n  DataRate UpperBound() const;\n  DataRate LowerBound() const;\n  void Reset();\n  void OnOveruseDetected(DataRate acknowledged_rate);\n  void OnProbeRate(DataRate probe_rate);\n  bool has_estimate() const;\n  DataRate estimate() const;\n\n private:\n  friend class GoogCcStatePrinter;\n  void Update(DataRate capacity_sample, double alpha);\n\n  double deviation_estimate_kbps() const;\n  absl::optional<double> estimate_kbps_;\n  double deviation_kbps_ = 0.4;\n};\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/congestion_controller/goog_cc/median_slope_estimator.h\"\n\n#include <vector>\n\nnamespace webrtc {\n\nconstexpr unsigned int kDeltaCounterMax = 1000;\n\nMedianSlopeEstimator::MedianSlopeEstimator(size_t window_size,\n                                           double threshold_gain)\n    : window_size_(window_size),\n      threshold_gain_(threshold_gain),\n      num_of_deltas_(0),\n      accumulated_delay_(0),\n      delay_hist_(),\n      median_filter_(0.5),\n      trendline_(0) {}\n\nMedianSlopeEstimator::~MedianSlopeEstimator() {}\n\nMedianSlopeEstimator::DelayInfo::DelayInfo(int64_t time,\n                                           double delay,\n                                           size_t slope_count)\n    : time(time), delay(delay) {\n  slopes.reserve(slope_count);\n}\n\nMedianSlopeEstimator::DelayInfo::~DelayInfo() = default;\n\nvoid MedianSlopeEstimator::Update(double recv_delta_ms,\n                                  double send_delta_ms,\n                                  int64_t arrival_time_ms) {\n  const double delta_ms = recv_delta_ms - send_delta_ms;\n  ++num_of_deltas_;\n  if (num_of_deltas_ > kDeltaCounterMax)\n    num_of_deltas_ = kDeltaCounterMax;\n\n  accumulated_delay_ += delta_ms;\n  // BWE_TEST_LOGGING_PLOT(1, \"accumulated_delay_ms\", arrival_time_ms,\n                        // accumulated_delay_);\n\n  // If the window is full, remove the |window_size_| - 1 slopes that belong to\n  // the oldest point.\n  if (delay_hist_.size() == window_size_) {\n    // for (double slope : delay_hist_.front().slopes) {\n      // const bool success = median_filter_.Erase(slope);\n      // RTC_CHECK(success);\n    // }\n    delay_hist_.pop_front();\n  }\n  // Add |window_size_| - 1 new slopes.\n  for (auto& old_delay : delay_hist_) {\n    if (arrival_time_ms - old_delay.time != 0) {\n      // The C99 standard explicitly states that casts and assignments must\n      // perform the associated conversions. This means that |slope| will be\n      // a 64-bit double even if the division is computed using, e.g., 80-bit\n      // extended precision. I believe this also holds in C++ even though the\n      // C++11 standard isn't as explicit. Furthermore, there are good reasons\n      // to believe that compilers couldn't perform optimizations that break\n      // this assumption even if they wanted to.\n      double slope = (accumulated_delay_ - old_delay.delay) /\n                     static_cast<double>(arrival_time_ms - old_delay.time);\n      median_filter_.Insert(slope);\n      // We want to avoid issues with different rounding mode / precision\n      // which we might get if we recomputed the slope when we remove it.\n      old_delay.slopes.push_back(slope);\n    }\n  }\n  delay_hist_.emplace_back(arrival_time_ms, accumulated_delay_,\n                           window_size_ - 1);\n  // Recompute the median slope.\n  if (delay_hist_.size() == window_size_)\n    trendline_ = median_filter_.GetPercentileValue();\n\n  // BWE_TEST_LOGGING_PLOT(1, \"trendline_slope\", arrival_time_ms, trendline_);\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_\n\n#include \"rtc_base/constructor_magic.h\"\n#include \"rtc_base/numerics/percentile_filter.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <deque>\n#include <vector>\n\nnamespace webrtc {\n\nclass MedianSlopeEstimator {\n public:\n  // |window_size| is the number of points required to compute a trend line.\n  // |threshold_gain| is used to scale the trendline slope for comparison to\n  // the old threshold. Once the old estimator has been removed (or the\n  // thresholds been merged into the estimators), we can just set the\n  // threshold instead of setting a gain.\n  MedianSlopeEstimator(size_t window_size, double threshold_gain);\n  ~MedianSlopeEstimator();\n\n  // Update the estimator with a new sample. The deltas should represent deltas\n  // between timestamp groups as defined by the InterArrival class.\n  void Update(double recv_delta_ms,\n              double send_delta_ms,\n              int64_t arrival_time_ms);\n\n  // Returns the estimated trend k multiplied by some gain.\n  // 0 < k < 1   ->  the delay increases, queues are filling up\n  //   k == 0    ->  the delay does not change\n  //   k <  0    ->  the delay decreases, queues are being emptied\n  double trendline_slope() const { return trendline_ * threshold_gain_; }\n\n  // Returns the number of deltas which the current estimator state is based on.\n  unsigned int num_of_deltas() const { return num_of_deltas_; }\n\n private:\n  struct DelayInfo {\n    DelayInfo(int64_t time, double delay, size_t slope_count);\n    ~DelayInfo();\n    int64_t time;\n    double delay;\n    std::vector<double> slopes;\n  };\n  // Parameters.\n  const size_t window_size_;\n  const double threshold_gain_;\n  // Used by the existing threshold.\n  unsigned int num_of_deltas_;\n  // Theil-Sen robust line fitting\n  double accumulated_delay_;\n  std::deque<DelayInfo> delay_hist_;\n  PercentileFilter<double> median_filter_;\n  double trendline_;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(MedianSlopeEstimator);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::ProbeBitrateEstimator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/probe_bitrate_estimator.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n\nnamespace webrtc {\nnamespace {\n// The minumum number of probes we need to receive feedback about in percent\n// in order to have a valid estimate.\nconstexpr double kMinReceivedProbesRatio = .80;\n\n// The minumum number of bytes we need to receive feedback about in percent\n// in order to have a valid estimate.\nconstexpr double kMinReceivedBytesRatio = .80;\n\n// The maximum |receive rate| / |send rate| ratio for a valid estimate.\nconstexpr float kMaxValidRatio = 2.0f;\n\n// The minimum |receive rate| / |send rate| ratio assuming that the link is\n// not saturated, i.e. we assume that we will receive at least\n// kMinRatioForUnsaturatedLink * |send rate| if |send rate| is less than the\n// link capacity.\nconstexpr float kMinRatioForUnsaturatedLink = 0.9f;\n\n// The target utilization of the link. If we know true link capacity\n// we'd like to send at 95% of that rate.\nconstexpr float kTargetUtilizationFraction = 0.95f;\n\n// The maximum time period over which the cluster history is retained.\n// This is also the maximum time period beyond which a probing burst is not\n// expected to last.\nconstexpr TimeDelta kMaxClusterHistory = TimeDelta::Seconds<1>();\n\n// The maximum time interval between first and the last probe on a cluster\n// on the sender side as well as the receive side.\nconstexpr TimeDelta kMaxProbeInterval = TimeDelta::Seconds<1>();\n\n}  // namespace\n\nProbeBitrateEstimator::ProbeBitrateEstimator() {\n\n}\n\nProbeBitrateEstimator::~ProbeBitrateEstimator() = default;\n\nabsl::optional<DataRate> ProbeBitrateEstimator::HandleProbeAndEstimateBitrate(\n    const PacketResult& packet_feedback) {\n  int cluster_id = packet_feedback.sent_packet.pacing_info.probe_cluster_id;\n\n  //RTC_DCHECK_NE(cluster_id, PacedPacketInfo::kNotAProbe);\n  if (cluster_id == PacedPacketInfo::kNotAProbe) {\n    MS_ERROR(\"cluster_id == kNotAProbe\");\n    return absl::nullopt;\n  }\n\n  EraseOldClusters(packet_feedback.receive_time);\n\n  AggregatedCluster* cluster = &clusters_[cluster_id];\n\n  if (packet_feedback.sent_packet.send_time < cluster->first_send) {\n    cluster->first_send = packet_feedback.sent_packet.send_time;\n  }\n  if (packet_feedback.sent_packet.send_time > cluster->last_send) {\n    cluster->last_send = packet_feedback.sent_packet.send_time;\n    cluster->size_last_send = packet_feedback.sent_packet.size;\n  }\n  if (packet_feedback.receive_time < cluster->first_receive) {\n    cluster->first_receive = packet_feedback.receive_time;\n    cluster->size_first_receive = packet_feedback.sent_packet.size;\n  }\n  if (packet_feedback.receive_time > cluster->last_receive) {\n    cluster->last_receive = packet_feedback.receive_time;\n  }\n  cluster->size_total += packet_feedback.sent_packet.size;\n  cluster->num_probes += 1;\n\n  // RTC_DCHECK_GT(\n      // packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes, 0);\n  // RTC_DCHECK_GT(packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes,\n                // 0);\n  if (packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes <= 0) {\n    MS_ERROR(\"probe_cluster_min_probes must be > 0\");\n    return absl::nullopt;\n  }\n  if (packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes <= 0) {\n    MS_ERROR(\"probe_cluster_min_bytes must be > 0\");\n    return absl::nullopt;\n  }\n\n  int min_probes =\n      packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes *\n      kMinReceivedProbesRatio;\n  DataSize min_size =\n      DataSize::bytes(\n          packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes) *\n      kMinReceivedBytesRatio;\n  if (cluster->num_probes < min_probes || cluster->size_total < min_size)\n    return absl::nullopt;\n\n  TimeDelta send_interval = cluster->last_send - cluster->first_send;\n  TimeDelta receive_interval = cluster->last_receive - cluster->first_receive;\n\n  // // TODO: TMP\n  // MS_WARN_DEV(\n  //   \"-------------- probing cluster result\"\n  //   \" [cluster id:%d]\"\n  //   \" [send interval:%s]\"\n  //   \" [receive interval:%s]\",\n  //   cluster_id,\n  //   ToString(send_interval).c_str(),\n  //   ToString(receive_interval).c_str());\n\n  // TODO: TMP WIP cerdo to avoid that send_interval or receive_interval is zero.\n  //\n  // if (send_interval <= TimeDelta::Zero())\n  //   send_interval = TimeDelta::ms(1u);\n  // if (receive_interval <= TimeDelta::Zero())\n  //   receive_interval = TimeDelta::ms(1u);\n\n  if (send_interval <= TimeDelta::Zero() || send_interval > kMaxProbeInterval ||\n      receive_interval <= TimeDelta::Zero() ||\n      receive_interval > kMaxProbeInterval) {\n    MS_WARN_DEV(\n      \"probing unsuccessful, invalid send/receive interval\"\n      \" [cluster id:%d]\"\n      \" [send interval:%s]\"\n      \" [receive interval:%s]\",\n      cluster_id,\n      ToString(send_interval).c_str(),\n      ToString(receive_interval).c_str());\n\n    return absl::nullopt;\n  }\n  // Since the |send_interval| does not include the time it takes to actually\n  // send the last packet the size of the last sent packet should not be\n  // included when calculating the send bitrate.\n  //RTC_DCHECK_GT(cluster->size_total, cluster->size_last_send);\n  DataSize send_size = cluster->size_total - cluster->size_last_send;\n  DataRate send_rate = send_size / send_interval;\n\n  // Since the |receive_interval| does not include the time it takes to\n  // actually receive the first packet the size of the first received packet\n  // should not be included when calculating the receive bitrate.\n  //RTC_DCHECK_GT(cluster->size_total, cluster->size_first_receive);\n  DataSize receive_size = cluster->size_total - cluster->size_first_receive;\n  DataRate receive_rate = receive_size / receive_interval;\n\n  double ratio = receive_rate / send_rate;\n  if (ratio > kMaxValidRatio) {\n    MS_WARN_DEV(\n      \"probing unsuccessful, receive/send ratio too high\"\n      \" [cluster id:%d, send:%s / %s = %s]\"\n      \" [receive:%s / %s = %s]\"\n      \" [ratio:%s / %s = %f]\"\n      \" > kMaxValidRatio:%f]\",\n      cluster_id,\n      ToString(send_size).c_str(),\n      ToString(send_interval).c_str(),\n      ToString(send_rate).c_str(),\n      ToString(receive_size).c_str(),\n      ToString(receive_interval).c_str(),\n      ToString(receive_rate).c_str(),\n      ToString(receive_rate).c_str(),\n      ToString(send_rate).c_str(),\n      ratio,\n      kMaxValidRatio);\n\n    return absl::nullopt;\n  }\n\n  MS_DEBUG_DEV(\n    \"probing successful\"\n    \" [cluster id:%d]\"\n    \" [send:%s / %s = %s]\"\n    \" [receive:%s / %s = %s]\",\n    cluster_id,\n    ToString(send_size).c_str(),\n    ToString(send_interval).c_str(),\n    ToString(send_rate).c_str(),\n    ToString(receive_size).c_str(),\n    ToString(receive_interval).c_str(),\n    ToString(receive_rate).c_str());\n\n  DataRate res = std::min(send_rate, receive_rate);\n  // If we're receiving at significantly lower bitrate than we were sending at,\n  // it suggests that we've found the true capacity of the link. In this case,\n  // set the target bitrate slightly lower to not immediately overuse.\n  if (receive_rate < kMinRatioForUnsaturatedLink * send_rate) {\n    //RTC_DCHECK_GT(send_rate, receive_rate);\n    res = kTargetUtilizationFraction * receive_rate;\n  }\n  last_estimate_ = res;\n  estimated_data_rate_ = res;\n  return res;\n}\n\nabsl::optional<DataRate>\nProbeBitrateEstimator::FetchAndResetLastEstimatedBitrate() {\n  absl::optional<DataRate> estimated_data_rate = estimated_data_rate_;\n  estimated_data_rate_.reset();\n  return estimated_data_rate;\n}\n\nabsl::optional<DataRate> ProbeBitrateEstimator::last_estimate() const {\n  return last_estimate_;\n}\n\nvoid ProbeBitrateEstimator::EraseOldClusters(Timestamp timestamp) {\n  for (auto it = clusters_.begin(); it != clusters_.end();) {\n    if (it->second.last_receive + kMaxClusterHistory < timestamp) {\n      it = clusters_.erase(it);\n    } else {\n      ++it;\n    }\n  }\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_rate.h\"\n\n#include <absl/types/optional.h>\n#include <limits>\n#include <map>\n\nnamespace webrtc {\nclass RtcEventLog;\n\nclass ProbeBitrateEstimator {\n public:\n  ProbeBitrateEstimator();\n  ~ProbeBitrateEstimator();\n\n  // Should be called for every probe packet we receive feedback about.\n  // Returns the estimated bitrate if the probe completes a valid cluster.\n  absl::optional<DataRate> HandleProbeAndEstimateBitrate(\n      const PacketResult& packet_feedback);\n\n  absl::optional<DataRate> FetchAndResetLastEstimatedBitrate();\n\n  absl::optional<DataRate> last_estimate() const;\n\n private:\n  struct AggregatedCluster {\n    int num_probes = 0;\n    Timestamp first_send = Timestamp::PlusInfinity();\n    Timestamp last_send = Timestamp::MinusInfinity();\n    Timestamp first_receive = Timestamp::PlusInfinity();\n    Timestamp last_receive = Timestamp::MinusInfinity();\n    DataSize size_last_send = DataSize::Zero();\n    DataSize size_first_receive = DataSize::Zero();\n    DataSize size_total = DataSize::Zero();\n  };\n\n  // Erases old cluster data that was seen before |timestamp|.\n  void EraseOldClusters(Timestamp timestamp);\n\n  std::map<int, AggregatedCluster> clusters_;\n  absl::optional<DataRate> estimated_data_rate_;\n  absl::optional<DataRate> last_estimate_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::ProbeController\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/probe_controller.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/time_delta.h\"\n#include \"api/units/timestamp.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n#include <initializer_list>\n#include <string>\n\nnamespace webrtc {\n\nnamespace {\n// The minimum number probing packets used.\nconstexpr int kMinProbePacketsSent = 5;\n\n// The minimum probing duration in ms.\nconstexpr int kMinProbeDurationMs = 15;\n\n// Maximum waiting time from the time of initiating probing to getting\n// the measured results back.\nconstexpr int64_t kMaxWaitingTimeForProbingResultMs = 1000;\n\n// Value of |min_bitrate_to_probe_further_bps_| that indicates\n// further probing is disabled.\nconstexpr int kExponentialProbingDisabled = 0;\n\n// Default probing bitrate limit. Applied only when the application didn't\n// specify max bitrate.\nconstexpr int64_t kDefaultMaxProbingBitrateBps = 5000000;\n\n// If the bitrate drops to a factor |kBitrateDropThreshold| or lower\n// and we recover within |kBitrateDropTimeoutMs|, then we'll send\n// a probe at a fraction |kProbeFractionAfterDrop| of the original bitrate.\nconstexpr double kBitrateDropThreshold = 0.66;\nconstexpr int kBitrateDropTimeoutMs = 5000;\nconstexpr double kProbeFractionAfterDrop = 0.85;\n\n// Timeout for probing after leaving ALR. If the bitrate drops significantly,\n// (as determined by the delay based estimator) and we leave ALR, then we will\n// send a probe if we recover within |kLeftAlrTimeoutMs| ms.\nconstexpr int kAlrEndedTimeoutMs = 3000;\n\n// The expected uncertainty of probe result (as a fraction of the target probe\n// This is a limit on how often probing can be done when there is a BW\n// drop detected in ALR.\nconstexpr int64_t kMinTimeBetweenAlrProbesMs = 5000;\n\n// bitrate). Used to avoid probing if the probe bitrate is close to our current\n// estimate.\nconstexpr double kProbeUncertainty = 0.05;\n\n// Use probing to recover faster after large bitrate estimate drops.\nconstexpr char kBweRapidRecoveryExperiment[] =\n    \"WebRTC-BweRapidRecoveryExperiment\";\n\n// Never probe higher than configured by OnMaxTotalAllocatedBitrate().\nconstexpr char kCappedProbingFieldTrialName[] = \"WebRTC-BweCappedProbing\";\n\n// Only do allocation probing when in ALR (but not when network-limited).\nconstexpr char kAllocProbingOnlyInAlrFieldTrialName[] =\n    \"WebRTC-BweAllocProbingOnlyInAlr\";\n\nvoid MaybeLogProbeClusterCreated(const ProbeClusterConfig& probe) {\n#if MS_LOG_DEV_LEVEL == 3\n  size_t min_bytes = static_cast<int32_t>(probe.target_data_rate.bps() *\n                                          probe.target_duration.ms() / 8000);\n#endif\n\n  MS_DEBUG_DEV(\n    \"probe cluster created [id:%d, target data rate(bps):%lld, target probe count:%d, min_bytes:%zu]\",\n    probe.id, probe.target_data_rate.bps(), probe.target_probe_count, min_bytes);\n}\n\n}  // namespace\n\nProbeControllerConfig::ProbeControllerConfig(\n    const WebRtcKeyValueConfig* key_value_config)\n    : first_exponential_probe_scale(\"p1\", 3.0),\n      second_exponential_probe_scale(\"p2\", 6.0),\n      further_exponential_probe_scale(\"step_size\", 2),\n      further_probe_threshold(\"further_probe_threshold\", 0.7),\n      alr_probing_interval(\"alr_interval\", TimeDelta::seconds(5)),\n      alr_probe_scale(\"alr_scale\", 2),\n      first_allocation_probe_scale(\"alloc_p1\", 1),\n      second_allocation_probe_scale(\"alloc_p2\", 2),\n      allocation_allow_further_probing(\"alloc_probe_further\", false) {\n  ParseFieldTrial(\n      {&first_exponential_probe_scale, &second_exponential_probe_scale,\n       &further_exponential_probe_scale, &further_probe_threshold,\n       &alr_probing_interval, &alr_probe_scale, &first_allocation_probe_scale,\n       &second_allocation_probe_scale, &allocation_allow_further_probing},\n      key_value_config->Lookup(\"WebRTC-Bwe-ProbingConfiguration\"));\n\n  // Specialized keys overriding subsets of WebRTC-Bwe-ProbingConfiguration\n  ParseFieldTrial(\n      {&first_exponential_probe_scale, &second_exponential_probe_scale},\n      key_value_config->Lookup(\"WebRTC-Bwe-InitialProbing\"));\n  ParseFieldTrial({&further_exponential_probe_scale, &further_probe_threshold},\n                  key_value_config->Lookup(\"WebRTC-Bwe-ExponentialProbing\"));\n  ParseFieldTrial({&alr_probing_interval, &alr_probe_scale},\n                  key_value_config->Lookup(\"WebRTC-Bwe-AlrProbing\"));\n  ParseFieldTrial(\n      {&first_allocation_probe_scale, &second_allocation_probe_scale,\n       &allocation_allow_further_probing},\n      key_value_config->Lookup(\"WebRTC-Bwe-AllocationProbing\"));\n}\n\nProbeControllerConfig::ProbeControllerConfig(const ProbeControllerConfig&) =\n    default;\nProbeControllerConfig::~ProbeControllerConfig() = default;\n\nProbeController::ProbeController(const WebRtcKeyValueConfig* key_value_config)\n    : enable_periodic_alr_probing_(false),\n      in_rapid_recovery_experiment_(\n          key_value_config->Lookup(kBweRapidRecoveryExperiment)\n              .find(\"Enabled\") == 0),\n      limit_probes_with_allocateable_rate_(\n          key_value_config->Lookup(kCappedProbingFieldTrialName)\n              .find(\"Disabled\") != 0),\n      allocation_probing_only_in_alr_(\n          key_value_config->Lookup(kAllocProbingOnlyInAlrFieldTrialName)\n              .find(\"Enabled\") == 0),\n      config_(ProbeControllerConfig(key_value_config)) {\n  Reset(0);\n}\n\nProbeController::~ProbeController() {}\n\nstd::vector<ProbeClusterConfig> ProbeController::SetBitrates(\n    int64_t min_bitrate_bps,\n    int64_t start_bitrate_bps,\n    int64_t max_bitrate_bps,\n    int64_t at_time_ms) {\n  if (start_bitrate_bps > 0) {\n    start_bitrate_bps_ = start_bitrate_bps;\n    estimated_bitrate_bps_ = start_bitrate_bps;\n  } else if (start_bitrate_bps_ == 0) {\n    start_bitrate_bps_ = min_bitrate_bps;\n  }\n\n  MS_DEBUG_DEV(\n    \"[old_max_bitrate_bps:%lld, max_bitrate_bps:%lld]\",\n    max_bitrate_bps_,\n    max_bitrate_bps);\n\n  // The reason we use the variable |old_max_bitrate_pbs| is because we\n  // need to set |max_bitrate_bps_| before we call InitiateProbing.\n  int64_t old_max_bitrate_bps = max_bitrate_bps_;\n  max_bitrate_bps_ = max_bitrate_bps;\n\n  switch (state_) {\n    case State::kInit:\n      if (network_available_)\n        return InitiateExponentialProbing(at_time_ms);\n      break;\n\n    case State::kWaitingForProbingResult:\n      break;\n\n    case State::kProbingComplete:\n      // If the new max bitrate is higher than both the old max bitrate and the\n      // estimate then initiate probing.\n      if (estimated_bitrate_bps_ != 0 &&\n          old_max_bitrate_bps < max_bitrate_bps_ &&\n          estimated_bitrate_bps_ < max_bitrate_bps_) {\n        // The assumption is that if we jump more than 20% in the bandwidth\n        // estimate or if the bandwidth estimate is within 90% of the new\n        // max bitrate then the probing attempt was successful.\n        mid_call_probing_succcess_threshold_ =\n            std::min(estimated_bitrate_bps_ * 1.2, max_bitrate_bps_ * 0.9);\n        mid_call_probing_waiting_for_result_ = true;\n        mid_call_probing_bitrate_bps_ = max_bitrate_bps_;\n\n        // RTC_HISTOGRAM_COUNTS_10000(\"WebRTC.BWE.MidCallProbing.Initiated\",\n                                   // max_bitrate_bps_ / 1000);\n\n        return InitiateProbing(at_time_ms, {max_bitrate_bps_}, false);\n      }\n      break;\n  }\n  return std::vector<ProbeClusterConfig>();\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::OnMaxTotalAllocatedBitrate(\n    int64_t max_total_allocated_bitrate,\n    int64_t at_time_ms) {\n  MS_DEBUG_DEV(\"[max_total_allocated_bitrate:%\" PRIi64 \"]\", max_total_allocated_bitrate);\n\n  const bool in_alr = alr_start_time_ms_.has_value();\n  const bool allow_allocation_probe =\n      allocation_probing_only_in_alr_ ? in_alr : true;\n\n  if (state_ == State::kProbingComplete &&\n      max_total_allocated_bitrate != max_total_allocated_bitrate_ &&\n      estimated_bitrate_bps_ != 0 &&\n      (max_bitrate_bps_ <= 0 || estimated_bitrate_bps_ < max_bitrate_bps_) &&\n      estimated_bitrate_bps_ < max_total_allocated_bitrate &&\n      allow_allocation_probe) {\n    max_total_allocated_bitrate_ = max_total_allocated_bitrate;\n\n    if (!config_.first_allocation_probe_scale)\n      return std::vector<ProbeClusterConfig>();\n\n    std::vector<int64_t> probes = {\n        static_cast<int64_t>(config_.first_allocation_probe_scale.Value() *\n                             max_total_allocated_bitrate)};\n    if (config_.second_allocation_probe_scale) {\n      probes.push_back(config_.second_allocation_probe_scale.Value() *\n                       max_total_allocated_bitrate);\n    }\n    return InitiateProbing(at_time_ms, probes,\n                           config_.allocation_allow_further_probing);\n  }\n  max_total_allocated_bitrate_ = max_total_allocated_bitrate;\n  return std::vector<ProbeClusterConfig>();\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::OnNetworkAvailability(\n    NetworkAvailability msg) {\n  network_available_ = msg.network_available;\n\n  if (!network_available_ && state_ == State::kWaitingForProbingResult) {\n    state_ = State::kProbingComplete;\n    min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;\n  }\n\n  if (network_available_ && state_ == State::kInit && start_bitrate_bps_ > 0)\n    return InitiateExponentialProbing(msg.at_time.ms());\n  return std::vector<ProbeClusterConfig>();\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::InitiateExponentialProbing(\n    int64_t at_time_ms) {\n  //RTC_DCHECK(network_available_);\n  //RTC_DCHECK(state_ == State::kInit);\n  //RTC_DCHECK_GT(start_bitrate_bps_, 0);\n  if (!network_available_) {\n    MS_ERROR(\"network not available\");\n  }\n  if (state_ != State::kInit) {\n    MS_ERROR(\"state_ must be State::kInit\");\n  }\n  if (start_bitrate_bps_ <= 0) {\n    MS_ERROR(\"start_bitrate_bps_ must be > 0\");\n  }\n\n  // When probing at 1.8 Mbps ( 6x 300), this represents a threshold of\n  // 1.2 Mbps to continue probing.\n  std::vector<int64_t> probes = {static_cast<int64_t>(\n      config_.first_exponential_probe_scale * start_bitrate_bps_)};\n  if (config_.second_exponential_probe_scale) {\n    probes.push_back(config_.second_exponential_probe_scale.Value() *\n                     start_bitrate_bps_);\n  }\n  return InitiateProbing(at_time_ms, probes, true);\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::SetEstimatedBitrate(\n    int64_t bitrate_bps,\n    int64_t at_time_ms) {\n  if (mid_call_probing_waiting_for_result_ &&\n      bitrate_bps >= mid_call_probing_succcess_threshold_) {\n    // RTC_HISTOGRAM_COUNTS_10000(\"WebRTC.BWE.MidCallProbing.Success\",\n                               // mid_call_probing_bitrate_bps_ / 1000);\n    // RTC_HISTOGRAM_COUNTS_10000(\"WebRTC.BWE.MidCallProbing.ProbedKbps\",\n                               // bitrate_bps / 1000);\n    mid_call_probing_waiting_for_result_ = false;\n  }\n  std::vector<ProbeClusterConfig> pending_probes;\n  if (state_ == State::kWaitingForProbingResult) {\n    // Continue probing if probing results indicate channel has greater\n    // capacity.\n    // MS_DEBUG_DEV(\n    //   \"[measured bitrate:%\" PRIi64 \", minimum to probe further:%\" PRIi64 \"]\",\n    //   bitrate_bps, min_bitrate_to_probe_further_bps_);\n\n    if (min_bitrate_to_probe_further_bps_ != kExponentialProbingDisabled &&\n        bitrate_bps > min_bitrate_to_probe_further_bps_) {\n      pending_probes = InitiateProbing(\n          at_time_ms,\n          {static_cast<int64_t>(config_.further_exponential_probe_scale *\n                                bitrate_bps)},\n          true);\n    }\n  }\n\n  if (bitrate_bps < kBitrateDropThreshold * estimated_bitrate_bps_) {\n    time_of_last_large_drop_ms_ = at_time_ms;\n    bitrate_before_last_large_drop_bps_ = estimated_bitrate_bps_;\n  }\n\n  estimated_bitrate_bps_ = bitrate_bps;\n  return pending_probes;\n}\n\nvoid ProbeController::EnablePeriodicAlrProbing(bool enable) {\n  enable_periodic_alr_probing_ = enable;\n}\n\nvoid ProbeController::SetAlrStartTimeMs(\n    absl::optional<int64_t> alr_start_time_ms) {\n  alr_start_time_ms_ = alr_start_time_ms;\n}\nvoid ProbeController::SetAlrEndedTimeMs(int64_t alr_end_time_ms) {\n  alr_end_time_ms_.emplace(alr_end_time_ms);\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::RequestProbe(\n    int64_t at_time_ms) {\n  // Called once we have returned to normal state after a large drop in\n  // estimated bandwidth. The current response is to initiate a single probe\n  // session (if not already probing) at the previous bitrate.\n  //\n  // If the probe session fails, the assumption is that this drop was a\n  // real one from a competing flow or a network change.\n  bool in_alr = alr_start_time_ms_.has_value();\n  bool alr_ended_recently =\n      (alr_end_time_ms_.has_value() &&\n       at_time_ms - alr_end_time_ms_.value() < kAlrEndedTimeoutMs);\n  if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) {\n    if (state_ == State::kProbingComplete) {\n      uint32_t suggested_probe_bps =\n          kProbeFractionAfterDrop * bitrate_before_last_large_drop_bps_;\n      uint32_t min_expected_probe_result_bps =\n          (1 - kProbeUncertainty) * suggested_probe_bps;\n      int64_t time_since_drop_ms = at_time_ms - time_of_last_large_drop_ms_;\n      int64_t time_since_probe_ms = at_time_ms - last_bwe_drop_probing_time_ms_;\n      if (min_expected_probe_result_bps > estimated_bitrate_bps_ &&\n          time_since_drop_ms < kBitrateDropTimeoutMs &&\n          time_since_probe_ms > kMinTimeBetweenAlrProbesMs) {\n        MS_WARN_TAG(bwe, \"detected big bandwidth drop, start probing\");\n        // Track how often we probe in response to bandwidth drop in ALR.\n        // RTC_HISTOGRAM_COUNTS_10000(\n        //     \"WebRTC.BWE.BweDropProbingIntervalInS\",\n        //     (at_time_ms - last_bwe_drop_probing_time_ms_) / 1000);\n        last_bwe_drop_probing_time_ms_ = at_time_ms;\n        return InitiateProbing(at_time_ms, {suggested_probe_bps}, false);\n      }\n    }\n  }\n  return std::vector<ProbeClusterConfig>();\n}\n\nvoid ProbeController::SetMaxBitrate(int64_t max_bitrate_bps) {\n  MS_DEBUG_DEV(\"[max_bitrate_bps:%\" PRIi64 \"]\", max_bitrate_bps);\n\n  max_bitrate_bps_ = max_bitrate_bps;\n}\n\nvoid ProbeController::Reset(int64_t at_time_ms) {\n  MS_DEBUG_DEV(\"resetted\");\n\n  network_available_ = true;\n  state_ = State::kInit;\n  min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;\n  time_last_probing_initiated_ms_ = 0;\n  estimated_bitrate_bps_ = 0;\n  start_bitrate_bps_ = 0;\n  max_bitrate_bps_ = 0;\n  int64_t now_ms = at_time_ms;\n  last_bwe_drop_probing_time_ms_ = now_ms;\n  alr_end_time_ms_.reset();\n  mid_call_probing_waiting_for_result_ = false;\n  time_of_last_large_drop_ms_ = now_ms;\n  bitrate_before_last_large_drop_bps_ = 0;\n  max_total_allocated_bitrate_ = 0;\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::Process(int64_t at_time_ms) {\n  if (at_time_ms - time_last_probing_initiated_ms_ >\n      kMaxWaitingTimeForProbingResultMs) {\n    mid_call_probing_waiting_for_result_ = false;\n\n    if (state_ == State::kWaitingForProbingResult) {\n      MS_WARN_TAG(bwe, \"kWaitingForProbingResult: timeout\");\n      state_ = State::kProbingComplete;\n      min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;\n    }\n  }\n\n  if (enable_periodic_alr_probing_ && state_ == State::kProbingComplete) {\n    // Probe bandwidth periodically when in ALR state.\n    if (alr_start_time_ms_ && estimated_bitrate_bps_ > 0) {\n      int64_t next_probe_time_ms =\n          std::max(*alr_start_time_ms_, time_last_probing_initiated_ms_) +\n          config_.alr_probing_interval->ms();\n      if (at_time_ms >= next_probe_time_ms) {\n        return InitiateProbing(at_time_ms,\n                               {static_cast<int64_t>(estimated_bitrate_bps_ *\n                                                     config_.alr_probe_scale)},\n                               true);\n      }\n    }\n  }\n  return std::vector<ProbeClusterConfig>();\n}\n\nstd::vector<ProbeClusterConfig> ProbeController::InitiateProbing(\n    int64_t now_ms,\n    std::vector<int64_t> bitrates_to_probe,\n    bool probe_further) {\n  int64_t max_probe_bitrate_bps =\n      max_bitrate_bps_ > 0 ? max_bitrate_bps_ : kDefaultMaxProbingBitrateBps;\n\n  MS_DEBUG_DEV(\n    \"[max_bitrate_bps_:%lld, max_probe_bitrate_bps:%\" PRIi64 \"]\",\n    max_bitrate_bps_,\n    max_probe_bitrate_bps);\n\n  if (limit_probes_with_allocateable_rate_ &&\n      max_total_allocated_bitrate_ > 0) {\n    // If a max allocated bitrate has been configured, allow probing up to 2x\n    // that rate. This allows some overhead to account for bursty streams,\n    // which otherwise would have to ramp up when the overshoot is already in\n    // progress.\n    // It also avoids minor quality reduction caused by probes often being\n    // received at slightly less than the target probe bitrate.\n    max_probe_bitrate_bps =\n        std::min(max_probe_bitrate_bps, max_total_allocated_bitrate_ * 2);\n  }\n\n  std::vector<ProbeClusterConfig> pending_probes;\n  for (int64_t bitrate : bitrates_to_probe) {\n    //RTC_DCHECK_GT(bitrate, 0);\n\n    if (bitrate > max_probe_bitrate_bps) {\n      bitrate = max_probe_bitrate_bps;\n      probe_further = false;\n    }\n\n    ProbeClusterConfig config;\n    config.at_time = Timestamp::ms(now_ms);\n    config.target_data_rate = DataRate::bps(rtc::dchecked_cast<int>(bitrate));\n    config.target_duration = TimeDelta::ms(kMinProbeDurationMs);\n    config.target_probe_count = kMinProbePacketsSent;\n    config.id = next_probe_cluster_id_;\n    next_probe_cluster_id_++;\n    MaybeLogProbeClusterCreated(config);\n    pending_probes.push_back(config);\n  }\n  time_last_probing_initiated_ms_ = now_ms;\n  if (probe_further) {\n    state_ = State::kWaitingForProbingResult;\n    min_bitrate_to_probe_further_bps_ =\n        (*(bitrates_to_probe.end() - 1)) * config_.further_probe_threshold;\n  } else {\n    state_ = State::kProbingComplete;\n    min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled;\n  }\n  return pending_probes;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_controller.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_\n\n#include \"api/transport/network_control.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"rtc_base/constructor_magic.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n#include \"rtc_base/system/unused.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n#include <initializer_list>\n#include <vector>\n\nnamespace webrtc {\n\nstruct ProbeControllerConfig {\n  explicit ProbeControllerConfig(const WebRtcKeyValueConfig* key_value_config);\n  ProbeControllerConfig(const ProbeControllerConfig&);\n  ProbeControllerConfig& operator=(const ProbeControllerConfig&) = default;\n  ~ProbeControllerConfig();\n\n  // These parameters configure the initial probes. First we send one or two\n  // probes of sizes p1 * start_bitrate_bps_ and p2 * start_bitrate_bps_.\n  // Then whenever we get a bitrate estimate of at least further_probe_threshold\n  // times the size of the last sent probe we'll send another one of size\n  // step_size times the new estimate.\n  FieldTrialParameter<double> first_exponential_probe_scale;\n  FieldTrialOptional<double> second_exponential_probe_scale;\n  FieldTrialParameter<double> further_exponential_probe_scale;\n  FieldTrialParameter<double> further_probe_threshold;\n\n  // Configures how often we send ALR probes and how big they are.\n  FieldTrialParameter<TimeDelta> alr_probing_interval;\n  FieldTrialParameter<double> alr_probe_scale;\n\n  // Configures the probes emitted by changed to the allocated bitrate.\n  FieldTrialOptional<double> first_allocation_probe_scale;\n  FieldTrialOptional<double> second_allocation_probe_scale;\n  FieldTrialFlag allocation_allow_further_probing;\n};\n\n// This class controls initiation of probing to estimate initial channel\n// capacity. There is also support for probing during a session when max\n// bitrate is adjusted by an application.\nclass ProbeController {\n public:\n  explicit ProbeController(const WebRtcKeyValueConfig* key_value_config);\n  ~ProbeController();\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> SetBitrates(\n      int64_t min_bitrate_bps,\n      int64_t start_bitrate_bps,\n      int64_t max_bitrate_bps,\n      int64_t at_time_ms);\n\n  // The total bitrate, as opposed to the max bitrate, is the sum of the\n  // configured bitrates for all active streams.\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig>\n  OnMaxTotalAllocatedBitrate(int64_t max_total_allocated_bitrate,\n                             int64_t at_time_ms);\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> OnNetworkAvailability(\n      NetworkAvailability msg);\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> SetEstimatedBitrate(\n      int64_t bitrate_bps,\n      int64_t at_time_ms);\n\n  void EnablePeriodicAlrProbing(bool enable);\n\n  void SetAlrStartTimeMs(absl::optional<int64_t> alr_start_time);\n  void SetAlrEndedTimeMs(int64_t alr_end_time);\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> RequestProbe(\n      int64_t at_time_ms);\n\n  // Sets a new maximum probing bitrate, without generating a new probe cluster.\n  void SetMaxBitrate(int64_t max_bitrate_bps);\n\n  // Resets the ProbeController to a state equivalent to as if it was just\n  // created EXCEPT for |enable_periodic_alr_probing_|.\n  void Reset(int64_t at_time_ms);\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> Process(\n      int64_t at_time_ms);\n\n private:\n  enum class State {\n    // Initial state where no probing has been triggered yet.\n    kInit,\n    // Waiting for probing results to continue further probing.\n    kWaitingForProbingResult,\n    // Probing is complete.\n    kProbingComplete,\n  };\n\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig>\n  InitiateExponentialProbing(int64_t at_time_ms);\n  RTC_WARN_UNUSED_RESULT std::vector<ProbeClusterConfig> InitiateProbing(\n      int64_t now_ms,\n      std::vector<int64_t> bitrates_to_probe,\n      bool probe_further);\n\n  bool network_available_;\n  State state_;\n  int64_t min_bitrate_to_probe_further_bps_;\n  int64_t time_last_probing_initiated_ms_;\n  int64_t estimated_bitrate_bps_;\n  int64_t start_bitrate_bps_;\n  int64_t max_bitrate_bps_;\n  int64_t last_bwe_drop_probing_time_ms_;\n  absl::optional<int64_t> alr_start_time_ms_;\n  absl::optional<int64_t> alr_end_time_ms_;\n  bool enable_periodic_alr_probing_;\n  int64_t time_of_last_large_drop_ms_;\n  int64_t bitrate_before_last_large_drop_bps_;\n  int64_t max_total_allocated_bitrate_;\n\n  const bool in_rapid_recovery_experiment_;\n  const bool limit_probes_with_allocateable_rate_;\n  const bool allocation_probing_only_in_alr_;\n  // For WebRTC.BWE.MidCallProbing.* metric.\n  bool mid_call_probing_waiting_for_result_;\n  int64_t mid_call_probing_bitrate_bps_;\n  int64_t mid_call_probing_succcess_threshold_;\n\n  int32_t next_probe_cluster_id_ = 1;\n\n  ProbeControllerConfig config_;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(ProbeController);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::TrendlineEstimator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/goog_cc/trendline_estimator.h\"\n\n#include <math.h>\n\n#include <algorithm>\n#include <string>\n\n#include \"absl/strings/match.h\"\n#include \"absl/types/optional.h\"\n#include \"api/network_state_predictor.h\"\n#include \"rtc_base/numerics/safe_minmax.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n\n#include \"Logger.hpp\"\n\nnamespace webrtc {\n\nnamespace {\n\n// Parameters for linear least squares fit of regression line to noisy data.\nconstexpr double kDefaultTrendlineSmoothingCoeff = 0.9;\nconstexpr double kDefaultTrendlineThresholdGain = 4.0;\n\nabsl::optional<double> LinearFitSlope(\n    const std::deque<TrendlineEstimator::PacketTiming>& packets) {\n  // RTC_DCHECK(packets.size() >= 2);\n  // Compute the \"center of mass\".\n  double sum_x = 0;\n  double sum_y = 0;\n  for (const auto& packet : packets) {\n    sum_x += packet.arrival_time_ms;\n    sum_y += packet.smoothed_delay_ms;\n  }\n  double x_avg = sum_x / packets.size();\n  double y_avg = sum_y / packets.size();\n  // Compute the slope k = \\sum (x_i-x_avg)(y_i-y_avg) / \\sum (x_i-x_avg)^2\n  double numerator = 0;\n  double denominator = 0;\n  for (const auto& packet : packets) {\n    double x = packet.arrival_time_ms;\n    double y = packet.smoothed_delay_ms;\n    numerator += (x - x_avg) * (y - y_avg);\n    denominator += (x - x_avg) * (x - x_avg);\n  }\n  if (denominator == 0)\n    return absl::nullopt;\n  return numerator / denominator;\n}\n\nabsl::optional<double> ComputeSlopeCap(\n    const std::deque<TrendlineEstimator::PacketTiming>& packets,\n    const TrendlineEstimatorSettings& settings) {\n  // RTC_DCHECK(1 <= settings.beginning_packets &&\n  //            settings.beginning_packets < packets.size());\n  // RTC_DCHECK(1 <= settings.end_packets &&\n  //            settings.end_packets < packets.size());\n  // RTC_DCHECK(settings.beginning_packets + settings.end_packets <=\n  //            packets.size());\n  TrendlineEstimator::PacketTiming early = packets[0];\n  for (size_t i = 1; i < settings.beginning_packets; ++i) {\n    if (packets[i].raw_delay_ms < early.raw_delay_ms)\n      early = packets[i];\n  }\n  size_t late_start = packets.size() - settings.end_packets;\n  TrendlineEstimator::PacketTiming late = packets[late_start];\n  for (size_t i = late_start + 1; i < packets.size(); ++i) {\n    if (packets[i].raw_delay_ms < late.raw_delay_ms)\n      late = packets[i];\n  }\n  if (late.arrival_time_ms - early.arrival_time_ms < 1) {\n    return absl::nullopt;\n  }\n  return (late.raw_delay_ms - early.raw_delay_ms) /\n             (late.arrival_time_ms - early.arrival_time_ms) +\n         settings.cap_uncertainty;\n}\n\nconstexpr double kMaxAdaptOffsetMs = 15.0;\nconstexpr double kOverUsingTimeThreshold = 10;\nconstexpr int kMinNumDeltas = 60;\nconstexpr int kDeltaCounterMax = 1000;\n\n}  // namespace\n\nTrendlineEstimator::TrendlineEstimator(\n    NetworkStatePredictor* network_state_predictor)\n    : smoothing_coef_(kDefaultTrendlineSmoothingCoeff),\n      threshold_gain_(kDefaultTrendlineThresholdGain),\n      num_of_deltas_(0),\n      first_arrival_time_ms_(-1),\n      accumulated_delay_(0),\n      smoothed_delay_(0),\n      delay_hist_(),\n      k_up_(0.0087),\n      k_down_(0.039),\n      overusing_time_threshold_(kOverUsingTimeThreshold),\n      threshold_(12.5),\n      prev_modified_trend_(NAN),\n      last_update_ms_(-1),\n      prev_trend_(0.0),\n      time_over_using_(-1),\n      overuse_counter_(0),\n      hypothesis_(BandwidthUsage::kBwNormal),\n      hypothesis_predicted_(BandwidthUsage::kBwNormal),\n      network_state_predictor_(network_state_predictor) {\n}\n\nTrendlineEstimator::~TrendlineEstimator() {}\n\nvoid TrendlineEstimator::UpdateTrendline(double recv_delta_ms,\n                                         double send_delta_ms,\n                                         int64_t send_time_ms,\n                                         int64_t arrival_time_ms) {\n  const double delta_ms = recv_delta_ms - send_delta_ms;\n  ++num_of_deltas_;\n  num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax);\n  if (first_arrival_time_ms_ == -1)\n    first_arrival_time_ms_ = arrival_time_ms;\n\n  // Exponential backoff filter.\n  accumulated_delay_ += delta_ms;\n  // BWE_TEST_LOGGING_PLOT(1, \"accumulated_delay_ms\", arrival_time_ms,\n  //                       accumulated_delay_);\n  smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +\n                    (1 - smoothing_coef_) * accumulated_delay_;\n  // BWE_TEST_LOGGING_PLOT(1, \"smoothed_delay_ms\", arrival_time_ms,\n  //                       smoothed_delay_);\n\n  // Maintain packet window\n  delay_hist_.emplace_back(\n      static_cast<double>(arrival_time_ms - first_arrival_time_ms_),\n      smoothed_delay_, accumulated_delay_);\n  if (settings_.enable_sort) {\n    for (size_t i = delay_hist_.size() - 1;\n         i > 0 &&\n         delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms;\n         --i) {\n      std::swap(delay_hist_[i], delay_hist_[i - 1]);\n    }\n  }\n  if (delay_hist_.size() > settings_.window_size)\n    delay_hist_.pop_front();\n\n  // Simple linear regression.\n  double trend = prev_trend_;\n  if (delay_hist_.size() == settings_.window_size) {\n    // Update trend_ if it is possible to fit a line to the data. The delay\n    // trend can be seen as an estimate of (send_rate - capacity)/capacity.\n    // 0 < trend < 1   ->  the delay increases, queues are filling up\n    //   trend == 0    ->  the delay does not change\n    //   trend < 0     ->  the delay decreases, queues are being emptied\n    trend = LinearFitSlope(delay_hist_).value_or(trend);\n    if (settings_.enable_cap) {\n      absl::optional<double> cap = ComputeSlopeCap(delay_hist_, settings_);\n      // We only use the cap to filter out overuse detections, not\n      // to detect additional underuses.\n      if (trend >= 0 && cap.has_value() && trend > cap.value()) {\n        trend = cap.value();\n      }\n    }\n  }\n  // BWE_TEST_LOGGING_PLOT(1, \"trendline_slope\", arrival_time_ms, trend);\n\n  Detect(trend, send_delta_ms, arrival_time_ms);\n}\n\nvoid TrendlineEstimator::Update(double recv_delta_ms,\n                                double send_delta_ms,\n                                int64_t send_time_ms,\n                                int64_t arrival_time_ms,\n                                bool calculated_deltas) {\n  if (calculated_deltas) {\n    UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms);\n  }\n  if (network_state_predictor_) {\n    hypothesis_predicted_ = network_state_predictor_->Update(\n        send_time_ms, arrival_time_ms, hypothesis_);\n  }\n}\n\nBandwidthUsage TrendlineEstimator::State() const {\n  return network_state_predictor_ ? hypothesis_predicted_ : hypothesis_;\n}\n\nvoid TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) {\n  if (num_of_deltas_ < 2) {\n    hypothesis_ = BandwidthUsage::kBwNormal;\n    return;\n  }\n  const double modified_trend =\n      std::min(num_of_deltas_, kMinNumDeltas) * trend * threshold_gain_;\n  prev_modified_trend_ = modified_trend;\n  // BWE_TEST_LOGGING_PLOT(1, \"T\", now_ms, modified_trend);\n  // BWE_TEST_LOGGING_PLOT(1, \"threshold\", now_ms, threshold_);\n  if (modified_trend > threshold_) {\n    if (time_over_using_ == -1) {\n      // Initialize the timer. Assume that we've been\n      // over-using half of the time since the previous\n      // sample.\n      time_over_using_ = ts_delta / 2;\n    } else {\n      // Increment timer\n      time_over_using_ += ts_delta;\n    }\n    overuse_counter_++;\n    if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {\n      if (trend >= prev_trend_) {\n        time_over_using_ = 0;\n        overuse_counter_ = 0;\n        hypothesis_ = BandwidthUsage::kBwOverusing;\n        MS_DEBUG_DEV(\"hypothesis_: BandwidthUsage::kBwOverusing\");\n\n#if MS_LOG_DEV_LEVEL == 3\n        for (auto& packetTiming : delay_hist_) {\n          MS_DEBUG_DEV(\n            \"packetTiming [arrival_time_ms:%f, smoothed_delay_ms:%f, raw_delay_ms:%f\",\n            packetTiming.arrival_time_ms, packetTiming.smoothed_delay_ms, packetTiming.raw_delay_ms\n          );\n        }\n#endif\n      }\n    }\n  } else if (modified_trend < -threshold_) {\n    time_over_using_ = -1;\n    overuse_counter_ = 0;\n    hypothesis_ = BandwidthUsage::kBwUnderusing;\n    MS_DEBUG_DEV(\"---- BandwidthUsage::kBwUnderusing ---\");\n  } else {\n    time_over_using_ = -1;\n    overuse_counter_ = 0;\n    hypothesis_ = BandwidthUsage::kBwNormal;\n    MS_DEBUG_DEV(\"---- BandwidthUsage::kBwNormal ---\");\n  }\n  prev_trend_ = trend;\n  UpdateThreshold(modified_trend, now_ms);\n}\n\nvoid TrendlineEstimator::UpdateThreshold(double modified_trend,\n                                         int64_t now_ms) {\n  if (last_update_ms_ == -1)\n    last_update_ms_ = now_ms;\n\n  if (fabs(modified_trend) > threshold_ + kMaxAdaptOffsetMs) {\n    // Avoid adapting the threshold to big latency spikes, caused e.g.,\n    // by a sudden capacity drop.\n    last_update_ms_ = now_ms;\n    return;\n  }\n\n  const double k = fabs(modified_trend) < threshold_ ? k_down_ : k_up_;\n  const int64_t kMaxTimeDeltaMs = 100;\n  int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);\n  threshold_ += k * (fabs(modified_trend) - threshold_) * time_delta_ms;\n  threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);\n  last_update_ms_ = now_ms;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_\n#define MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include <deque>\n#include <memory>\n#include <utility>\n\n#include \"api/network_state_predictor.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"modules/congestion_controller/goog_cc/delay_increase_detector_interface.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/constructor_magic.h\"\n\nnamespace webrtc {\n\nstruct TrendlineEstimatorSettings {\n  static constexpr unsigned kDefaultTrendlineWindowSize = 20;\n\n  // Sort the packets in the window. Should be redundant,\n  // but then almost no cost.\n  bool enable_sort = false;\n\n  // Cap the trendline slope based on the minimum delay seen\n  // in the beginning_packets and end_packets respectively.\n  bool enable_cap = false;\n  unsigned beginning_packets = 7;\n  unsigned end_packets = 7;\n  double cap_uncertainty = 0.0;\n\n  // Size (in packets) of the window.\n  unsigned window_size = kDefaultTrendlineWindowSize;\n};\n\nclass TrendlineEstimator : public DelayIncreaseDetectorInterface {\n public:\n  TrendlineEstimator(NetworkStatePredictor* network_state_predictor);\n\n  ~TrendlineEstimator() override;\n\n  TrendlineEstimator(const TrendlineEstimator&) = delete;\n  TrendlineEstimator& operator=(const TrendlineEstimator&) = delete;\n\n  // Update the estimator with a new sample. The deltas should represent deltas\n  // between timestamp groups as defined by the InterArrival class.\n  void Update(double recv_delta_ms,\n              double send_delta_ms,\n              int64_t send_time_ms,\n              int64_t arrival_time_ms,\n              bool calculated_deltas) override;\n\n  void UpdateTrendline(double recv_delta_ms,\n                       double send_delta_ms,\n                       int64_t send_time_ms,\n                       int64_t arrival_time_ms);\n\n  BandwidthUsage State() const override;\n\n  struct PacketTiming {\n    PacketTiming(double arrival_time_ms,\n                 double smoothed_delay_ms,\n                 double raw_delay_ms)\n        : arrival_time_ms(arrival_time_ms),\n          smoothed_delay_ms(smoothed_delay_ms),\n          raw_delay_ms(raw_delay_ms) {}\n    double arrival_time_ms;\n    double smoothed_delay_ms;\n    double raw_delay_ms;\n  };\n\n private:\n  friend class GoogCcStatePrinter;\n  void Detect(double trend, double ts_delta, int64_t now_ms);\n\n  void UpdateThreshold(double modified_offset, int64_t now_ms);\n\n  // Parameters.\n  TrendlineEstimatorSettings settings_;\n  const double smoothing_coef_;\n  const double threshold_gain_;\n  // Used by the existing threshold.\n  int num_of_deltas_;\n  // Keep the arrival times small by using the change from the first packet.\n  int64_t first_arrival_time_ms_;\n  // Exponential backoff filtering.\n  double accumulated_delay_;\n  double smoothed_delay_;\n  // Linear least squares regression.\n  std::deque<PacketTiming> delay_hist_;\n\n  const double k_up_;\n  const double k_down_;\n  double overusing_time_threshold_;\n  double threshold_;\n  double prev_modified_trend_;\n  int64_t last_update_ms_;\n  double prev_trend_;\n  double time_over_using_;\n  int overuse_counter_;\n  BandwidthUsage hypothesis_;\n  BandwidthUsage hypothesis_predicted_;\n  NetworkStatePredictor* network_state_predictor_;\n};\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/control_handler.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::CongestionControlHandler\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/rtp/control_handler.h\"\n\n#include \"api/units/data_rate.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n#include \"rtc_base/numerics/safe_minmax.h\"\n#include \"system_wrappers/source/field_trial.h\"\n\n#include \"Logger.hpp\"\n\n#include <algorithm>\n#include <vector>\n\nnamespace webrtc {\n\nvoid CongestionControlHandler::SetTargetRate(\n    TargetTransferRate new_target_rate) {\n  last_incoming_ = new_target_rate;\n}\n\nvoid CongestionControlHandler::SetNetworkAvailability(bool network_available) {\n  network_available_ = network_available;\n}\n\nabsl::optional<TargetTransferRate> CongestionControlHandler::GetUpdate() {\n  if (!last_incoming_.has_value())\n    return absl::nullopt;\n  TargetTransferRate new_outgoing = *last_incoming_;\n  DataRate log_target_rate = new_outgoing.target_rate;\n  bool pause_encoding = false;\n  if (!network_available_)\n    pause_encoding = true;\n  if (pause_encoding)\n    new_outgoing.target_rate = DataRate::Zero();\n  if (!last_reported_ ||\n      last_reported_->target_rate != new_outgoing.target_rate ||\n      (!new_outgoing.target_rate.IsZero() &&\n       (last_reported_->network_estimate.loss_rate_ratio !=\n            new_outgoing.network_estimate.loss_rate_ratio ||\n        last_reported_->network_estimate.round_trip_time !=\n            new_outgoing.network_estimate.round_trip_time))) {\n    if (encoder_paused_in_last_report_ != pause_encoding)\n      MS_DEBUG_TAG(bwe, \"Bitrate estimate state changed, BWE: %s\",\n                       ToString(log_target_rate).c_str());\n    encoder_paused_in_last_report_ = pause_encoding;\n    last_reported_ = new_outgoing;\n    return new_outgoing;\n  }\n  return absl::nullopt;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/control_handler.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_\n#define MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_size.h\"\n#include \"api/units/time_delta.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n\nnamespace webrtc {\n// This is used to observe the network controller state and route calls to\n// the proper handler. It also keeps cached values for safe asynchronous use.\n// This makes sure that things running on the worker queue can't access state\n// in RtpTransportControllerSend, which would risk causing data race on\n// destruction unless members are properly ordered.\nclass CongestionControlHandler {\n public:\n  CongestionControlHandler() = default;\n  ~CongestionControlHandler() = default;\n\n  void SetTargetRate(TargetTransferRate new_target_rate);\n  void SetNetworkAvailability(bool network_available);\n  absl::optional<TargetTransferRate> GetUpdate();\n\n private:\n  absl::optional<TargetTransferRate> last_incoming_;\n  absl::optional<TargetTransferRate> last_reported_;\n  bool network_available_ = true;\n  bool encoder_paused_in_last_report_ = false;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(CongestionControlHandler);\n};\n}  // namespace webrtc\n#endif  // MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/send_time_history.cc",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::SendTimeHistory\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/rtp/send_time_history.h\"\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n\n#include \"Logger.hpp\"\n\n#include <algorithm>\n#include <utility>\n\nnamespace webrtc {\n\nSendTimeHistory::SendTimeHistory(int64_t packet_age_limit_ms)\n    : packet_age_limit_ms_(packet_age_limit_ms) {}\n\nSendTimeHistory::~SendTimeHistory() {}\n\nvoid SendTimeHistory::RemoveOld(int64_t at_time_ms) {\n  while (!history_.empty() &&\n         at_time_ms - history_.begin()->second.creation_time_ms >\n             packet_age_limit_ms_) {\n    // TODO(sprang): Warn if erasing (too many) old items?\n    RemovePacketBytes(history_.begin()->second);\n    history_.erase(history_.begin());\n  }\n}\n\nvoid SendTimeHistory::AddNewPacket(PacketFeedback packet) {\n  packet.long_sequence_number =\n      seq_num_unwrapper_.Unwrap(packet.sequence_number);\n  history_.insert(std::make_pair(packet.long_sequence_number, packet));\n  if (packet.send_time_ms >= 0) {\n    AddPacketBytes(packet);\n    last_send_time_ms_ = std::max(last_send_time_ms_, packet.send_time_ms);\n  }\n}\n\nvoid SendTimeHistory::AddUntracked(size_t packet_size, int64_t send_time_ms) {\n  if (send_time_ms < last_send_time_ms_) {\n    MS_WARN_TAG(bwe, \"ignoring untracked data for out of order packet\");\n  }\n  pending_untracked_size_ += packet_size;\n  last_untracked_send_time_ms_ =\n      std::max(last_untracked_send_time_ms_, send_time_ms);\n}\n\nSendTimeHistory::Status SendTimeHistory::OnSentPacket(uint16_t sequence_number,\n                                                      int64_t send_time_ms) {\n  int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(sequence_number);\n  auto it = history_.find(unwrapped_seq_num);\n  if (it == history_.end())\n    return Status::kNotAdded;\n  bool packet_retransmit = it->second.send_time_ms >= 0;\n  it->second.send_time_ms = send_time_ms;\n  last_send_time_ms_ = std::max(last_send_time_ms_, send_time_ms);\n  if (!packet_retransmit)\n    AddPacketBytes(it->second);\n  if (pending_untracked_size_ > 0) {\n    if (send_time_ms < last_untracked_send_time_ms_) {\n      MS_WARN_TAG(bwe,\n          \"appending acknowledged data for out of order packet.\"\n          \" (Diff:%\" PRIi64 \" ms)\",\n          last_untracked_send_time_ms_ - send_time_ms);\n    }\n    it->second.unacknowledged_data += pending_untracked_size_;\n    pending_untracked_size_ = 0;\n  }\n  return packet_retransmit ? Status::kDuplicate : Status::kOk;\n}\n\nabsl::optional<PacketFeedback> SendTimeHistory::GetPacket(\n    uint16_t sequence_number) const {\n  int64_t unwrapped_seq_num =\n      seq_num_unwrapper_.UnwrapWithoutUpdate(sequence_number);\n  absl::optional<PacketFeedback> optional_feedback;\n  auto it = history_.find(unwrapped_seq_num);\n  if (it != history_.end())\n    optional_feedback.emplace(it->second);\n  return optional_feedback;\n}\n\nbool SendTimeHistory::GetFeedback(PacketFeedback* packet_feedback,\n                                  bool remove) {\n  // RTC_DCHECK(packet_feedback);\n  int64_t unwrapped_seq_num =\n      seq_num_unwrapper_.Unwrap(packet_feedback->sequence_number);\n  UpdateAckedSeqNum(unwrapped_seq_num);\n  // RTC_DCHECK_GE(*last_ack_seq_num_, 0);\n  auto it = history_.find(unwrapped_seq_num);\n  if (it == history_.end())\n    return false;\n\n  // Save arrival_time not to overwrite it.\n  int64_t arrival_time_ms = packet_feedback->arrival_time_ms;\n  *packet_feedback = it->second;\n  packet_feedback->arrival_time_ms = arrival_time_ms;\n\n  if (remove)\n    history_.erase(it);\n  return true;\n}\n\nDataSize SendTimeHistory::GetOutstandingData(uint16_t local_net_id,\n                                             uint16_t remote_net_id) const {\n  auto it = in_flight_bytes_.find({local_net_id, remote_net_id});\n  if (it != in_flight_bytes_.end()) {\n    return DataSize::bytes(it->second);\n  } else {\n    return DataSize::Zero();\n  }\n}\n\nabsl::optional<int64_t> SendTimeHistory::GetFirstUnackedSendTime() const {\n  if (!last_ack_seq_num_)\n    return absl::nullopt;\n  auto it = history_.find(*last_ack_seq_num_);\n  if (it == history_.end() ||\n      it->second.send_time_ms == PacketFeedback::kNoSendTime)\n    return absl::nullopt;\n  return it->second.send_time_ms;\n}\n\nvoid SendTimeHistory::AddPacketBytes(const PacketFeedback& packet) {\n  if (packet.send_time_ms < 0 || packet.payload_size == 0 ||\n      (last_ack_seq_num_ && *last_ack_seq_num_ >= packet.long_sequence_number))\n    return;\n  auto it = in_flight_bytes_.find({packet.local_net_id, packet.remote_net_id});\n  if (it != in_flight_bytes_.end()) {\n    it->second += packet.payload_size;\n  } else {\n    in_flight_bytes_[{packet.local_net_id, packet.remote_net_id}] =\n        packet.payload_size;\n  }\n}\n\nvoid SendTimeHistory::RemovePacketBytes(const PacketFeedback& packet) {\n  if (packet.send_time_ms < 0 || packet.payload_size == 0 ||\n      (last_ack_seq_num_ && *last_ack_seq_num_ >= packet.long_sequence_number))\n    return;\n  auto it = in_flight_bytes_.find({packet.local_net_id, packet.remote_net_id});\n  if (it != in_flight_bytes_.end()) {\n    it->second -= packet.payload_size;\n    if (it->second == 0)\n      in_flight_bytes_.erase(it);\n  }\n}\n\nvoid SendTimeHistory::UpdateAckedSeqNum(int64_t acked_seq_num) {\n  if (last_ack_seq_num_ && *last_ack_seq_num_ >= acked_seq_num)\n    return;\n\n  auto unacked_it = history_.begin();\n  if (last_ack_seq_num_)\n    unacked_it = history_.lower_bound(*last_ack_seq_num_);\n\n  auto newly_acked_end = history_.upper_bound(acked_seq_num);\n  for (; unacked_it != newly_acked_end; ++unacked_it) {\n    RemovePacketBytes(unacked_it->second);\n  }\n  last_ack_seq_num_.emplace(acked_seq_num);\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/send_time_history.h",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_\n#define MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_\n\n#include \"api/units/data_size.h\"\n#include \"rtc_base/constructor_magic.h\"\n#include \"modules/include/module_common_types_public.h\"\n\n#include <absl/types/optional.h>\n#include <map>\n#include <utility>\n\nnamespace webrtc {\nstruct PacketFeedback;\n\nclass SendTimeHistory {\n public:\n  enum class Status { kNotAdded, kOk, kDuplicate };\n\n  explicit SendTimeHistory(int64_t packet_age_limit_ms);\n  ~SendTimeHistory();\n\n  // Cleanup old entries, then add new packet info with provided parameters.\n  void RemoveOld(int64_t at_time_ms);\n  void AddNewPacket(PacketFeedback packet);\n\n  void AddUntracked(size_t packet_size, int64_t send_time_ms);\n\n  // Updates packet info identified by |sequence_number| with |send_time_ms|.\n  // Returns a PacketSendState indicating if the packet was not found, sent,\n  // or if it was previously already marked as sent.\n  Status OnSentPacket(uint16_t sequence_number, int64_t send_time_ms);\n\n  // Retrieves packet info identified by |sequence_number|.\n  absl::optional<PacketFeedback> GetPacket(uint16_t sequence_number) const;\n\n  // Look up PacketFeedback for a sent packet, based on the sequence number, and\n  // populate all fields except for arrival_time. The packet parameter must\n  // thus be non-null and have the sequence_number field set.\n  bool GetFeedback(PacketFeedback* packet_feedback, bool remove);\n\n  DataSize GetOutstandingData(uint16_t local_net_id,\n                              uint16_t remote_net_id) const;\n\n  absl::optional<int64_t> GetFirstUnackedSendTime() const;\n\n private:\n  using RemoteAndLocalNetworkId = std::pair<uint16_t, uint16_t>;\n\n  void AddPacketBytes(const PacketFeedback& packet);\n  void RemovePacketBytes(const PacketFeedback& packet);\n  void UpdateAckedSeqNum(int64_t acked_seq_num);\n  const int64_t packet_age_limit_ms_;\n  size_t pending_untracked_size_ = 0;\n  int64_t last_send_time_ms_ = -1;\n  int64_t last_untracked_send_time_ms_ = -1;\n  SequenceNumberUnwrapper seq_num_unwrapper_;\n  std::map<int64_t, PacketFeedback> history_;\n  absl::optional<int64_t> last_ack_seq_num_;\n  std::map<RemoteAndLocalNetworkId, size_t> in_flight_bytes_;\n\n  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendTimeHistory);\n};\n\n}  // namespace webrtc\n#endif  // MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::TransportFeedbackAdapter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/congestion_controller/rtp/transport_feedback_adapter.h\"\n#include \"api/units/timestamp.h\"\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n#include \"system_wrappers/source/field_trial.h\"\n#include \"mediasoup_helpers.h\"\n\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\n#include <stdlib.h>\n#include <algorithm>\n#include <cmath>\n#include <utility>\n\nnamespace webrtc {\nnamespace {\n\nPacketResult NetworkPacketFeedbackFromRtpPacketFeedback(\n    const webrtc::PacketFeedback& pf) {\n  PacketResult feedback;\n  if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived) {\n    feedback.receive_time = Timestamp::PlusInfinity();\n  } else {\n    feedback.receive_time = Timestamp::ms(pf.arrival_time_ms);\n  }\n  feedback.sent_packet.sequence_number = pf.long_sequence_number;\n  feedback.sent_packet.send_time = Timestamp::ms(pf.send_time_ms);\n  feedback.sent_packet.size = DataSize::bytes(pf.payload_size);\n  feedback.sent_packet.pacing_info = pf.pacing_info;\n  feedback.sent_packet.prior_unacked_data =\n      DataSize::bytes(pf.unacknowledged_data);\n  return feedback;\n}\n}  // namespace\nconst int64_t kNoTimestamp = -1;\nconst int64_t kSendTimeHistoryWindowMs = 60000;\n\nTransportFeedbackAdapter::TransportFeedbackAdapter()\n    : allow_duplicates_(field_trial::IsEnabled(\n          \"WebRTC-TransportFeedbackAdapter-AllowDuplicates\")),\n      send_time_history_(kSendTimeHistoryWindowMs),\n      current_offset_ms_(kNoTimestamp),\n      last_timestamp_us_(kNoTimestamp),\n      local_net_id_(0),\n      remote_net_id_(0) {}\n\nTransportFeedbackAdapter::~TransportFeedbackAdapter() {\n}\n\nvoid TransportFeedbackAdapter::AddPacket(const RtpPacketSendInfo& packet_info,\n                                         size_t overhead_bytes,\n                                         Timestamp creation_time) {\n  {\n    PacketFeedback packet_feedback(\n        creation_time.ms(), packet_info.transport_sequence_number,\n        packet_info.length + overhead_bytes, local_net_id_, remote_net_id_,\n        packet_info.pacing_info);\n    if (packet_info.has_rtp_sequence_number) {\n      packet_feedback.ssrc = packet_info.ssrc;\n      packet_feedback.rtp_sequence_number = packet_info.rtp_sequence_number;\n    }\n\n    // MS_NOTE: TODO remove.\n    // MS_DUMP(\"packet_feedback.arrival_time_ms: %\" PRIi64, packet_feedback.arrival_time_ms);\n    // MS_DUMP(\"packet_feedback.send_time_ms: %\" PRIi64, packet_feedback.send_time_ms);\n    // MS_DUMP(\"packet_feedback.sequence_number: %\" PRIu16, packet_feedback.sequence_number);\n    // MS_DUMP(\"packet_feedback.long_sequence_number: %\" PRIi64, packet_feedback.long_sequence_number);\n    // MS_DUMP(\"packet_feedback.payload_size: %zu\", packet_feedback.payload_size);\n    // MS_DUMP(\"packet_feedback.unacknowledged_data: %zu\", packet_feedback.unacknowledged_data);\n    // MS_DUMP(\"packet_feedback.local_net_id: %\" PRIu16, packet_feedback.local_net_id);\n    // MS_DUMP(\"packet_feedback.remote_net_id: %\" PRIu16, packet_feedback.remote_net_id);\n    // MS_DUMP(\"packet_feedback.ssrc: %\" PRIu32, packet_feedback.ssrc.value());\n    // MS_DUMP(\"packet_feedback.rtp_sequence_number: %\" PRIu16, packet_feedback.rtp_sequence_number);\n\n    send_time_history_.RemoveOld(creation_time.ms());\n    send_time_history_.AddNewPacket(std::move(packet_feedback));\n  }\n\n  {\n    for (auto* observer : observers_) {\n      observer->OnPacketAdded(packet_info.ssrc,\n                              packet_info.transport_sequence_number);\n    }\n  }\n}\nabsl::optional<SentPacket> TransportFeedbackAdapter::ProcessSentPacket(\n    const rtc::SentPacket& sent_packet) {\n  // TODO(srte): Only use one way to indicate that packet feedback is used.\n  if (sent_packet.info.included_in_feedback || sent_packet.packet_id != -1) {\n    SendTimeHistory::Status send_status = send_time_history_.OnSentPacket(\n        sent_packet.packet_id, sent_packet.send_time_ms);\n    absl::optional<PacketFeedback> packet;\n    if (allow_duplicates_ ||\n        send_status != SendTimeHistory::Status::kDuplicate) {\n      packet = send_time_history_.GetPacket(sent_packet.packet_id);\n    }\n\n    if (packet) {\n      SentPacket msg;\n      msg.size = DataSize::bytes(packet->payload_size);\n      msg.send_time = Timestamp::ms(packet->send_time_ms);\n      msg.sequence_number = packet->long_sequence_number;\n      msg.prior_unacked_data = DataSize::bytes(packet->unacknowledged_data);\n      msg.data_in_flight =\n          send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);\n      return msg;\n    }\n  } else if (sent_packet.info.included_in_allocation) {\n    send_time_history_.AddUntracked(sent_packet.info.packet_size_bytes,\n                                    sent_packet.send_time_ms);\n  }\n  return absl::nullopt;\n}\n\nabsl::optional<TransportPacketsFeedback>\nTransportFeedbackAdapter::ProcessTransportFeedback(\n    const RTC::RTCP::FeedbackRtpTransportPacket& feedback,\n    Timestamp feedback_receive_time) {\n  DataSize prior_in_flight = GetOutstandingData();\n\n  last_packet_feedback_vector_ =\n      GetPacketFeedbackVector(feedback, feedback_receive_time);\n  {\n    for (auto* observer : observers_) {\n      observer->OnPacketFeedbackVector(last_packet_feedback_vector_);\n    }\n  }\n\n  std::vector<PacketFeedback> feedback_vector = last_packet_feedback_vector_;\n  if (feedback_vector.empty())\n    return absl::nullopt;\n\n  TransportPacketsFeedback msg;\n  for (const PacketFeedback& rtp_feedback : feedback_vector) {\n    if (rtp_feedback.send_time_ms != PacketFeedback::kNoSendTime) {\n      auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback);\n      MS_DEBUG_DEV(\"feedback received for RTP packet: [seq_num: %\" PRIi64 \", send_time:%\" PRIi64 \", size: %lld, feedback.receive_time:%\" PRIi64,\n          feedback.sent_packet.sequence_number,\n          feedback.sent_packet.send_time.ms(),\n          feedback.sent_packet.size.bytes(),\n          feedback.receive_time.ms());\n\n      msg.packet_feedbacks.push_back(feedback);\n    } else if (rtp_feedback.arrival_time_ms == PacketFeedback::kNotReceived) {\n      MS_DEBUG_DEV(\"--- rtp_feedback.arrival_time_ms == PacketFeedback::kNotReceived ---\");\n      msg.sendless_arrival_times.push_back(Timestamp::PlusInfinity());\n    } else {\n      msg.sendless_arrival_times.push_back(\n          Timestamp::ms(rtp_feedback.arrival_time_ms));\n    }\n  }\n  {\n    absl::optional<int64_t> first_unacked_send_time_ms =\n        send_time_history_.GetFirstUnackedSendTime();\n    if (first_unacked_send_time_ms)\n      msg.first_unacked_send_time = Timestamp::ms(*first_unacked_send_time_ms);\n  }\n  msg.feedback_time = feedback_receive_time;\n  msg.prior_in_flight = prior_in_flight;\n  msg.data_in_flight = GetOutstandingData();\n\n  MS_DEBUG_DEV(\"prior_in_flight:%lld, data_in_flight:%lld\", msg.prior_in_flight.bytes(), msg.data_in_flight.bytes());\n  return msg;\n}\n\nDataSize TransportFeedbackAdapter::GetOutstandingData() const {\n  return send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_);\n}\n\nstd::vector<PacketFeedback> TransportFeedbackAdapter::GetPacketFeedbackVector(\n    const RTC::RTCP::FeedbackRtpTransportPacket& feedback,\n    Timestamp feedback_time) {\n  // Add timestamp deltas to a local time base selected on first packet arrival.\n  // This won't be the true time base, but makes it easier to manually inspect\n  // time stamps.\n  if (last_timestamp_us_ == kNoTimestamp) {\n    current_offset_ms_ = feedback_time.ms();\n  } else {\n    current_offset_ms_ +=\n      mediasoup_helpers::FeedbackRtpTransport::GetBaseDeltaUs(&feedback, last_timestamp_us_) / 1000;\n  }\n  last_timestamp_us_ =\n    mediasoup_helpers::FeedbackRtpTransport::GetBaseTimeUs(&feedback);\n\n  std::vector<PacketFeedback> packet_feedback_vector;\n  if (feedback.GetPacketStatusCount() == 0) {\n    MS_WARN_DEV(\"empty transport feedback packet received\");\n    return packet_feedback_vector;\n  }\n  packet_feedback_vector.reserve(feedback.GetPacketStatusCount());\n  {\n    size_t failed_lookups = 0;\n    int64_t offset_us = 0;\n    int64_t timestamp_ms = 0;\n    uint16_t seq_num = feedback.GetBaseSequenceNumber();\n    for (const auto& packet : mediasoup_helpers::FeedbackRtpTransport::GetReceivedPackets(&feedback)) {\n      // Insert into the vector those unreceived packets which precede this\n      // iteration's received packet.\n      for (; seq_num != packet.sequence_number(); ++seq_num) {\n        PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num);\n        // Note: Element not removed from history because it might be reported\n        // as received by another feedback.\n        if (!send_time_history_.GetFeedback(&packet_feedback, false))\n          ++failed_lookups;\n        if (packet_feedback.local_net_id == local_net_id_ &&\n            packet_feedback.remote_net_id == remote_net_id_) {\n          packet_feedback_vector.push_back(packet_feedback);\n        }\n      }\n\n      // Handle this iteration's received packet.\n      offset_us += packet.delta_us();\n      timestamp_ms = current_offset_ms_ + (offset_us / 1000);\n      PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number());\n      if (!send_time_history_.GetFeedback(&packet_feedback, true))\n        ++failed_lookups;\n      if (packet_feedback.local_net_id == local_net_id_ &&\n          packet_feedback.remote_net_id == remote_net_id_) {\n        packet_feedback_vector.push_back(packet_feedback);\n      }\n      ++seq_num;\n    }\n\n    if (failed_lookups > 0) {\n      MS_WARN_DEV(\"failed to lookup send time for %zu\"\n                  \" packet%s, send time history too small?\",\n                  failed_lookups,\n                  (failed_lookups > 1 ? \"s\" : \"\"));\n    }\n  }\n  return packet_feedback_vector;\n}\n\nstd::vector<PacketFeedback>\nTransportFeedbackAdapter::GetTransportFeedbackVector() const {\n  return last_packet_feedback_vector_;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.h",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_\n#define MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_\n\n#include \"api/transport/network_types.h\"\n#include \"modules/congestion_controller/rtp/send_time_history.h\"\n#include \"rtc_base/network/sent_packet.h\"\n\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\n#include <deque>\n#include <vector>\n\nnamespace webrtc {\n\nclass PacketFeedbackObserver;\nstruct RtpPacketSendInfo;\n\nclass TransportFeedbackAdapter {\n public:\n  TransportFeedbackAdapter();\n  virtual ~TransportFeedbackAdapter();\n\n  void AddPacket(const RtpPacketSendInfo& packet_info,\n                 size_t overhead_bytes,\n                 Timestamp creation_time);\n  absl::optional<SentPacket> ProcessSentPacket(\n      const rtc::SentPacket& sent_packet);\n\n  absl::optional<TransportPacketsFeedback> ProcessTransportFeedback(\n      const RTC::RTCP::FeedbackRtpTransportPacket& feedback,\n      Timestamp feedback_time);\n\n  std::vector<PacketFeedback> GetTransportFeedbackVector() const;\n\n  DataSize GetOutstandingData() const;\n\n private:\n  void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback);\n\n  std::vector<PacketFeedback> GetPacketFeedbackVector(\n      const RTC::RTCP::FeedbackRtpTransportPacket& feedback,\n      Timestamp feedback_time);\n\n  const bool allow_duplicates_;\n\n  SendTimeHistory send_time_history_;\n  int64_t current_offset_ms_;\n  int64_t last_timestamp_us_;\n  std::vector<PacketFeedback> last_packet_feedback_vector_;\n  // MS_NOTE: local_net_id_ and remote_net_id_ are not set.\n  uint16_t local_net_id_;\n  uint16_t remote_net_id_;\n\n  std::vector<PacketFeedbackObserver*> observers_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/include/module_common_types_public.h",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_\n#define MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_\n\n#include <absl/types/optional.h>\n#include <limits>\n\nnamespace webrtc {\n\ntemplate <typename U>\ninline bool IsNewer(U value, U prev_value) {\n  static_assert(!std::numeric_limits<U>::is_signed, \"U must be unsigned\");\n  // kBreakpoint is the half-way mark for the type U. For instance, for a\n  // uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000.\n  constexpr U kBreakpoint = (std::numeric_limits<U>::max() >> 1) + 1;\n  // Distinguish between elements that are exactly kBreakpoint apart.\n  // If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true,\n  // IsNewer(t2,t1)=false\n  // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false.\n  if (value - prev_value == kBreakpoint) {\n    return value > prev_value;\n  }\n  return value != prev_value &&\n         static_cast<U>(value - prev_value) < kBreakpoint;\n}\n\n// Utility class to unwrap a number to a larger type. The numbers will never be\n// unwrapped to a negative value.\ntemplate <typename U>\nclass Unwrapper {\n  static_assert(!std::numeric_limits<U>::is_signed, \"U must be unsigned\");\n  static_assert(std::numeric_limits<U>::max() <=\n                    std::numeric_limits<uint32_t>::max(),\n                \"U must not be wider than 32 bits\");\n\n public:\n  // Get the unwrapped value, but don't update the internal state.\n  int64_t UnwrapWithoutUpdate(U value) const {\n    if (!last_value_)\n      return value;\n\n    constexpr int64_t kMaxPlusOne =\n        static_cast<int64_t>(std::numeric_limits<U>::max()) + 1;\n\n    U cropped_last = static_cast<U>(*last_value_);\n    int64_t delta = value - cropped_last;\n    if (IsNewer(value, cropped_last)) {\n      if (delta < 0)\n        delta += kMaxPlusOne;  // Wrap forwards.\n    } else if (delta > 0 && (*last_value_ + delta - kMaxPlusOne) >= 0) {\n      // If value is older but delta is positive, this is a backwards\n      // wrap-around. However, don't wrap backwards past 0 (unwrapped).\n      delta -= kMaxPlusOne;\n    }\n\n    return *last_value_ + delta;\n  }\n\n  // Only update the internal state to the specified last (unwrapped) value.\n  void UpdateLast(int64_t last_value) { last_value_ = last_value; }\n\n  // Unwrap the value and update the internal state.\n  int64_t Unwrap(U value) {\n    int64_t unwrapped = UnwrapWithoutUpdate(value);\n    UpdateLast(unwrapped);\n    return unwrapped;\n  }\n\n private:\n  absl::optional<int64_t> last_value_;\n};\n\nusing SequenceNumberUnwrapper = Unwrapper<uint16_t>;\nusing TimestampUnwrapper = Unwrapper<uint32_t>;\n\n// NB: Doesn't fulfill strict weak ordering requirements.\n//     Mustn't be used as std::map Compare function.\ninline bool IsNewerSequenceNumber(uint16_t sequence_number,\n                                  uint16_t prev_sequence_number) {\n  return IsNewer(sequence_number, prev_sequence_number);\n}\n\n// NB: Doesn't fulfill strict weak ordering requirements.\n//     Mustn't be used as std::map Compare function.\ninline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) {\n  return IsNewer(timestamp, prev_timestamp);\n}\n\ninline uint16_t LatestSequenceNumber(uint16_t sequence_number1,\n                                     uint16_t sequence_number2) {\n  return IsNewerSequenceNumber(sequence_number1, sequence_number2)\n             ? sequence_number1\n             : sequence_number2;\n}\n\ninline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) {\n  return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2;\n}\n\n}  // namespace webrtc\n#endif  // MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/bitrate_prober.cc",
    "content": "/*\n *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::BitrateProber\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/pacing/bitrate_prober.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n\n// TODO: Remove.\n#define TODO_PRINT_PROBING_STATE() \\\n  switch (probing_state_) \\\n  { \\\n    case ProbingState::kDisabled: \\\n      MS_DUMP(\"--- probing_state_:kDisabled, clusters_.size():%zu\", clusters_.size()); \\\n      break; \\\n    case ProbingState::kInactive: \\\n      MS_DUMP(\"--- probing_state_:kInactive, clusters_.size():%zu\", clusters_.size()); \\\n      break; \\\n    case ProbingState::kActive: \\\n      MS_DUMP(\"--- probing_state_:kActive, clusters_.size():%zu\", clusters_.size()); \\\n      break; \\\n    case ProbingState::kSuspended: \\\n      MS_DUMP(\"--- probing_state_:kSuspended, clusters_.size():%zu\", clusters_.size()); \\\n      break; \\\n  }\n#undef TODO_PRINT_PROBING_STATE\n  #define TODO_PRINT_PROBING_STATE() {}\n\n\nnamespace webrtc {\n\nnamespace {\n// The min probe packet size is scaled with the bitrate we're probing at.\n// This defines the max min probe packet size, meaning that on high bitrates\n// we have a min probe packet size of 200 bytes.\nconstexpr size_t kMinProbePacketSize = 200;\n\nconstexpr int64_t kProbeClusterTimeoutMs = 5000;\n\n}  // namespace\n\nBitrateProberConfig::BitrateProberConfig(\n    const WebRtcKeyValueConfig* key_value_config)\n    : min_probe_packets_sent(\"min_probe_packets_sent\", 5),\n      min_probe_delta(\"min_probe_delta\", TimeDelta::ms(1)),\n      min_probe_duration(\"min_probe_duration\", TimeDelta::ms(15)),\n      max_probe_delay(\"max_probe_delay\", TimeDelta::ms(3)) {\n  ParseFieldTrial({&min_probe_packets_sent, &min_probe_delta,\n                   &min_probe_duration, &max_probe_delay},\n                  key_value_config->Lookup(\"WebRTC-Bwe-ProbingConfiguration\"));\n  ParseFieldTrial({&min_probe_packets_sent, &min_probe_delta,\n                   &min_probe_duration, &max_probe_delay},\n                  key_value_config->Lookup(\"WebRTC-Bwe-ProbingBehavior\"));\n}\n\nBitrateProber::~BitrateProber() {\n  // RTC_HISTOGRAM_COUNTS_1000(\"WebRTC.BWE.Probing.TotalProbeClustersRequested\",\n                            // total_probe_count_);\n  // RTC_HISTOGRAM_COUNTS_1000(\"WebRTC.BWE.Probing.TotalFailedProbeClusters\",\n                            // total_failed_probe_count_);\n}\n\nBitrateProber::BitrateProber(const WebRtcKeyValueConfig& field_trials)\n    : probing_state_(ProbingState::kDisabled),\n      next_probe_time_ms_(-1),\n      total_probe_count_(0),\n      total_failed_probe_count_(0),\n      config_(&field_trials) {\n  SetEnabled(true);\n\n  // TODO: Remove.\n  TODO_PRINT_PROBING_STATE();\n}\n\nvoid BitrateProber::SetEnabled(bool enable) {\n  if (enable) {\n    if (probing_state_ == ProbingState::kDisabled) {\n      probing_state_ = ProbingState::kInactive;\n      MS_DEBUG_TAG(bwe, \"Bandwidth probing enabled, set to inactive\");\n    }\n  } else {\n    probing_state_ = ProbingState::kDisabled;\n    MS_DEBUG_TAG(bwe, \"Bandwidth probing disabled\");\n  }\n\n  // TODO: Remove.\n  TODO_PRINT_PROBING_STATE();\n}\n\nbool BitrateProber::IsProbing() const {\n  return probing_state_ == ProbingState::kActive;\n}\n\nvoid BitrateProber::OnIncomingPacket(size_t packet_size) {\n  // Don't initialize probing unless we have something large enough to start\n  // probing.\n  if (probing_state_ == ProbingState::kInactive && !clusters_.empty() &&\n      packet_size >=\n          std::min<size_t>(RecommendedMinProbeSize(), kMinProbePacketSize)) {\n    // Send next probe right away.\n    next_probe_time_ms_ = -1;\n    probing_state_ = ProbingState::kActive;\n  }\n\n  // TODO: Remove.\n  TODO_PRINT_PROBING_STATE();\n}\n\nvoid BitrateProber::CreateProbeCluster(int bitrate_bps,\n                                       int64_t now_ms,\n                                       int cluster_id) {\n  // RTC_DCHECK(probing_state_ != ProbingState::kDisabled);\n  // RTC_DCHECK_GT(bitrate_bps, 0);\n  if (probing_state_ == ProbingState::kDisabled) {\n    MS_ERROR(\"probing disabled\");\n    return;\n  }\n  if (bitrate_bps <= 0) {\n    MS_ERROR(\"bitrate must be > 0\");\n    return;\n  }\n\n  total_probe_count_++;\n  while (!clusters_.empty() &&\n         now_ms - clusters_.front().time_created_ms > kProbeClusterTimeoutMs) {\n    clusters_.pop();\n    total_failed_probe_count_++;\n  }\n\n  ProbeCluster cluster;\n  cluster.time_created_ms = now_ms;\n  cluster.pace_info.probe_cluster_min_probes = config_.min_probe_packets_sent;\n  cluster.pace_info.probe_cluster_min_bytes =\n      static_cast<int32_t>(static_cast<int64_t>(bitrate_bps) *\n                           config_.min_probe_duration->ms() / 8000);\n\n  // RTC_DCHECK_GE(cluster.pace_info.probe_cluster_min_bytes, 0);\n  if (cluster.pace_info.probe_cluster_min_bytes < 0) {\n    MS_ERROR(\"cluster min bytes must be >= 0\");\n    return;\n  }\n\n  cluster.pace_info.send_bitrate_bps = bitrate_bps;\n  cluster.pace_info.probe_cluster_id = cluster_id;\n  clusters_.push(cluster);\n\n  MS_DEBUG_DEV(\"probe cluster [bitrate:%d, min bytes:%d, min probes:%d]\",\n               cluster.pace_info.send_bitrate_bps,\n               cluster.pace_info.probe_cluster_min_bytes,\n               cluster.pace_info.probe_cluster_min_probes);\n\n  // If we are already probing, continue to do so. Otherwise set it to\n  // kInactive and wait for OnIncomingPacket to start the probing.\n  if (probing_state_ != ProbingState::kActive)\n    probing_state_ = ProbingState::kInactive;\n\n  // TODO (ibc): We need to send probation even if there is no real packets, so add\n  // this code (taken from `OnIncomingPacket()` above) also here.\n  if (probing_state_ == ProbingState::kInactive && !clusters_.empty()) {\n    // Send next probe right away.\n    next_probe_time_ms_ = -1;\n    probing_state_ = ProbingState::kActive;\n  }\n\n  // TODO: Remove.\n  TODO_PRINT_PROBING_STATE();\n}\n\nint BitrateProber::TimeUntilNextProbe(int64_t now_ms) {\n  // TODO: Remove.\n  TODO_PRINT_PROBING_STATE();\n\n  // Probing is not active or probing is already complete.\n  if (probing_state_ != ProbingState::kActive || clusters_.empty())\n    return -1;\n\n  int time_until_probe_ms = 0;\n  if (next_probe_time_ms_ >= 0) {\n    time_until_probe_ms = next_probe_time_ms_ - now_ms;\n    if (time_until_probe_ms < -config_.max_probe_delay->ms()) {\n      MS_WARN_TAG(bwe, \"probe delay too high [next_ms:%\" PRIi64 \", now_ms:%\" PRIi64 \"]\",\n                       next_probe_time_ms_,\n                       now_ms);\n      return -1;\n    }\n  }\n\n  return std::max(time_until_probe_ms, 0);\n}\n\nabsl::optional<PacedPacketInfo> BitrateProber::CurrentCluster() const {\n  // RTC_DCHECK(!clusters_.empty());\n  // RTC_DCHECK(probing_state_ == ProbingState::kActive);\n  if (clusters_.empty() || probing_state_ != ProbingState::kActive) {\n    return absl::nullopt;\n  }\n\n  return clusters_.front().pace_info;\n}\n\n// Probe size is recommended based on the probe bitrate required. We choose\n// a minimum of twice |kMinProbeDeltaMs| interval to allow scheduling to be\n// feasible.\nsize_t BitrateProber::RecommendedMinProbeSize() const {\n  // RTC_DCHECK(!clusters_.empty());\n  if (clusters_.empty()) {\n    return 0;\n  }\n\n  return clusters_.front().pace_info.send_bitrate_bps * 2 *\n         config_.min_probe_delta->ms() / (8 * 1000);\n}\n\nvoid BitrateProber::ProbeSent(int64_t now_ms, size_t bytes) {\n  // RTC_DCHECK(probing_state_ == ProbingState::kActive);\n  // RTC_DCHECK_GT(bytes, 0);\n  if (probing_state_ != ProbingState::kActive) {\n    MS_ERROR(\"probing not active\");\n    return;\n  }\n  if (bytes <= 0) {\n    MS_ERROR(\"bytes must be > 0\");\n    return;\n  }\n\n  if (!clusters_.empty()) {\n    ProbeCluster* cluster = &clusters_.front();\n    if (cluster->sent_probes == 0) {\n      // RTC_DCHECK_EQ(cluster->time_started_ms, -1);\n      if (cluster->time_started_ms != -1) {\n        MS_ERROR(\"cluster started time must be -1\");\n        return;\n      }\n\n      cluster->time_started_ms = now_ms;\n    }\n    cluster->sent_bytes += static_cast<int>(bytes);\n    cluster->sent_probes += 1;\n    next_probe_time_ms_ = GetNextProbeTime(*cluster);\n    if (cluster->sent_bytes >= cluster->pace_info.probe_cluster_min_bytes &&\n        cluster->sent_probes >= cluster->pace_info.probe_cluster_min_probes) {\n      // RTC_HISTOGRAM_COUNTS_100000(\"WebRTC.BWE.Probing.ProbeClusterSizeInBytes\",\n                                  // cluster->sent_bytes);\n      // RTC_HISTOGRAM_COUNTS_100(\"WebRTC.BWE.Probing.ProbesPerCluster\",\n                               // cluster->sent_probes);\n      // RTC_HISTOGRAM_COUNTS_10000(\"WebRTC.BWE.Probing.TimePerProbeCluster\",\n                                 // now_ms - cluster->time_started_ms);\n\n      clusters_.pop();\n    }\n    if (clusters_.empty())\n      probing_state_ = ProbingState::kSuspended;\n\n    // TODO: Remove.\n    TODO_PRINT_PROBING_STATE();\n  }\n}\n\nint64_t BitrateProber::GetNextProbeTime(const ProbeCluster& cluster) {\n  // RTC_CHECK_GT(cluster.pace_info.send_bitrate_bps, 0);\n  // RTC_CHECK_GE(cluster.time_started_ms, 0);\n  MS_ASSERT(cluster.pace_info.send_bitrate_bps > 0, \"cluster.pace_info.send_bitrate_bps must be > 0\");\n  MS_ASSERT(cluster.time_started_ms > 0, \"cluster.time_started_ms must be > 0\");\n\n  // Compute the time delta from the cluster start to ensure probe bitrate stays\n  // close to the target bitrate. Result is in milliseconds.\n  int64_t delta_ms =\n      (8000ll * cluster.sent_bytes + cluster.pace_info.send_bitrate_bps / 2) /\n      cluster.pace_info.send_bitrate_bps;\n  return cluster.time_started_ms + delta_ms;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/bitrate_prober.h",
    "content": "/*\n *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_PACING_BITRATE_PROBER_H_\n#define MODULES_PACING_BITRATE_PROBER_H_\n\n#include \"api/transport/field_trial_based_config.h\"\n#include \"api/transport/network_types.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <queue>\n\nnamespace webrtc {\n\nstruct BitrateProberConfig {\n  explicit BitrateProberConfig(const WebRtcKeyValueConfig* key_value_config);\n  BitrateProberConfig(const BitrateProberConfig&) = default;\n  BitrateProberConfig& operator=(const BitrateProberConfig&) = default;\n  ~BitrateProberConfig() = default;\n\n  // The minimum number probing packets used.\n  FieldTrialParameter<int> min_probe_packets_sent;\n  // A minimum interval between probes to allow scheduling to be feasible.\n  FieldTrialParameter<TimeDelta> min_probe_delta;\n  // The minimum probing duration.\n  FieldTrialParameter<TimeDelta> min_probe_duration;\n  // Maximum amount of time each probe can be delayed. Probe cluster is reset\n  // and retried from the start when this limit is reached.\n  FieldTrialParameter<TimeDelta> max_probe_delay;\n};\n\n// Note that this class isn't thread-safe by itself and therefore relies\n// on being protected by the caller.\nclass BitrateProber {\n public:\n  explicit BitrateProber(const WebRtcKeyValueConfig& field_trials);\n  ~BitrateProber();\n\n  void SetEnabled(bool enable);\n\n  // Returns true if the prober is in a probing session, i.e., it currently\n  // wants packets to be sent out according to the time returned by\n  // TimeUntilNextProbe().\n  bool IsProbing() const;\n\n  // Initializes a new probing session if the prober is allowed to probe. Does\n  // not initialize the prober unless the packet size is large enough to probe\n  // with.\n  void OnIncomingPacket(size_t packet_size);\n\n  // Create a cluster used to probe for |bitrate_bps| with |num_probes| number\n  // of probes.\n  void CreateProbeCluster(int bitrate_bps, int64_t now_ms, int cluster_id);\n\n  // Returns the number of milliseconds until the next probe should be sent to\n  // get accurate probing.\n  int TimeUntilNextProbe(int64_t now_ms);\n\n  // Information about the current probing cluster.\n  absl::optional<PacedPacketInfo> CurrentCluster() const;\n\n  // Returns the minimum number of bytes that the prober recommends for\n  // the next probe.\n  size_t RecommendedMinProbeSize() const;\n\n  // Called to report to the prober that a probe has been sent. In case of\n  // multiple packets per probe, this call would be made at the end of sending\n  // the last packet in probe. |probe_size| is the total size of all packets\n  // in probe.\n  void ProbeSent(int64_t now_ms, size_t probe_size);\n\n private:\n  enum class ProbingState {\n    // Probing will not be triggered in this state at all times.\n    kDisabled,\n    // Probing is enabled and ready to trigger on the first packet arrival.\n    kInactive,\n    // Probe cluster is filled with the set of data rates to be probed and\n    // probes are being sent.\n    kActive,\n    // Probing is enabled, but currently suspended until an explicit trigger\n    // to start probing again.\n    kSuspended,\n  };\n\n  // A probe cluster consists of a set of probes. Each probe in turn can be\n  // divided into a number of packets to accommodate the MTU on the network.\n  struct ProbeCluster {\n    PacedPacketInfo pace_info;\n\n    int sent_probes = 0;\n    int sent_bytes = 0;\n    int64_t time_created_ms = -1;\n    int64_t time_started_ms = -1;\n    int retries = 0;\n  };\n\n  int64_t GetNextProbeTime(const ProbeCluster& cluster);\n\n  ProbingState probing_state_;\n\n  // Probe bitrate per packet. These are used to compute the delta relative to\n  // the previous probe packet based on the size and time when that packet was\n  // sent.\n  std::queue<ProbeCluster> clusters_;\n\n  // Time the next probe should be sent when in kActive state.\n  int64_t next_probe_time_ms_;\n\n  int total_probe_count_;\n  int total_failed_probe_count_;\n\n  BitrateProberConfig config_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_PACING_BITRATE_PROBER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/interval_budget.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/pacing/interval_budget.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include <algorithm>\n\nnamespace webrtc {\nnamespace {\nconstexpr int64_t kWindowMs = 500;\n}\n\nIntervalBudget::IntervalBudget(int initial_target_rate_kbps)\n    : IntervalBudget(initial_target_rate_kbps, false) {}\n\nIntervalBudget::IntervalBudget(int initial_target_rate_kbps,\n                               bool can_build_up_underuse)\n    : bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) {\n  set_target_rate_kbps(initial_target_rate_kbps);\n}\n\nvoid IntervalBudget::set_target_rate_kbps(int target_rate_kbps) {\n  target_rate_kbps_ = target_rate_kbps;\n  max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8;\n  bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_),\n                              max_bytes_in_budget_);\n}\n\nvoid IntervalBudget::IncreaseBudget(int64_t delta_time_ms) {\n  int64_t bytes = target_rate_kbps_ * delta_time_ms / 8;\n  if (bytes_remaining_ < 0 || can_build_up_underuse_) {\n    // We overused last interval, compensate this interval.\n    bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_);\n  } else {\n    // If we underused last interval we can't use it this interval.\n    bytes_remaining_ = std::min(bytes, max_bytes_in_budget_);\n  }\n}\n\nvoid IntervalBudget::UseBudget(size_t bytes) {\n  bytes_remaining_ = std::max(bytes_remaining_ - static_cast<int>(bytes),\n                              -max_bytes_in_budget_);\n}\n\nsize_t IntervalBudget::bytes_remaining() const {\n  return rtc::saturated_cast<size_t>(std::max<int64_t>(0, bytes_remaining_));\n}\n\ndouble IntervalBudget::budget_ratio() const {\n  if (max_bytes_in_budget_ == 0)\n    return 0.0;\n  return static_cast<double>(bytes_remaining_) / max_bytes_in_budget_;\n}\n\nint IntervalBudget::target_rate_kbps() const {\n  return target_rate_kbps_;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/interval_budget.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_PACING_INTERVAL_BUDGET_H_\n#define MODULES_PACING_INTERVAL_BUDGET_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace webrtc {\n\n// TODO(tschumim): Reflector IntervalBudget so that we can set a under- and\n// over-use budget in ms.\nclass IntervalBudget {\n public:\n  explicit IntervalBudget(int initial_target_rate_kbps);\n  IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse);\n  void set_target_rate_kbps(int target_rate_kbps);\n\n  // TODO(tschumim): Unify IncreaseBudget and UseBudget to one function.\n  void IncreaseBudget(int64_t delta_time_ms);\n  void UseBudget(size_t bytes);\n\n  size_t bytes_remaining() const;\n  double budget_ratio() const;\n  int target_rate_kbps() const;\n\n private:\n  int target_rate_kbps_;\n  int64_t max_bytes_in_budget_;\n  int64_t bytes_remaining_;\n  bool can_build_up_underuse_;\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_PACING_INTERVAL_BUDGET_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/paced_sender.cc",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::PacedSender\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/pacing/paced_sender.h\"\n#include \"modules/pacing/bitrate_prober.h\"\n#include \"modules/pacing/interval_budget.h\"\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n#include \"system_wrappers/source/field_trial.h\" // webrtc::field_trial.\n\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n#include <utility>\n\nnamespace webrtc {\nnamespace {\n// Time limit in milliseconds between packet bursts.\nconst int64_t kDefaultMinPacketLimitMs = 5;\nconst int64_t kCongestedPacketIntervalMs = 500;\nconst int64_t kPausedProcessIntervalMs = kCongestedPacketIntervalMs;\nconst int64_t kMaxElapsedTimeMs = 2000;\n\n// Upper cap on process interval, in case process has not been called in a long\n// time.\nconst int64_t kMaxIntervalTimeMs = 30;\n\n}  // namespace\nconst float PacedSender::kDefaultPaceMultiplier = 2.5f;\n\nPacedSender::PacedSender(PacketRouter* packet_router,\n                         const WebRtcKeyValueConfig* field_trials)\n    : packet_router_(packet_router),\n      fallback_field_trials_(\n          !field_trials ? absl::make_unique<FieldTrialBasedConfig>() : nullptr),\n      field_trials_(field_trials ? field_trials : fallback_field_trials_.get()),\n      min_packet_limit_ms_(\"\", kDefaultMinPacketLimitMs),\n      paused_(false),\n      media_budget_(0),\n      padding_budget_(0),\n      prober_(*field_trials_),\n      probing_send_failure_(false),\n      pacing_bitrate_kbps_(0),\n      time_last_process_us_(DepLibUV::GetTimeUsInt64()),\n      first_sent_packet_ms_(-1),\n      packet_counter_(0),\n      account_for_audio_(false) {\n  ParseFieldTrial({&min_packet_limit_ms_},\n                  webrtc::field_trial::FindFullName(\"WebRTC-Pacer-MinPacketLimitMs\"));\n  UpdateBudgetWithElapsedTime(min_packet_limit_ms_);\n}\n\nvoid PacedSender::CreateProbeCluster(int bitrate_bps, int cluster_id) {\n  // TODO: REMOVE\n  // MS_DEBUG_DEV(\"---- bitrate_bps:%d, cluster_id:%d\", bitrate_bps, cluster_id);\n\n  prober_.CreateProbeCluster(bitrate_bps, DepLibUV::GetTimeMsInt64(), cluster_id);\n}\n\nvoid PacedSender::Pause() {\n  if (!paused_)\n    MS_DEBUG_DEV(\"paused\");\n\n  paused_ = true;\n}\n\nvoid PacedSender::Resume() {\n  if (paused_)\n    MS_DEBUG_DEV(\"resumed\");\n\n  paused_ = false;\n}\n\nvoid PacedSender::SetCongestionWindow(int64_t congestion_window_bytes) {\n  congestion_window_bytes_ = congestion_window_bytes;\n}\n\nvoid PacedSender::UpdateOutstandingData(int64_t outstanding_bytes) {\n  outstanding_bytes_ = outstanding_bytes;\n}\n\nbool PacedSender::Congested() const {\n  if (congestion_window_bytes_ == kNoCongestionWindow)\n    return false;\n  return outstanding_bytes_ >= congestion_window_bytes_;\n}\n\nvoid PacedSender::SetProbingEnabled(bool enabled) {\n  // RTC_CHECK_EQ(0, packet_counter_);\n  if (packet_counter_ != 0) {\n    MS_ERROR(\"packet counter must be 0\");\n    return;\n  }\n\n  prober_.SetEnabled(enabled);\n}\n\nvoid PacedSender::SetPacingRates(uint32_t pacing_rate_bps,\n                                 uint32_t padding_rate_bps) {\n  // RTC_DCHECK(pacing_rate_bps > 0);\n  if (pacing_rate_bps == 0) {\n    MS_ERROR(\"pacing rate must be > 0\");\n    return;\n  }\n\n  pacing_bitrate_kbps_ = pacing_rate_bps / 1000;\n  padding_budget_.set_target_rate_kbps(padding_rate_bps / 1000);\n\n  // TODO: REMOVE\n  // MS_DEBUG_DEV(\"[pacer_updated pacing_kbps:%\" PRIu32 \", padding_budget_kbps:%\" PRIu32 \"]\",\n  //              pacing_bitrate_kbps_,\n  //              padding_rate_bps / 1000);\n}\n\nvoid PacedSender::InsertPacket(size_t bytes) {\n  // RTC_DCHECK(pacing_bitrate_kbps_ > 0)\n  //     << \"SetPacingRate must be called before InsertPacket.\";\n  if (pacing_bitrate_kbps_ <= 0) {\n    MS_ERROR(\"SetPacingRates() must be called before InsertPacket()\");\n    return;\n  }\n\n  prober_.OnIncomingPacket(bytes);\n\n  packet_counter_++;\n\n  // MS_NOTE: Since we don't send media packets within ::Process(),\n  // we use this callback to acknowledge sent packets.\n  OnPacketSent(bytes);\n}\n\nvoid PacedSender::SetAccountForAudioPackets(bool account_for_audio) {\n  account_for_audio_ = account_for_audio;\n}\n\nint64_t PacedSender::TimeUntilNextProcess() {\n  int64_t elapsed_time_us =\n      DepLibUV::GetTimeUsInt64() - time_last_process_us_;\n  int64_t elapsed_time_ms = (elapsed_time_us + 500) / 1000;\n  // When paused we wake up every 500 ms to send a padding packet to ensure\n  // we won't get stuck in the paused state due to no feedback being received.\n  if (paused_)\n    return std::max<int64_t>(kPausedProcessIntervalMs - elapsed_time_ms, 0);\n\n  if (prober_.IsProbing()) {\n    int64_t ret = prober_.TimeUntilNextProbe(DepLibUV::GetTimeMsInt64());\n    if (ret > 0 || (ret == 0 && !probing_send_failure_))\n      return ret;\n  }\n  return std::max<int64_t>(min_packet_limit_ms_ - elapsed_time_ms, 0);\n}\n\nint64_t PacedSender::UpdateTimeAndGetElapsedMs(int64_t now_us) {\n  int64_t elapsed_time_ms = (now_us - time_last_process_us_ + 500) / 1000;\n  time_last_process_us_ = now_us;\n  if (elapsed_time_ms > kMaxElapsedTimeMs) {\n    MS_WARN_TAG(bwe, \"elapsed time (%\" PRIi64 \" ms) longer than expected,\"\n                     \" limiting to %\" PRIi64 \" ms\",\n                        elapsed_time_ms,\n                        kMaxElapsedTimeMs);\n    elapsed_time_ms = kMaxElapsedTimeMs;\n  }\n  return elapsed_time_ms;\n}\n\nvoid PacedSender::Process() {\n  int64_t now_us = DepLibUV::GetTimeUsInt64();\n  int64_t elapsed_time_ms = UpdateTimeAndGetElapsedMs(now_us);\n\n  if (paused_)\n    return;\n\n  if (elapsed_time_ms > 0) {\n    int target_bitrate_kbps = pacing_bitrate_kbps_;\n    media_budget_.set_target_rate_kbps(target_bitrate_kbps);\n    UpdateBudgetWithElapsedTime(elapsed_time_ms);\n  }\n\n  if (!prober_.IsProbing())\n    return;\n\n  PacedPacketInfo pacing_info;\n  absl::optional<size_t> recommended_probe_size;\n\n  pacing_info = prober_.CurrentCluster().value_or(PacedPacketInfo());\n  recommended_probe_size = prober_.RecommendedMinProbeSize();\n\n  size_t bytes_sent = 0;\n  // MS_NOTE: Let's not use a useless vector.\n  RTC::RTP::Packet* padding_packet{ nullptr };\n\n  // Check if we should send padding.\n  while (true)\n  {\n    size_t padding_bytes_to_add =\n      PaddingBytesToAdd(recommended_probe_size, bytes_sent);\n\n    if (padding_bytes_to_add == 0)\n      break;\n\n    // TODO: REMOVE\n    // MS_DEBUG_DEV(\n    //   \"[recommended_probe_size:%zu, padding_bytes_to_add:%zu]\",\n    //   *recommended_probe_size, padding_bytes_to_add);\n\n    padding_packet =\n      packet_router_->GeneratePadding(padding_bytes_to_add);\n\n    // TODO: REMOVE.\n    // MS_DEBUG_DEV(\"sending padding packet [size:%zu]\", padding_packet->GetSize());\n\n    packet_router_->SendPacket(padding_packet, pacing_info);\n    bytes_sent += padding_packet->GetLength();\n\n    if (recommended_probe_size && bytes_sent > *recommended_probe_size)\n      break;\n  }\n\n  if (bytes_sent != 0)\n  {\n    auto now = DepLibUV::GetTimeUsInt64();\n\n    OnPaddingSent(now, bytes_sent);\n    prober_.ProbeSent((now + 500) / 1000, bytes_sent);\n  }\n}\n\nsize_t PacedSender::PaddingBytesToAdd(\n    absl::optional<size_t> recommended_probe_size,\n    size_t bytes_sent) {\n\n  // Don't add padding if congested, even if requested for probing.\n  if (Congested()) {\n    return 0;\n  }\n\n  // MS_NOTE: This does not apply to mediasoup.\n  // We can not send padding unless a normal packet has first been sent. If we\n  // do, timestamps get messed up.\n  // if (packet_counter_ == 0) {\n  //   return 0;\n  // }\n\n  if (recommended_probe_size) {\n    if (*recommended_probe_size > bytes_sent) {\n      return *recommended_probe_size - bytes_sent;\n    }\n    return 0;\n  }\n\n  return padding_budget_.bytes_remaining();\n}\n\nvoid PacedSender::OnPacketSent(size_t size) {\n  if (first_sent_packet_ms_ == -1)\n    first_sent_packet_ms_ = DepLibUV::GetTimeMsInt64();\n\n  // Update media bytes sent.\n  UpdateBudgetWithBytesSent(size);\n}\n\nPacedPacketInfo PacedSender::GetPacingInfo() {\n  PacedPacketInfo pacing_info;\n\n  if (prober_.IsProbing()) {\n    pacing_info = prober_.CurrentCluster().value_or(PacedPacketInfo());;\n  }\n\n  return pacing_info;\n}\n\nvoid PacedSender::OnPaddingSent(int64_t now, size_t bytes_sent) {\n  if (bytes_sent > 0) {\n    UpdateBudgetWithBytesSent(bytes_sent);\n  }\n}\n\nvoid PacedSender::UpdateBudgetWithElapsedTime(int64_t delta_time_ms) {\n  delta_time_ms = std::min(kMaxIntervalTimeMs, delta_time_ms);\n  media_budget_.IncreaseBudget(delta_time_ms);\n  padding_budget_.IncreaseBudget(delta_time_ms);\n}\n\nvoid PacedSender::UpdateBudgetWithBytesSent(size_t bytes_sent) {\n  outstanding_bytes_ += bytes_sent;\n  media_budget_.UseBudget(bytes_sent);\n  padding_budget_.UseBudget(bytes_sent);\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/paced_sender.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_PACING_PACED_SENDER_H_\n#define MODULES_PACING_PACED_SENDER_H_\n\n#include \"api/transport/field_trial_based_config.h\"\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"modules/pacing/bitrate_prober.h\"\n#include \"modules/pacing/interval_budget.h\"\n#include \"modules/pacing/packet_router.h\"\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <atomic>\n#include <memory>\n\nnamespace webrtc {\n\nclass PacedSender {\n public:\n  static constexpr int64_t kNoCongestionWindow = -1;\n\n  // Pacing-rate relative to our target send rate.\n  // Multiplicative factor that is applied to the target bitrate to calculate\n  // the number of bytes that can be transmitted per interval.\n  // Increasing this factor will result in lower delays in cases of bitrate\n  // overshoots from the encoder.\n  static const float kDefaultPaceMultiplier;\n\n  PacedSender(PacketRouter* packet_router,\n              const WebRtcKeyValueConfig* field_trials = nullptr);\n\n  virtual ~PacedSender() = default;\n\n  virtual void CreateProbeCluster(int bitrate_bps, int cluster_id);\n\n  // Temporarily pause all sending.\n  void Pause();\n\n  // Resume sending packets.\n  void Resume();\n\n  void SetCongestionWindow(int64_t congestion_window_bytes);\n  void UpdateOutstandingData(int64_t outstanding_bytes);\n\n  // Enable bitrate probing. Enabled by default, mostly here to simplify\n  // testing. Must be called before any packets are being sent to have an\n  // effect.\n  void SetProbingEnabled(bool enabled);\n\n  // Sets the pacing rates. Must be called once before packets can be sent.\n  void SetPacingRates(uint32_t pacing_rate_bps, uint32_t padding_rate_bps);\n\n  // Adds the packet information to the queue and calls TimeToSendPacket\n  // when it's time to send.\n  // MS_NOTE: defined in \"modules/rtp_rtcp/include/rtp_packet_sender.h\"\n  void InsertPacket(size_t bytes);\n\n  // Currently audio traffic is not accounted by pacer and passed through.\n  // With the introduction of audio BWE audio traffic will be accounted for\n  // the pacer budget calculation. The audio traffic still will be injected\n  // at high priority.\n  void SetAccountForAudioPackets(bool account_for_audio);\n\n  // Returns the number of milliseconds until the module want a worker thread\n  // to call Process.\n  int64_t TimeUntilNextProcess();\n\n  // Process any pending packets in the queue(s).\n  void Process();\n\n  void OnPacketSent(size_t size);\n  PacedPacketInfo GetPacingInfo();\n\n private:\n  int64_t UpdateTimeAndGetElapsedMs(int64_t now_us);\n\n  // Updates the number of bytes that can be sent for the next time interval.\n  void UpdateBudgetWithElapsedTime(int64_t delta_time_in_ms);\n  void UpdateBudgetWithBytesSent(size_t bytes);\n\n  size_t PaddingBytesToAdd(absl::optional<size_t> recommended_probe_size,\n                           size_t bytes_sent);\n\n  void OnPaddingSent(int64_t now_us, size_t bytes_sent);\n\n  bool Congested() const;\n\n  PacketRouter* const packet_router_;\n  const std::unique_ptr<FieldTrialBasedConfig> fallback_field_trials_;\n  const WebRtcKeyValueConfig* field_trials_;\n\n  FieldTrialParameter<int> min_packet_limit_ms_;\n\n  bool paused_;\n  // This is the media budget, keeping track of how many bits of media\n  // we can pace out during the current interval.\n  IntervalBudget media_budget_;\n  // This is the padding budget, keeping track of how many bits of padding we're\n  // allowed to send out during the current interval. This budget will be\n  // utilized when there's no media to send.\n  IntervalBudget padding_budget_;\n\n  BitrateProber prober_;\n  bool probing_send_failure_;\n\n  uint32_t pacing_bitrate_kbps_;\n\n  int64_t time_last_process_us_;\n  int64_t first_sent_packet_ms_;\n\n  uint64_t packet_counter_;\n\n  int64_t congestion_window_bytes_ = kNoCongestionWindow;\n  int64_t outstanding_bytes_ = 0;\n\n  bool account_for_audio_;\n};\n}  // namespace webrtc\n#endif  // MODULES_PACING_PACED_SENDER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/pacing/packet_router.h",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_PACING_PACKET_ROUTER_H_\n#define MODULES_PACING_PACKET_ROUTER_H_\n\n#include \"api/transport/network_types.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include \"RTC/RTP/Packet.hpp\"\n\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace webrtc {\n\n// PacketRouter keeps track of rtp send modules to support the pacer.\n// In addition, it handles feedback messages, which are sent on a send\n// module if possible (sender report), otherwise on receive module\n// (receiver report). For the latter case, we also keep track of the\n// receive modules.\nclass PacketRouter {\n public:\n  PacketRouter() = default;\n  virtual ~PacketRouter() = default;\n\n  virtual void SendPacket(RTC::RTP::Packet* packet,\n                          const PacedPacketInfo& cluster_info) = 0;\n\n  // MS_NOTE: Changed to return a single RTP::Packet pointer (maybe nullptr).\n  virtual RTC::RTP::Packet* GeneratePadding(size_t target_size_bytes) = 0;\n};\n}  // namespace webrtc\n#endif  // MODULES_PACING_PACKET_ROUTER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc",
    "content": "/*\n *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::AimdRateControl\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/remote_bitrate_estimator/aimd_rate_control.h\"\n#include \"api/transport/network_types.h\"\n#include \"api/units/data_rate.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"modules/remote_bitrate_estimator/overuse_detector.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n#include \"rtc_base/numerics/safe_minmax.h\"\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\n#include <inttypes.h>\n#include <algorithm>\n#include <cmath>\n#include <cstdio>\n#include <string>\n\nnamespace webrtc {\nnamespace {\n\nconstexpr TimeDelta kDefaultRtt = TimeDelta::Millis<200>();\nconstexpr double kDefaultBackoffFactor = 0.85;\n\nconstexpr char kBweBackOffFactorExperiment[] = \"WebRTC-BweBackOffFactor\";\n\nbool IsEnabled(const WebRtcKeyValueConfig& field_trials,\n               absl::string_view key) {\n  return field_trials.Lookup(key).find(\"Enabled\") == 0;\n}\n\ndouble ReadBackoffFactor(const WebRtcKeyValueConfig& key_value_config) {\n  std::string experiment_string =\n      key_value_config.Lookup(kBweBackOffFactorExperiment);\n  double backoff_factor;\n  int parsed_values =\n      sscanf(experiment_string.c_str(), \"Enabled-%lf\", &backoff_factor);\n  if (parsed_values == 1) {\n    if (backoff_factor >= 1.0) {\n      MS_WARN_TAG(bwe, \"Back-off factor must be less than 1.\");\n    } else if (backoff_factor <= 0.0) {\n      MS_WARN_TAG(bwe, \"Back-off factor must be greater than 0.\");\n    } else {\n      return backoff_factor;\n    }\n  }\n\n  MS_WARN_TAG(bwe, \"Failed to parse parameters for AimdRateControl experiment from field trial string. Using default.\");\n\n  return kDefaultBackoffFactor;\n}\n\n}  // namespace\n\nAimdRateControl::AimdRateControl(const WebRtcKeyValueConfig* key_value_config)\n    : AimdRateControl(key_value_config, /* send_side =*/false) {}\n\nAimdRateControl::AimdRateControl(const WebRtcKeyValueConfig* key_value_config,\n                                 bool send_side)\n    : min_configured_bitrate_(congestion_controller::GetMinBitrate()),\n      max_configured_bitrate_(DataRate::kbps(30000)),\n      current_bitrate_(max_configured_bitrate_),\n      latest_estimated_throughput_(current_bitrate_),\n      link_capacity_(),\n      rate_control_state_(kRcHold),\n      time_last_bitrate_change_(Timestamp::MinusInfinity()),\n      time_last_bitrate_decrease_(Timestamp::MinusInfinity()),\n      time_first_throughput_estimate_(Timestamp::MinusInfinity()),\n      bitrate_is_initialized_(false),\n      beta_(IsEnabled(*key_value_config, kBweBackOffFactorExperiment)\n                ? ReadBackoffFactor(*key_value_config)\n                : kDefaultBackoffFactor),\n      in_alr_(false),\n      rtt_(kDefaultRtt),\n      send_side_(send_side),\n      in_experiment_(!AdaptiveThresholdExperimentIsDisabled(*key_value_config)),\n      no_bitrate_increase_in_alr_(\n          IsEnabled(*key_value_config,\n                    \"WebRTC-DontIncreaseDelayBasedBweInAlr\")),\n      smoothing_experiment_(false),\n      estimate_bounded_backoff_(\n          IsEnabled(*key_value_config, \"WebRTC-Bwe-EstimateBoundedBackoff\")),\n      estimate_bounded_increase_(\n          IsEnabled(*key_value_config, \"WebRTC-Bwe-EstimateBoundedIncrease\")),\n      initial_backoff_interval_(\"initial_backoff_interval\"),\n      low_throughput_threshold_(\"low_throughput\", DataRate::Zero()),\n      capacity_deviation_ratio_threshold_(\"cap_thr\", 0.2),\n      capacity_limit_deviation_factor_(\"cap_lim\", 1) {\n  // E.g\n  // WebRTC-BweAimdRateControlConfig/initial_backoff_interval:100ms,\n  // low_throughput:50kbps/\n  ParseFieldTrial({&initial_backoff_interval_, &low_throughput_threshold_},\n                  key_value_config->Lookup(\"WebRTC-BweAimdRateControlConfig\"));\n  if (initial_backoff_interval_) {\n    MS_DEBUG_TAG(bwe, \"Using aimd rate control with initial back-off interval: %s\",\n                     ToString(*initial_backoff_interval_).c_str());\n  }\n  MS_DEBUG_TAG(bwe, \"Using aimd rate control with back off factor: %f \", beta_);\n  ParseFieldTrial(\n      {&capacity_deviation_ratio_threshold_, &capacity_limit_deviation_factor_},\n      key_value_config->Lookup(\"WebRTC-Bwe-AimdRateControl-NetworkState\"));\n}\n\nAimdRateControl::~AimdRateControl() {}\n\nvoid AimdRateControl::SetStartBitrate(DataRate start_bitrate) {\n  current_bitrate_ = start_bitrate;\n  latest_estimated_throughput_ = current_bitrate_;\n  bitrate_is_initialized_ = true;\n}\n\nvoid AimdRateControl::SetMinBitrate(DataRate min_bitrate) {\n  MS_DEBUG_DEV(\"[min_bitrate:%\" PRIi64 \"]\", min_bitrate.bps());\n\n  min_configured_bitrate_ = min_bitrate;\n  current_bitrate_ = std::max(min_bitrate, current_bitrate_);\n}\n\nbool AimdRateControl::ValidEstimate() const {\n  return bitrate_is_initialized_;\n}\n\nTimeDelta AimdRateControl::GetFeedbackInterval() const {\n  // Estimate how often we can send RTCP if we allocate up to 5% of bandwidth\n  // to feedback.\n  const DataSize kRtcpSize = DataSize::bytes(80);\n  const DataRate rtcp_bitrate = current_bitrate_ * 0.05;\n  const TimeDelta interval = kRtcpSize / rtcp_bitrate;\n  const TimeDelta kMinFeedbackInterval = TimeDelta::ms(200);\n  const TimeDelta kMaxFeedbackInterval = TimeDelta::ms(1000);\n  return interval.Clamped(kMinFeedbackInterval, kMaxFeedbackInterval);\n}\n\nbool AimdRateControl::TimeToReduceFurther(Timestamp at_time,\n                                          DataRate estimated_throughput) const {\n  const TimeDelta bitrate_reduction_interval =\n      rtt_.Clamped(TimeDelta::ms(10), TimeDelta::ms(200));\n  if (at_time - time_last_bitrate_change_ >= bitrate_reduction_interval) {\n    return true;\n  }\n  if (ValidEstimate()) {\n    // TODO(terelius/holmer): Investigate consequences of increasing\n    // the threshold to 0.95 * LatestEstimate().\n    const DataRate threshold = 0.5 * LatestEstimate();\n    return estimated_throughput < threshold;\n  }\n  return false;\n}\n\nbool AimdRateControl::InitialTimeToReduceFurther(Timestamp at_time) const {\n  if (!initial_backoff_interval_) {\n    return ValidEstimate() &&\n           TimeToReduceFurther(at_time,\n                               LatestEstimate() / 2 - DataRate::bps(1));\n  }\n  // TODO(terelius): We could use the RTT (clamped to suitable limits) instead\n  // of a fixed bitrate_reduction_interval.\n  if (time_last_bitrate_decrease_.IsInfinite() ||\n      at_time - time_last_bitrate_decrease_ >= *initial_backoff_interval_) {\n    return true;\n  }\n  return false;\n}\n\nDataRate AimdRateControl::LatestEstimate() const {\n  return current_bitrate_;\n}\n\nvoid AimdRateControl::SetRtt(TimeDelta rtt) {\n  rtt_ = rtt;\n}\n\nDataRate AimdRateControl::Update(const RateControlInput* input,\n                                 Timestamp at_time) {\n  // RTC_CHECK(input);\n\n  // Set the initial bit rate value to what we're receiving the first half\n  // second.\n  // TODO(bugs.webrtc.org/9379): The comment above doesn't match to the code.\n  if (!bitrate_is_initialized_) {\n    const TimeDelta kInitializationTime = TimeDelta::seconds(5);\n    // RTC_DCHECK_LE(kBitrateWindowMs, kInitializationTime.ms());\n    if (time_first_throughput_estimate_.IsInfinite()) {\n      if (input->estimated_throughput)\n        time_first_throughput_estimate_ = at_time;\n    } else if (at_time - time_first_throughput_estimate_ >\n                   kInitializationTime &&\n               input->estimated_throughput) {\n      current_bitrate_ = *input->estimated_throughput;\n      bitrate_is_initialized_ = true;\n    }\n  }\n\n  current_bitrate_ = ChangeBitrate(current_bitrate_, *input, at_time);\n  return current_bitrate_;\n}\n\nvoid AimdRateControl::SetInApplicationLimitedRegion(bool in_alr) {\n  in_alr_ = in_alr;\n}\n\nvoid AimdRateControl::SetEstimate(DataRate bitrate, Timestamp at_time) {\n  bitrate_is_initialized_ = true;\n  DataRate prev_bitrate = current_bitrate_;\n  current_bitrate_ = ClampBitrate(bitrate, bitrate);\n  time_last_bitrate_change_ = at_time;\n  if (current_bitrate_ < prev_bitrate) {\n    time_last_bitrate_decrease_ = at_time;\n  }\n}\n\nvoid AimdRateControl::SetNetworkStateEstimate(\n    const absl::optional<NetworkStateEstimate>& estimate) {\n  network_estimate_ = estimate;\n}\n\ndouble AimdRateControl::GetNearMaxIncreaseRateBpsPerSecond() const {\n  // RTC_DCHECK(!current_bitrate_.IsZero());\n  const TimeDelta kFrameInterval = TimeDelta::seconds(1) / 30;\n  DataSize frame_size = current_bitrate_ * kFrameInterval;\n  const DataSize kPacketSize = DataSize::bytes(1200);\n  double packets_per_frame = std::ceil(frame_size / kPacketSize);\n  DataSize avg_packet_size = frame_size / packets_per_frame;\n\n  // Approximate the over-use estimator delay to 100 ms.\n  TimeDelta response_time = rtt_ + TimeDelta::ms(100);\n  if (in_experiment_)\n    response_time = response_time * 2;\n  double increase_rate_bps_per_second =\n      (avg_packet_size / response_time).bps<double>();\n  double kMinIncreaseRateBpsPerSecond = 4000;\n  return std::max(kMinIncreaseRateBpsPerSecond, increase_rate_bps_per_second);\n}\n\nTimeDelta AimdRateControl::GetExpectedBandwidthPeriod() const {\n  const TimeDelta kMinPeriod =\n      smoothing_experiment_ ? TimeDelta::ms(500) : TimeDelta::seconds(2);\n  const TimeDelta kDefaultPeriod = TimeDelta::seconds(3);\n  const TimeDelta kMaxPeriod = TimeDelta::seconds(50);\n\n  double increase_rate_bps_per_second = GetNearMaxIncreaseRateBpsPerSecond();\n  if (!last_decrease_)\n    return smoothing_experiment_ ? kMinPeriod : kDefaultPeriod;\n  double time_to_recover_decrease_seconds =\n      last_decrease_->bps() / increase_rate_bps_per_second;\n  TimeDelta period = TimeDelta::seconds(time_to_recover_decrease_seconds);\n  return period.Clamped(kMinPeriod, kMaxPeriod);\n}\n\nDataRate AimdRateControl::ChangeBitrate(DataRate new_bitrate,\n                                        const RateControlInput& input,\n                                        Timestamp at_time) {\n  DataRate estimated_throughput =\n      input.estimated_throughput.value_or(latest_estimated_throughput_);\n  if (input.estimated_throughput)\n    latest_estimated_throughput_ = *input.estimated_throughput;\n\n  // An over-use should always trigger us to reduce the bitrate, even though\n  // we have not yet established our first estimate. By acting on the over-use,\n  // we will end up with a valid estimate.\n  if (!bitrate_is_initialized_ &&\n      input.bw_state != BandwidthUsage::kBwOverusing)\n    return current_bitrate_;\n\n  ChangeState(input, at_time);\n\n  switch (rate_control_state_) {\n    case kRcHold:\n      break;\n\n    case kRcIncrease:\n      if (estimated_throughput > link_capacity_.UpperBound())\n        link_capacity_.Reset();\n\n      // Do not increase the delay based estimate in alr since the estimator\n      // will not be able to get transport feedback necessary to detect if\n      // the new estimate is correct.\n      if (!(send_side_ && in_alr_ && no_bitrate_increase_in_alr_)) {\n        if (link_capacity_.has_estimate()) {\n          // The link_capacity estimate is reset if the measured throughput\n          // is too far from the estimate. We can therefore assume that our\n          // target rate is reasonably close to link capacity and use additive\n          // increase.\n          DataRate additive_increase =\n              AdditiveRateIncrease(at_time, time_last_bitrate_change_);\n          new_bitrate += additive_increase;\n        } else {\n          // If we don't have an estimate of the link capacity, use faster ramp\n          // up to discover the capacity.\n          DataRate multiplicative_increase = MultiplicativeRateIncrease(\n              at_time, time_last_bitrate_change_, new_bitrate);\n          new_bitrate += multiplicative_increase;\n        }\n      }\n\n      time_last_bitrate_change_ = at_time;\n      break;\n\n    case kRcDecrease:\n      // TODO(srte): Remove when |estimate_bounded_backoff_| has been validated.\n      if (network_estimate_ && capacity_deviation_ratio_threshold_ &&\n          !estimate_bounded_backoff_) {\n        estimated_throughput = std::max(estimated_throughput,\n                                        network_estimate_->link_capacity_lower);\n      }\n      if (estimated_throughput > low_throughput_threshold_) {\n        // Set bit rate to something slightly lower than the measured throughput\n        // to get rid of any self-induced delay.\n        new_bitrate = estimated_throughput * beta_;\n        if (new_bitrate > current_bitrate_) {\n          // Avoid increasing the rate when over-using.\n          if (link_capacity_.has_estimate()) {\n            new_bitrate = beta_ * link_capacity_.estimate();\n          }\n        }\n        if (estimate_bounded_backoff_ && network_estimate_) {\n          new_bitrate = std::max(\n              new_bitrate, network_estimate_->link_capacity_lower * beta_);\n        }\n      } else {\n        new_bitrate = estimated_throughput;\n        if (link_capacity_.has_estimate()) {\n          new_bitrate = std::max(new_bitrate, link_capacity_.estimate());\n        }\n        new_bitrate = std::min(new_bitrate, low_throughput_threshold_.Get());\n      }\n      new_bitrate = std::min(new_bitrate, current_bitrate_);\n\n      if (bitrate_is_initialized_ && estimated_throughput < current_bitrate_) {\n        constexpr double kDegradationFactor = 0.9;\n        if (smoothing_experiment_ &&\n            new_bitrate < kDegradationFactor * beta_ * current_bitrate_) {\n          // If bitrate decreases more than a normal back off after overuse, it\n          // indicates a real network degradation. We do not let such a decrease\n          // to determine the bandwidth estimation period.\n          last_decrease_ = absl::nullopt;\n        } else {\n          last_decrease_ = current_bitrate_ - new_bitrate;\n        }\n      }\n      if (estimated_throughput < link_capacity_.LowerBound()) {\n        // The current throughput is far from the estimated link capacity. Clear\n        // the estimate to allow an immediate update in OnOveruseDetected.\n        link_capacity_.Reset();\n      }\n\n      bitrate_is_initialized_ = true;\n      link_capacity_.OnOveruseDetected(estimated_throughput);\n      // Stay on hold until the pipes are cleared.\n      rate_control_state_ = kRcHold;\n      time_last_bitrate_change_ = at_time;\n      time_last_bitrate_decrease_ = at_time;\n      break;\n\n    default:\n      MS_THROW_ERROR(\"unknown rate control state\");\n  }\n  return ClampBitrate(new_bitrate, estimated_throughput);\n}\n\nDataRate AimdRateControl::ClampBitrate(DataRate new_bitrate,\n                                       DataRate estimated_throughput) const {\n  // Allow the estimate to increase as long as alr is not detected to ensure\n  // that there is no BWE values that can make the estimate stuck at a too\n  // low bitrate. If an encoder can not produce the bitrate necessary to\n  // fully use the capacity, alr will sooner or later trigger.\n  if (!(send_side_ && no_bitrate_increase_in_alr_)) {\n    // Don't change the bit rate if the send side is too far off.\n    // We allow a bit more lag at very low rates to not too easily get stuck if\n    // the encoder produces uneven outputs.\n    const DataRate max_bitrate =\n        1.5 * estimated_throughput + DataRate::kbps(10);\n    if (new_bitrate > current_bitrate_ && new_bitrate > max_bitrate) {\n      new_bitrate = std::max(current_bitrate_, max_bitrate);\n    }\n  }\n\n  if (network_estimate_ &&\n      (estimate_bounded_increase_ || capacity_limit_deviation_factor_)) {\n    DataRate upper_bound = network_estimate_->link_capacity_upper;\n    new_bitrate = std::min(new_bitrate, upper_bound);\n  }\n  new_bitrate = std::max(new_bitrate, min_configured_bitrate_);\n  return new_bitrate;\n}\n\nDataRate AimdRateControl::MultiplicativeRateIncrease(\n    Timestamp at_time,\n    Timestamp last_time,\n    DataRate current_bitrate) const {\n  double alpha = 1.08;\n  if (last_time.IsFinite()) {\n    auto time_since_last_update = at_time - last_time;\n    alpha = pow(alpha, std::min(time_since_last_update.seconds<double>(), 1.0));\n  }\n  DataRate multiplicative_increase =\n      std::max(current_bitrate * (alpha - 1.0), DataRate::bps(1000));\n  return multiplicative_increase;\n}\n\nDataRate AimdRateControl::AdditiveRateIncrease(Timestamp at_time,\n                                               Timestamp last_time) const {\n  double time_period_seconds = (at_time - last_time).seconds<double>();\n  double data_rate_increase_bps =\n      GetNearMaxIncreaseRateBpsPerSecond() * time_period_seconds;\n  return DataRate::bps(data_rate_increase_bps);\n}\n\nvoid AimdRateControl::ChangeState(const RateControlInput& input,\n                                  Timestamp at_time) {\n  switch (input.bw_state) {\n    case BandwidthUsage::kBwNormal:\n      if (rate_control_state_ == kRcHold) {\n        time_last_bitrate_change_ = at_time;\n        rate_control_state_ = kRcIncrease;\n      }\n      break;\n    case BandwidthUsage::kBwOverusing:\n      if (rate_control_state_ != kRcDecrease) {\n        rate_control_state_ = kRcDecrease;\n      }\n      break;\n    case BandwidthUsage::kBwUnderusing:\n      rate_control_state_ = kRcHold;\n      break;\n    default:\n      MS_THROW_ERROR(\"unknown input.bw_state\");\n  }\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.h",
    "content": "/*\n *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_\n\n#include \"api/transport/network_types.h\"\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"api/units/data_rate.h\"\n#include \"api/units/timestamp.h\"\n#include \"modules/congestion_controller/goog_cc/link_capacity_estimator.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n\nnamespace webrtc {\n// A rate control implementation based on additive increases of\n// bitrate when no over-use is detected and multiplicative decreases when\n// over-uses are detected. When we think the available bandwidth has changes or\n// is unknown, we will switch to a \"slow-start mode\" where we increase\n// multiplicatively.\nclass AimdRateControl {\n public:\n  explicit AimdRateControl(const WebRtcKeyValueConfig* key_value_config);\n  AimdRateControl(const WebRtcKeyValueConfig* key_value_config, bool send_side);\n  ~AimdRateControl();\n\n  // Returns true if the target bitrate has been initialized. This happens\n  // either if it has been explicitly set via SetStartBitrate/SetEstimate, or if\n  // we have measured a throughput.\n  bool ValidEstimate() const;\n  void SetStartBitrate(DataRate start_bitrate);\n  void SetMinBitrate(DataRate min_bitrate);\n  TimeDelta GetFeedbackInterval() const;\n\n  // Returns true if the bitrate estimate hasn't been changed for more than\n  // an RTT, or if the estimated_throughput is less than half of the current\n  // estimate. Should be used to decide if we should reduce the rate further\n  // when over-using.\n  bool TimeToReduceFurther(Timestamp at_time,\n                           DataRate estimated_throughput) const;\n  // As above. To be used if overusing before we have measured a throughput.\n  bool InitialTimeToReduceFurther(Timestamp at_time) const;\n\n  DataRate LatestEstimate() const;\n  void SetRtt(TimeDelta rtt);\n  DataRate Update(const RateControlInput* input, Timestamp at_time);\n  void SetInApplicationLimitedRegion(bool in_alr);\n  void SetEstimate(DataRate bitrate, Timestamp at_time);\n  void SetNetworkStateEstimate(\n      const absl::optional<NetworkStateEstimate>& estimate);\n\n  // Returns the increase rate when used bandwidth is near the link capacity.\n  double GetNearMaxIncreaseRateBpsPerSecond() const;\n  // Returns the expected time between overuse signals (assuming steady state).\n  TimeDelta GetExpectedBandwidthPeriod() const;\n\n private:\n  friend class GoogCcStatePrinter;\n  // Update the target bitrate based on, among other things, the current rate\n  // control state, the current target bitrate and the estimated throughput.\n  // When in the \"increase\" state the bitrate will be increased either\n  // additively or multiplicatively depending on the rate control region. When\n  // in the \"decrease\" state the bitrate will be decreased to slightly below the\n  // current throughput. When in the \"hold\" state the bitrate will be kept\n  // constant to allow built up queues to drain.\n  DataRate ChangeBitrate(DataRate current_bitrate,\n                         const RateControlInput& input,\n                         Timestamp at_time);\n  // Clamps new_bitrate to within the configured min bitrate and a linear\n  // function of the throughput, so that the new bitrate can't grow too\n  // large compared to the bitrate actually being received by the other end.\n  DataRate ClampBitrate(DataRate new_bitrate,\n                        DataRate estimated_throughput) const;\n  DataRate MultiplicativeRateIncrease(Timestamp at_time,\n                                      Timestamp last_ms,\n                                      DataRate current_bitrate) const;\n  DataRate AdditiveRateIncrease(Timestamp at_time, Timestamp last_time) const;\n  void UpdateChangePeriod(Timestamp at_time);\n  void ChangeState(const RateControlInput& input, Timestamp at_time);\n\n  DataRate min_configured_bitrate_;\n  DataRate max_configured_bitrate_;\n  DataRate current_bitrate_;\n  DataRate latest_estimated_throughput_;\n  LinkCapacityEstimator link_capacity_;\n  absl::optional<NetworkStateEstimate> network_estimate_;\n  RateControlState rate_control_state_;\n  Timestamp time_last_bitrate_change_;\n  Timestamp time_last_bitrate_decrease_;\n  Timestamp time_first_throughput_estimate_;\n  bool bitrate_is_initialized_;\n  double beta_;\n  bool in_alr_;\n  TimeDelta rtt_;\n  const bool send_side_;\n  const bool in_experiment_;\n  // Allow the delay based estimate to only increase as long as application\n  // limited region (alr) is not detected.\n  const bool no_bitrate_increase_in_alr_;\n  const bool smoothing_experiment_;\n  // Use estimated link capacity lower bound if it is higher than the\n  // acknowledged rate when backing off due to overuse.\n  const bool estimate_bounded_backoff_;\n  // Use estimated link capacity upper bound as upper limit for increasing\n  // bitrate over the acknowledged rate.\n  const bool estimate_bounded_increase_;\n  absl::optional<DataRate> last_decrease_;\n  FieldTrialOptional<TimeDelta> initial_backoff_interval_;\n  FieldTrialParameter<DataRate> low_throughput_threshold_;\n  // Deprecated, enable |estimate_bounded_backoff_| instead.\n  FieldTrialOptional<double> capacity_deviation_ratio_threshold_;\n  // Deprecated, enable |estimate_bounded_increase_| instead.\n  FieldTrialOptional<double> capacity_limit_deviation_factor_;\n};\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n\nnamespace webrtc {\n\nconst char kBweTypeHistogram[] = \"WebRTC.BWE.Types\";\n\nnamespace congestion_controller {\nint GetMinBitrateBps() {\n  constexpr int kMinBitrateBps = 5000;\n  return kMinBitrateBps;\n}\n\nDataRate GetMinBitrate() {\n  return DataRate::bps(GetMinBitrateBps());\n}\n\n}  // namespace congestion_controller\n\nRateControlInput::RateControlInput(\n    BandwidthUsage bw_state,\n    const absl::optional<DataRate>& estimated_throughput)\n    : bw_state(bw_state), estimated_throughput(estimated_throughput) {}\n\nRateControlInput::~RateControlInput() = default;\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/include/bwe_defines.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_\n\n#include \"api/network_state_predictor.h\"\n#include \"api/units/data_rate.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n\n#define BWE_MAX(a, b) ((a) > (b) ? (a) : (b))\n#define BWE_MIN(a, b) ((a) < (b) ? (a) : (b))\n\nnamespace webrtc {\n\nnamespace congestion_controller {\nint GetMinBitrateBps();\nDataRate GetMinBitrate();\n}  // namespace congestion_controller\n\nstatic const int64_t kBitrateWindowMs = 1000;\n\nextern const char kBweTypeHistogram[];\n\nenum BweNames {\n  kReceiverNoExtension = 0,\n  kReceiverTOffset = 1,\n  kReceiverAbsSendTime = 2,\n  kSendSideTransportSeqNum = 3,\n  kBweNamesMax = 4\n};\n\nenum RateControlState { kRcHold, kRcIncrease, kRcDecrease };\n\nstruct RateControlInput {\n  RateControlInput(BandwidthUsage bw_state,\n                   const absl::optional<DataRate>& estimated_throughput);\n  ~RateControlInput();\n\n  BandwidthUsage bw_state;\n  absl::optional<DataRate> estimated_throughput;\n};\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n// This class estimates the incoming available bandwidth.\n\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_\n\n#include <map>\n#include <vector>\n\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace webrtc {\nnamespace rtcp {\nclass TransportFeedback;\n}  // namespace rtcp\n\nclass RemoteBitrateEstimator;\n\n// RemoteBitrateObserver is used to signal changes in bitrate estimates for\n// the incoming streams.\nclass RemoteBitrateObserver {\n public:\n  // Called when a receive channel group has a new bitrate estimate for the\n  // incoming streams.\n   virtual void OnRembServerAvailableBitrate(\n       const RemoteBitrateEstimator* remoteBitrateEstimator,\n       const std::vector<uint32_t>& ssrcs,\n       uint32_t availableBitrate) = 0;\n\n  virtual ~RemoteBitrateObserver() {}\n};\n\nclass TransportFeedbackSenderInterface {\n public:\n  virtual ~TransportFeedbackSenderInterface() = default;\n  virtual bool SendTransportFeedback(rtcp::TransportFeedback* packet) = 0;\n};\n\n// TODO(holmer): Remove when all implementations have been updated.\nstruct ReceiveBandwidthEstimatorStats {};\n\nclass RemoteBitrateEstimator {\n public:\n  using Listener = RemoteBitrateObserver;\n\n public:\n  virtual ~RemoteBitrateEstimator() {}\n\n  // Called for each incoming packet. Updates the incoming payload bitrate\n  // estimate and the over-use detector. If an over-use is detected the\n  // remote bitrate estimate will be updated. Note that |payload_size| is the\n  // packet size excluding headers.\n  // Note that |arrival_time_ms| can be of an arbitrary time base.\n  virtual void IncomingPacket(\n      int64_t arrival_time_ms,\n      size_t payload_size,\n      const RTC::RTP::Packet& packet,\n      uint32_t send_time_24bits) = 0;\n\n  // Removes all data for |ssrc|.\n  virtual void RemoveStream(uint32_t ssrc) = 0;\n\n  // Returns true if a valid estimate exists and sets |bitrate_bps| to the\n  // estimated payload bitrate in bits per second. |ssrcs| is the list of ssrcs\n  // currently being received and of which the bitrate estimate is based upon.\n  virtual bool LatestEstimate(std::vector<uint32_t>* ssrcs,\n                              uint32_t* bitrate_bps) const = 0;\n\n  // TODO(holmer): Remove when all implementations have been updated.\n  virtual bool GetStats(ReceiveBandwidthEstimatorStats* output) const;\n\n  virtual void SetMinBitrate(int min_bitrate_bps) = 0;\n\n // MS_NOTE: added method.\n public:\n  uint32_t GetAvailableBitrate() const;\n\n protected:\n  static const int64_t kProcessIntervalMs = 500;\n  static const int64_t kStreamTimeOutMs = 2000;\n\n  uint32_t available_bitrate = 0;\n};\n\ninline bool RemoteBitrateEstimator::GetStats(\n    ReceiveBandwidthEstimatorStats* output) const {\n  return false;\n}\n\ninline uint32_t RemoteBitrateEstimator::GetAvailableBitrate() const\n{\n\treturn this->available_bitrate;\n}\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::InterArrival\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/remote_bitrate_estimator/inter_arrival.h\"\n#include \"modules/include/module_common_types_public.h\"\n\n#include \"Logger.hpp\"\n\nnamespace webrtc {\n\nstatic const int kBurstDeltaThresholdMs = 5;\nstatic const int kMaxBurstDurationMs = 100;\n\nInterArrival::InterArrival(uint32_t timestamp_group_length_ticks,\n                           double timestamp_to_ms_coeff,\n                           bool enable_burst_grouping)\n    : kTimestampGroupLengthTicks(timestamp_group_length_ticks),\n      current_timestamp_group_(),\n      prev_timestamp_group_(),\n      timestamp_to_ms_coeff_(timestamp_to_ms_coeff),\n      burst_grouping_(enable_burst_grouping),\n      num_consecutive_reordered_packets_(0) {}\n\nbool InterArrival::ComputeDeltas(uint32_t timestamp,\n                                 int64_t arrival_time_ms,\n                                 int64_t system_time_ms,\n                                 size_t packet_size,\n                                 uint32_t* timestamp_delta, // send_delta.\n                                 int64_t* arrival_time_delta_ms, // recv_delta.\n                                 int* packet_size_delta) {\n  if (timestamp_delta == nullptr) {\n    MS_ERROR(\"timestamp_delta is null\");\n    return false;\n  }\n  if (arrival_time_delta_ms == nullptr) {\n    MS_ERROR(\"arrival_time_delta_ms is null\");\n    return false;\n  }\n  if (packet_size_delta == nullptr) {\n    MS_ERROR(\"packet_size_delta is null\");\n    return false;\n  }\n\n  // Ignore packets with invalid arrival time.\n  if (arrival_time_ms < 0) {\n    MS_WARN_TAG(bwe, \"invalid arrival time %\" PRIi64, arrival_time_ms);\n\n    return false;\n  }\n  bool calculated_deltas = false;\n  if (current_timestamp_group_.IsFirstPacket()) {\n    // We don't have enough data to update the filter, so we store it until we\n    // have two frames of data to process.\n    current_timestamp_group_.timestamp = timestamp;\n    current_timestamp_group_.first_timestamp = timestamp;\n    current_timestamp_group_.first_arrival_ms = arrival_time_ms;\n  } else if (!PacketInOrder(timestamp)) {\n    return false;\n  } else if (NewTimestampGroup(arrival_time_ms, timestamp)) {\n    // First packet of a later frame, the previous frame sample is ready.\n    if (prev_timestamp_group_.complete_time_ms >= 0) {\n      *timestamp_delta =\n          current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp;\n      *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms -\n                               prev_timestamp_group_.complete_time_ms;\n      MS_DEBUG_DEV(\"timestamp previous/current [%\" PRIu32 \"/%\" PRIu32\"] complete time previous/current [%\" PRIi64 \"/%\" PRIi64 \"]\",\n          prev_timestamp_group_.timestamp, current_timestamp_group_.timestamp,\n          prev_timestamp_group_.complete_time_ms, current_timestamp_group_.complete_time_ms);\n      // Check system time differences to see if we have an unproportional jump\n      // in arrival time. In that case reset the inter-arrival computations.\n      int64_t system_time_delta_ms =\n          current_timestamp_group_.last_system_time_ms -\n          prev_timestamp_group_.last_system_time_ms;\n      if (*arrival_time_delta_ms - system_time_delta_ms >=\n          kArrivalTimeOffsetThresholdMs) {\n        MS_WARN_TAG(bwe,\n            \"the arrival time clock offset has changed (diff = %\" PRIi64 \"ms, resetting\",\n            *arrival_time_delta_ms - system_time_delta_ms);\n        Reset();\n        return false;\n      }\n      if (*arrival_time_delta_ms < 0) {\n        // The group of packets has been reordered since receiving its local\n        // arrival timestamp.\n        ++num_consecutive_reordered_packets_;\n        if (num_consecutive_reordered_packets_ >= kReorderedResetThreshold) {\n          MS_WARN_TAG(bwe,\n                 \"packets are being reordered on the path from the \"\n                 \"socket to the bandwidth estimator. Ignoring this \"\n                 \"packet for bandwidth estimation, resetting\");\n          Reset();\n        }\n        return false;\n      } else {\n        num_consecutive_reordered_packets_ = 0;\n      }\n\n      if (*arrival_time_delta_ms < 0) {\n        MS_ERROR(\"arrival_time_delta_ms is < 0\");\n\n        return false;\n      }\n\n      *packet_size_delta = static_cast<int>(current_timestamp_group_.size) -\n                           static_cast<int>(prev_timestamp_group_.size);\n      calculated_deltas = true;\n    }\n    prev_timestamp_group_ = current_timestamp_group_;\n    // The new timestamp is now the current frame.\n    current_timestamp_group_.first_timestamp = timestamp;\n    current_timestamp_group_.timestamp = timestamp;\n    current_timestamp_group_.first_arrival_ms = arrival_time_ms;\n    current_timestamp_group_.size = 0;\n    MS_DEBUG_DEV(\"new timestamp group: first_timestamp:%\" PRIu32 \", first_arrival_ms:%\" PRIi64,\n        current_timestamp_group_.first_timestamp, current_timestamp_group_.first_arrival_ms);\n  } else {\n    current_timestamp_group_.timestamp =\n        LatestTimestamp(current_timestamp_group_.timestamp, timestamp);\n  }\n  // Accumulate the frame size.\n  current_timestamp_group_.size += packet_size;\n  current_timestamp_group_.complete_time_ms = arrival_time_ms;\n  current_timestamp_group_.last_system_time_ms = system_time_ms;\n\n  return calculated_deltas;\n}\n\nbool InterArrival::PacketInOrder(uint32_t timestamp) {\n  if (current_timestamp_group_.IsFirstPacket()) {\n    return true;\n  } else {\n    // Assume that a diff which is bigger than half the timestamp interval\n    // (32 bits) must be due to reordering. This code is almost identical to\n    // that in IsNewerTimestamp() in module_common_types.h.\n    uint32_t timestamp_diff =\n        timestamp - current_timestamp_group_.first_timestamp;\n\n    const static uint32_t int_middle = 0x80000000;\n\n    if (timestamp_diff == int_middle) {\n      return timestamp > current_timestamp_group_.first_timestamp;\n    }\n\n    return timestamp_diff < int_middle;\n  }\n}\n\n// Assumes that |timestamp| is not reordered compared to\n// |current_timestamp_group_|.\nbool InterArrival::NewTimestampGroup(int64_t arrival_time_ms,\n                                     uint32_t timestamp) const {\n  if (current_timestamp_group_.IsFirstPacket()) {\n    return false;\n  } else if (BelongsToBurst(arrival_time_ms, timestamp)) {\n    return false;\n  } else {\n    uint32_t timestamp_diff =\n        timestamp - current_timestamp_group_.first_timestamp;\n    return timestamp_diff > kTimestampGroupLengthTicks;\n  }\n}\n\nbool InterArrival::BelongsToBurst(int64_t arrival_time_ms,\n                                  uint32_t timestamp) const {\n  if (!burst_grouping_) {\n    return false;\n  }\n\n  if (current_timestamp_group_.complete_time_ms < 0) {\n    MS_ERROR(\"current_timestamp_group_.complete_time_ms < 0 [current_timestamp_group_.complete_time_ms:%\" PRIi64 \"]\",\n      current_timestamp_group_.complete_time_ms);\n\n    return false;\n  }\n\n  int64_t arrival_time_delta_ms =\n      arrival_time_ms - current_timestamp_group_.complete_time_ms;\n  uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp;\n  int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5;\n  if (ts_delta_ms == 0)\n    return true;\n  int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms;\n  if (propagation_delta_ms < 0 &&\n      arrival_time_delta_ms <= kBurstDeltaThresholdMs &&\n      arrival_time_ms - current_timestamp_group_.first_arrival_ms <\n          kMaxBurstDurationMs)\n    return true;\n  return false;\n}\n\nvoid InterArrival::Reset() {\n  num_consecutive_reordered_packets_ = 0;\n  current_timestamp_group_ = TimestampGroup();\n  prev_timestamp_group_ = TimestampGroup();\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/inter_arrival.h",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_\n\n#include \"rtc_base/constructor_magic.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace webrtc {\n\n// Helper class to compute the inter-arrival time delta and the size delta\n// between two timestamp groups. A timestamp is a 32 bit unsigned number with\n// a client defined rate.\nclass InterArrival {\n public:\n  // After this many packet groups received out of order InterArrival will\n  // reset, assuming that clocks have made a jump.\n  static constexpr int kReorderedResetThreshold = 3;\n  static constexpr int64_t kArrivalTimeOffsetThresholdMs = 3000;\n\n  // A timestamp group is defined as all packets with a timestamp which are at\n  // most timestamp_group_length_ticks older than the first timestamp in that\n  // group.\n  InterArrival(uint32_t timestamp_group_length_ticks,\n               double timestamp_to_ms_coeff,\n               bool enable_burst_grouping);\n\n  // This function returns true if a delta was computed, or false if the current\n  // group is still incomplete or if only one group has been completed.\n  // |timestamp| is the timestamp.\n  // |arrival_time_ms| is the local time at which the packet arrived.\n  // |packet_size| is the size of the packet.\n  // |timestamp_delta| (output) is the computed timestamp delta.\n  // |arrival_time_delta_ms| (output) is the computed arrival-time delta.\n  // |packet_size_delta| (output) is the computed size delta.\n  bool ComputeDeltas(uint32_t timestamp,\n                     int64_t arrival_time_ms,\n                     int64_t system_time_ms,\n                     size_t packet_size,\n                     uint32_t* timestamp_delta,\n                     int64_t* arrival_time_delta_ms,\n                     int* packet_size_delta);\n\n private:\n  struct TimestampGroup {\n    TimestampGroup()\n        : size(0),\n          first_timestamp(0),\n          timestamp(0),\n          first_arrival_ms(-1),\n          complete_time_ms(-1) {}\n\n    bool IsFirstPacket() const { return complete_time_ms == -1; }\n\n    size_t size;\n    uint32_t first_timestamp;\n    uint32_t timestamp;\n    int64_t first_arrival_ms;\n    int64_t complete_time_ms;\n    int64_t last_system_time_ms;\n  };\n\n  // Returns true if the packet with timestamp |timestamp| arrived in order.\n  bool PacketInOrder(uint32_t timestamp);\n\n  // Returns true if the last packet was the end of the current batch and the\n  // packet with |timestamp| is the first of a new batch.\n  bool NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const;\n\n  bool BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const;\n\n  void Reset();\n\n  const uint32_t kTimestampGroupLengthTicks;\n  TimestampGroup current_timestamp_group_;\n  TimestampGroup prev_timestamp_group_;\n  double timestamp_to_ms_coeff_;\n  bool burst_grouping_;\n  int num_consecutive_reordered_packets_;\n\n  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(InterArrival);\n};\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::OveruseDetector\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/remote_bitrate_estimator/overuse_detector.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/numerics/safe_minmax.h\"\n\n#include \"Logger.hpp\"\n\n#include <math.h>\n#include <stdio.h>\n#include <algorithm>\n#include <string>\n\nnamespace webrtc {\n\nconst char kAdaptiveThresholdExperiment[] = \"WebRTC-AdaptiveBweThreshold\";\nconst char kEnabledPrefix[] = \"Enabled\";\nconst size_t kEnabledPrefixLength = sizeof(kEnabledPrefix) - 1;\nconst char kDisabledPrefix[] = \"Disabled\";\nconst size_t kDisabledPrefixLength = sizeof(kDisabledPrefix) - 1;\n\nconst double kMaxAdaptOffsetMs = 15.0;\nconst double kOverUsingTimeThreshold = 10;\nconst int kMaxNumDeltas = 60;\n\nbool AdaptiveThresholdExperimentIsDisabled(\n    const WebRtcKeyValueConfig& key_value_config) {\n  std::string experiment_string =\n      key_value_config.Lookup(kAdaptiveThresholdExperiment);\n  const size_t kMinExperimentLength = kDisabledPrefixLength;\n  if (experiment_string.length() < kMinExperimentLength)\n    return false;\n  return experiment_string.substr(0, kDisabledPrefixLength) == kDisabledPrefix;\n}\n\n// Gets thresholds from the experiment name following the format\n// \"WebRTC-AdaptiveBweThreshold/Enabled-0.5,0.002/\".\nbool ReadExperimentConstants(const WebRtcKeyValueConfig& key_value_config,\n                             double* k_up,\n                             double* k_down) {\n  std::string experiment_string =\n      key_value_config.Lookup(kAdaptiveThresholdExperiment);\n  const size_t kMinExperimentLength = kEnabledPrefixLength + 3;\n  if (experiment_string.length() < kMinExperimentLength ||\n      experiment_string.substr(0, kEnabledPrefixLength) != kEnabledPrefix)\n    return false;\n  return sscanf(experiment_string.substr(kEnabledPrefixLength + 1).c_str(),\n                \"%lf,%lf\", k_up, k_down) == 2;\n}\n\nOveruseDetector::OveruseDetector(const WebRtcKeyValueConfig* key_value_config)\n    // Experiment is on by default, but can be disabled with finch by setting\n    // the field trial string to \"WebRTC-AdaptiveBweThreshold/Disabled/\".\n    : in_experiment_(!AdaptiveThresholdExperimentIsDisabled(*key_value_config)),\n      k_up_(0.0087),\n      k_down_(0.039),\n      overusing_time_threshold_(100),\n      threshold_(12.5),\n      last_update_ms_(-1),\n      prev_offset_(0.0),\n      time_over_using_(-1),\n      overuse_counter_(0),\n      hypothesis_(BandwidthUsage::kBwNormal) {\n  if (!AdaptiveThresholdExperimentIsDisabled(*key_value_config))\n    InitializeExperiment(*key_value_config);\n}\n\nOveruseDetector::~OveruseDetector() {}\n\nBandwidthUsage OveruseDetector::State() const {\n  return hypothesis_;\n}\n\nBandwidthUsage OveruseDetector::Detect(double offset,\n                                       double ts_delta,\n                                       int num_of_deltas,\n                                       int64_t now_ms) {\n  if (num_of_deltas < 2) {\n    return BandwidthUsage::kBwNormal;\n  }\n  const double T = std::min(num_of_deltas, kMaxNumDeltas) * offset;\n  // BWE_TEST_LOGGING_PLOT(1, \"T\", now_ms, T);\n  // BWE_TEST_LOGGING_PLOT(1, \"threshold\", now_ms, threshold_);\n  if (T > threshold_) {\n    if (time_over_using_ == -1) {\n      // Initialize the timer. Assume that we've been\n      // over-using half of the time since the previous\n      // sample.\n      time_over_using_ = ts_delta / 2;\n    } else {\n      // Increment timer\n      time_over_using_ += ts_delta;\n    }\n    overuse_counter_++;\n    if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {\n      if (offset >= prev_offset_) {\n        time_over_using_ = 0;\n        overuse_counter_ = 0;\n        MS_DEBUG_DEV(\"hypothesis_: BandwidthUsage::kBwOverusing\");\n        hypothesis_ = BandwidthUsage::kBwOverusing;\n      }\n    }\n  } else if (T < -threshold_) {\n    time_over_using_ = -1;\n    overuse_counter_ = 0;\n    hypothesis_ = BandwidthUsage::kBwUnderusing;\n  } else {\n    time_over_using_ = -1;\n    overuse_counter_ = 0;\n    hypothesis_ = BandwidthUsage::kBwNormal;\n  }\n  prev_offset_ = offset;\n\n  UpdateThreshold(T, now_ms);\n\n  return hypothesis_;\n}\n\nvoid OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) {\n  if (!in_experiment_)\n    return;\n\n  if (last_update_ms_ == -1)\n    last_update_ms_ = now_ms;\n\n  if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) {\n    // Avoid adapting the threshold to big latency spikes, caused e.g.,\n    // by a sudden capacity drop.\n    last_update_ms_ = now_ms;\n    return;\n  }\n\n  const double k = fabs(modified_offset) < threshold_ ? k_down_ : k_up_;\n  const int64_t kMaxTimeDeltaMs = 100;\n  int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs);\n  threshold_ += k * (fabs(modified_offset) - threshold_) * time_delta_ms;\n  threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);\n  last_update_ms_ = now_ms;\n}\n\nvoid OveruseDetector::InitializeExperiment(\n    const WebRtcKeyValueConfig& key_value_config) {\n  // RTC_DCHECK(in_experiment_);\n  double k_up = 0.0;\n  double k_down = 0.0;\n  overusing_time_threshold_ = kOverUsingTimeThreshold;\n  if (ReadExperimentConstants(key_value_config, &k_up, &k_down)) {\n    k_up_ = k_up;\n    k_down_ = k_down;\n  }\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_detector.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include <stdint.h>\n\nnamespace webrtc {\n\nbool AdaptiveThresholdExperimentIsDisabled(\n    const WebRtcKeyValueConfig& key_value_config);\n\nclass OveruseDetector {\n public:\n  explicit OveruseDetector(const WebRtcKeyValueConfig* key_value_config);\n  virtual ~OveruseDetector();\n\n  // Update the detection state based on the estimated inter-arrival time delta\n  // offset. |timestamp_delta| is the delta between the last timestamp which the\n  // estimated offset is based on and the last timestamp on which the last\n  // offset was based on, representing the time between detector updates.\n  // |num_of_deltas| is the number of deltas the offset estimate is based on.\n  // Returns the state after the detection update.\n  BandwidthUsage Detect(double offset,\n                        double timestamp_delta,\n                        int num_of_deltas,\n                        int64_t now_ms);\n\n  // Returns the current detector state.\n  BandwidthUsage State() const;\n\n private:\n  void UpdateThreshold(double modified_offset, int64_t now_ms);\n  void InitializeExperiment(const WebRtcKeyValueConfig& key_value_config);\n\n  bool in_experiment_;\n  double k_up_;\n  double k_down_;\n  double overusing_time_threshold_;\n  double threshold_;\n  int64_t last_update_ms_;\n  double prev_offset_;\n  double time_over_using_;\n  int overuse_counter_;\n  BandwidthUsage hypothesis_;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(OveruseDetector);\n};\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::OveruseEstimator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/remote_bitrate_estimator/overuse_estimator.h\"\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n\n#include \"Logger.hpp\"\n\n#include <math.h>\n#include <string.h>\n#include <algorithm>\n\nnamespace webrtc {\n\nenum { kMinFramePeriodHistoryLength = 60 };\nenum { kDeltaCounterMax = 1000 };\n\nOveruseEstimator::OveruseEstimator(const OverUseDetectorOptions& options)\n    : options_(options),\n      num_of_deltas_(0),\n      slope_(options_.initial_slope),\n      offset_(options_.initial_offset),\n      prev_offset_(options_.initial_offset),\n      E_(),\n      process_noise_(),\n      avg_noise_(options_.initial_avg_noise),\n      var_noise_(options_.initial_var_noise),\n      ts_delta_hist_() {\n  memcpy(E_, options_.initial_e, sizeof(E_));\n  memcpy(process_noise_, options_.initial_process_noise,\n         sizeof(process_noise_));\n}\n\nOveruseEstimator::~OveruseEstimator() {\n  ts_delta_hist_.clear();\n}\n\nvoid OveruseEstimator::Update(int64_t t_delta,\n                              double ts_delta,\n                              int size_delta,\n                              BandwidthUsage current_hypothesis,\n                              int64_t now_ms) {\n  const double min_frame_period = UpdateMinFramePeriod(ts_delta);\n  const double t_ts_delta = t_delta - ts_delta;\n  double fs_delta = size_delta;\n\n  ++num_of_deltas_;\n  if (num_of_deltas_ > kDeltaCounterMax) {\n    num_of_deltas_ = kDeltaCounterMax;\n  }\n\n  // Update the Kalman filter.\n  E_[0][0] += process_noise_[0];\n  E_[1][1] += process_noise_[1];\n\n  if ((current_hypothesis == BandwidthUsage::kBwOverusing &&\n       offset_ < prev_offset_) ||\n      (current_hypothesis == BandwidthUsage::kBwUnderusing &&\n       offset_ > prev_offset_)) {\n    E_[1][1] += 10 * process_noise_[1];\n  }\n\n  const double h[2] = {fs_delta, 1.0};\n  const double Eh[2] = {E_[0][0] * h[0] + E_[0][1] * h[1],\n                        E_[1][0] * h[0] + E_[1][1] * h[1]};\n\n  const double residual = t_ts_delta - slope_ * h[0] - offset_;\n\n  const bool in_stable_state =\n      (current_hypothesis == BandwidthUsage::kBwNormal);\n  const double max_residual = 3.0 * sqrt(var_noise_);\n  // We try to filter out very late frames. For instance periodic key\n  // frames doesn't fit the Gaussian model well.\n  if (fabs(residual) < max_residual) {\n    UpdateNoiseEstimate(residual, min_frame_period, in_stable_state);\n  } else {\n    UpdateNoiseEstimate(residual < 0 ? -max_residual : max_residual,\n                        min_frame_period, in_stable_state);\n  }\n\n  const double denom = var_noise_ + h[0] * Eh[0] + h[1] * Eh[1];\n\n  const double K[2] = {Eh[0] / denom, Eh[1] / denom};\n\n  const double IKh[2][2] = {{1.0 - K[0] * h[0], -K[0] * h[1]},\n                            {-K[1] * h[0], 1.0 - K[1] * h[1]}};\n  const double e00 = E_[0][0];\n  const double e01 = E_[0][1];\n\n  // Update state.\n  E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1];\n  E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1];\n  E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1];\n  E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1];\n\n  // The covariance matrix must be positive semi-definite.\n  bool positive_semi_definite =\n      E_[0][0] + E_[1][1] >= 0 &&\n      E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 && E_[0][0] >= 0;\n\n  if (!positive_semi_definite) {\n    MS_ERROR(\"the over-use estimator's covariance matrix is no longer semi-definite\");\n  }\n\n  slope_ = slope_ + K[0] * residual;\n  prev_offset_ = offset_;\n  offset_ = offset_ + K[1] * residual;\n}\n\ndouble OveruseEstimator::UpdateMinFramePeriod(double ts_delta) {\n  double min_frame_period = ts_delta;\n  if (ts_delta_hist_.size() >= kMinFramePeriodHistoryLength) {\n    ts_delta_hist_.pop_front();\n  }\n  for (const double old_ts_delta : ts_delta_hist_) {\n    min_frame_period = std::min(old_ts_delta, min_frame_period);\n  }\n  ts_delta_hist_.push_back(ts_delta);\n  return min_frame_period;\n}\n\nvoid OveruseEstimator::UpdateNoiseEstimate(double residual,\n                                           double ts_delta,\n                                           bool stable_state) {\n  if (!stable_state) {\n    return;\n  }\n  // Faster filter during startup to faster adapt to the jitter level\n  // of the network. |alpha| is tuned for 30 frames per second, but is scaled\n  // according to |ts_delta|.\n  double alpha = 0.01;\n  if (num_of_deltas_ > 10 * 30) {\n    alpha = 0.002;\n  }\n  // Only update the noise estimate if we're not over-using. |beta| is a\n  // function of alpha and the time delta since the previous update.\n  const double beta = pow(1 - alpha, ts_delta * 30.0 / 1000.0);\n  avg_noise_ = beta * avg_noise_ + (1 - beta) * residual;\n  var_noise_ = beta * var_noise_ +\n               (1 - beta) * (avg_noise_ - residual) * (avg_noise_ - residual);\n  if (var_noise_ < 1) {\n    var_noise_ = 1;\n  }\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.h",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_\n\n#include \"modules/remote_bitrate_estimator/include/bwe_defines.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include <stdint.h>\n#include <deque>\n\nnamespace webrtc {\n\n// Bandwidth over-use detector options.  These are used to drive\n// experimentation with bandwidth estimation parameters.\n// TODO(terelius): This is only used in overuse_estimator.cc, and only in the\n// default constructed state. Can we move the relevant variables into that\n// class and delete this?\nstruct OverUseDetectorOptions {\n  OverUseDetectorOptions() = default;\n  double initial_slope = 8.0 / 512.0;\n  double initial_offset = 0;\n  double initial_e[2][2] = {{100.0, 0.0}, {0.0, 1e-1}};\n  double initial_process_noise[2] = {1e-13, 1e-3};\n  double initial_avg_noise = 0.0;\n  double initial_var_noise = 50.0;\n};\n\nclass OveruseEstimator {\n public:\n  explicit OveruseEstimator(const OverUseDetectorOptions& options);\n  ~OveruseEstimator();\n\n  // Update the estimator with a new sample. The deltas should represent deltas\n  // between timestamp groups as defined by the InterArrival class.\n  // |current_hypothesis| should be the hypothesis of the over-use detector at\n  // this time.\n  void Update(int64_t t_delta,\n              double ts_delta,\n              int size_delta,\n              BandwidthUsage current_hypothesis,\n              int64_t now_ms);\n\n  // Returns the estimated noise/jitter variance in ms^2.\n  double var_noise() const { return var_noise_; }\n\n  // Returns the estimated inter-arrival time delta offset in ms.\n  double offset() const { return offset_; }\n\n  // Returns the number of deltas which the current over-use estimator state is\n  // based on.\n  unsigned int num_of_deltas() const { return num_of_deltas_; }\n\n private:\n  double UpdateMinFramePeriod(double ts_delta);\n  void UpdateNoiseEstimate(double residual, double ts_delta, bool stable_state);\n\n  // Must be first member variable. Cannot be const because we need to be\n  // copyable.\n  OverUseDetectorOptions options_;\n  uint16_t num_of_deltas_;\n  double slope_;\n  double offset_;\n  double prev_offset_;\n  double E_[2][2];\n  double process_noise_[2];\n  double avg_noise_;\n  double var_noise_;\n  std::deque<double> ts_delta_hist_;\n\n  RTC_DISALLOW_COPY_AND_ASSIGN(OveruseEstimator);\n};\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::RemoteBitrateEstimatorAbsSendTime\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h\"\n#include \"api/transport/field_trial_based_config.h\"\n#include \"modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h\"\n#include \"rtc_base/constructor_magic.h\"\n\n#include \"Logger.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\n#include <math.h>\n#include <algorithm>\n\nnamespace webrtc {\nnamespace {\nabsl::optional<DataRate> OptionalRateFromOptionalBps(\n    absl::optional<int> bitrate_bps) {\n  if (bitrate_bps) {\n    return DataRate::bps(*bitrate_bps);\n  } else {\n    return absl::nullopt;\n  }\n}\n}  // namespace\n\nenum {\n  // MS_NOTE: kAbsSendTimeFraction taken from RTPHeaderExtension::kAbsSendTimeFraction.\n  kAbsSendTimeFraction = 18,\n  kTimestampGroupLengthMs = 5,\n  kAbsSendTimeInterArrivalUpshift = 8,\n  kInterArrivalShift = kAbsSendTimeFraction +\n                       kAbsSendTimeInterArrivalUpshift,\n  kInitialProbingIntervalMs = 2000,\n  kMinClusterSize = 4,\n  kMaxProbePackets = 15,\n  kExpectedNumberOfProbes = 3\n};\n\nstatic const double kTimestampToMs =\n    1000.0 / static_cast<double>(1 << kInterArrivalShift);\n\ntemplate <typename K, typename V>\nstd::vector<K> Keys(const std::map<K, V>& map) {\n  std::vector<K> keys;\n  keys.reserve(map.size());\n  for (typename std::map<K, V>::const_iterator it = map.begin();\n       it != map.end(); ++it) {\n    keys.push_back(it->first);\n  }\n  return keys;\n}\n\nRemoteBitrateEstimatorAbsSendTime::~RemoteBitrateEstimatorAbsSendTime() =\n    default;\n\nbool RemoteBitrateEstimatorAbsSendTime::IsWithinClusterBounds(\n    int send_delta_ms,\n    const Cluster& cluster_aggregate) {\n  if (cluster_aggregate.count == 0)\n    return true;\n  float cluster_mean = cluster_aggregate.send_mean_ms /\n                       static_cast<float>(cluster_aggregate.count);\n  return fabs(static_cast<float>(send_delta_ms) - cluster_mean) < 2.5f;\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::AddCluster(std::list<Cluster>* clusters,\n                                                   Cluster* cluster) {\n  cluster->send_mean_ms /= static_cast<float>(cluster->count);\n  cluster->recv_mean_ms /= static_cast<float>(cluster->count);\n  cluster->mean_size /= cluster->count;\n  clusters->push_back(*cluster);\n}\n\nRemoteBitrateEstimatorAbsSendTime::RemoteBitrateEstimatorAbsSendTime(\n    RemoteBitrateObserver* observer)\n    : observer_(observer),\n      inter_arrival_(),\n      estimator_(),\n      detector_(&field_trials_),\n      incoming_bitrate_(kBitrateWindowMs, 8000),\n      incoming_bitrate_initialized_(false),\n      total_probes_received_(0),\n      first_packet_time_ms_(-1),\n      last_update_ms_(-1),\n      uma_recorded_(false),\n      remote_rate_(&field_trials_) {\n  MS_DEBUG_TAG(bwe, \"RemoteBitrateEstimatorAbsSendTime: Instantiating.\");\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::ComputeClusters(\n    std::list<Cluster>* clusters) const {\n  Cluster current;\n  int64_t prev_send_time = -1;\n  int64_t prev_recv_time = -1;\n  for (std::list<Probe>::const_iterator it = probes_.begin();\n       it != probes_.end(); ++it) {\n    if (prev_send_time >= 0) {\n      int send_delta_ms = it->send_time_ms - prev_send_time;\n      int recv_delta_ms = it->recv_time_ms - prev_recv_time;\n      if (send_delta_ms >= 1 && recv_delta_ms >= 1) {\n        ++current.num_above_min_delta;\n      }\n      if (!IsWithinClusterBounds(send_delta_ms, current)) {\n        if (current.count >= kMinClusterSize && current.send_mean_ms > 0.0f &&\n            current.recv_mean_ms > 0.0f) {\n          AddCluster(clusters, &current);\n        }\n        current = Cluster();\n      }\n      current.send_mean_ms += send_delta_ms;\n      current.recv_mean_ms += recv_delta_ms;\n      current.mean_size += it->payload_size;\n      ++current.count;\n    }\n    prev_send_time = it->send_time_ms;\n    prev_recv_time = it->recv_time_ms;\n  }\n  if (current.count >= kMinClusterSize && current.send_mean_ms > 0.0f &&\n      current.recv_mean_ms > 0.0f) {\n    AddCluster(clusters, &current);\n  }\n}\n\nstd::list<Cluster>::const_iterator\nRemoteBitrateEstimatorAbsSendTime::FindBestProbe(\n    const std::list<Cluster>& clusters) const {\n  int highest_probe_bitrate_bps = 0;\n  std::list<Cluster>::const_iterator best_it = clusters.end();\n  for (std::list<Cluster>::const_iterator it = clusters.begin();\n       it != clusters.end(); ++it) {\n    if (it->send_mean_ms == 0 || it->recv_mean_ms == 0)\n      continue;\n    if (it->num_above_min_delta > it->count / 2 &&\n        (it->recv_mean_ms - it->send_mean_ms <= 2.0f &&\n         it->send_mean_ms - it->recv_mean_ms <= 5.0f)) {\n      int probe_bitrate_bps =\n          std::min(it->GetSendBitrateBps(), it->GetRecvBitrateBps());\n      if (probe_bitrate_bps > highest_probe_bitrate_bps) {\n        highest_probe_bitrate_bps = probe_bitrate_bps;\n        best_it = it;\n      }\n    } else {\n// MS_NOTE: avoid 'unused variable' compiler warning.\n#if MS_LOG_DEV_LEVEL == 3\n      int send_bitrate_bps = it->mean_size * 8 * 1000 / it->send_mean_ms;\n      int recv_bitrate_bps = it->mean_size * 8 * 1000 / it->recv_mean_ms;\n\n      MS_DEBUG_DEV(\n        \"probe failed, sent at %d bps, received at %d bps [mean \"\n        \"send delta:%fms, mean recv delta:%fms, num probes:%d]\",\n        send_bitrate_bps,\n        recv_bitrate_bps,\n        it->send_mean_ms,\n        it->recv_mean_ms,\n        it->count);\n#endif\n\n      break;\n    }\n  }\n  return best_it;\n}\n\nRemoteBitrateEstimatorAbsSendTime::ProbeResult\nRemoteBitrateEstimatorAbsSendTime::ProcessClusters(int64_t now_ms) {\n  std::list<Cluster> clusters;\n  ComputeClusters(&clusters);\n  if (clusters.empty()) {\n    // If we reach the max number of probe packets and still have no clusters,\n    // we will remove the oldest one.\n    if (probes_.size() >= kMaxProbePackets)\n      probes_.pop_front();\n    return ProbeResult::kNoUpdate;\n  }\n\n  std::list<Cluster>::const_iterator best_it = FindBestProbe(clusters);\n  if (best_it != clusters.end()) {\n    int probe_bitrate_bps =\n        std::min(best_it->GetSendBitrateBps(), best_it->GetRecvBitrateBps());\n    // Make sure that a probe sent on a lower bitrate than our estimate can't\n    // reduce the estimate.\n    if (IsBitrateImproving(probe_bitrate_bps)) {\n      MS_DEBUG_DEV(\n          \"probe successful, sent at %d bps, received at %d bps \"\n          \"mean send delta:%fms, mean recv delta:%f ms, \"\n          \"num probes:%d\",\n          best_it->GetSendBitrateBps(),\n          best_it->GetRecvBitrateBps(),\n          best_it->send_mean_ms,\n          best_it->recv_mean_ms,\n          best_it->count);\n\n      remote_rate_.SetEstimate(DataRate::bps(probe_bitrate_bps),\n                               Timestamp::ms(now_ms));\n      return ProbeResult::kBitrateUpdated;\n    }\n  }\n\n  // Not probing and received non-probe packet, or finished with current set\n  // of probes.\n  if (clusters.size() >= kExpectedNumberOfProbes)\n    probes_.clear();\n  return ProbeResult::kNoUpdate;\n}\n\nbool RemoteBitrateEstimatorAbsSendTime::IsBitrateImproving(\n    int new_bitrate_bps) const {\n  bool initial_probe = !remote_rate_.ValidEstimate() && new_bitrate_bps > 0;\n  bool bitrate_above_estimate =\n      remote_rate_.ValidEstimate() &&\n      new_bitrate_bps > remote_rate_.LatestEstimate().bps<int>();\n  return initial_probe || bitrate_above_estimate;\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::IncomingPacket(\n    int64_t arrival_time_ms, size_t payload_size, const RTC::RTP::Packet& packet, const uint32_t send_time_24bits)\n{\n  MS_TRACE();\n\n  IncomingPacketInfo(arrival_time_ms, send_time_24bits, payload_size, packet.GetSsrc());\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::IncomingPacketInfo(\n    int64_t arrival_time_ms,\n    uint32_t send_time_24bits,\n    size_t payload_size,\n    uint32_t ssrc) {\n  // RTC_CHECK(send_time_24bits < (1ul << 24));\n  if (send_time_24bits >= (1ul << 24)) {\n    MS_ERROR(\"invalid sendTime24bits value\");\n    return;\n  }\n\n  if (!uma_recorded_) {\n    uma_recorded_ = true;\n  }\n\n  // Shift up send time to use the full 32 bits that inter_arrival works with,\n  // so wrapping works properly.\n  uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift;\n  int64_t send_time_ms = static_cast<int64_t>(timestamp) * kTimestampToMs;\n\n  int64_t now_ms = DepLibUV::GetTimeMsInt64();\n  // TODO(holmer): SSRCs are only needed for REMB, should be broken out from\n  // here.\n\n  // Check if incoming bitrate estimate is valid, and if it needs to be reset.\n  absl::optional<uint32_t> incoming_bitrate =\n      incoming_bitrate_.Rate(arrival_time_ms);\n  if (incoming_bitrate) {\n    incoming_bitrate_initialized_ = true;\n  } else if (incoming_bitrate_initialized_) {\n    // Incoming bitrate had a previous valid value, but now not enough data\n    // point are left within the current window. Reset incoming bitrate\n    // estimator so that the window size will only contain new data points.\n    incoming_bitrate_.Reset();\n    incoming_bitrate_initialized_ = false;\n  }\n  incoming_bitrate_.Update(payload_size, arrival_time_ms);\n\n  if (first_packet_time_ms_ == -1)\n    first_packet_time_ms_ = now_ms;\n\n  uint32_t ts_delta = 0;\n  int64_t t_delta = 0;\n  int size_delta = 0;\n  bool update_estimate = false;\n  uint32_t target_bitrate_bps = 0;\n  std::vector<uint32_t> ssrcs;\n  {\n    TimeoutStreams(now_ms);\n    ssrcs_[ssrc] = now_ms;\n\n    // For now only try to detect probes while we don't have a valid estimate.\n    // We currently assume that only packets larger than 200 bytes are paced by\n    // the sender.\n    const size_t kMinProbePacketSize = 200;\n    if (payload_size > kMinProbePacketSize &&\n        (!remote_rate_.ValidEstimate() ||\n         now_ms - first_packet_time_ms_ < kInitialProbingIntervalMs)) {\n\n#if MS_LOG_DEV_LEVEL == 3\n      // TODO(holmer): Use a map instead to get correct order?\n      if (total_probes_received_ < kMaxProbePackets) {\n        int send_delta_ms = -1;\n        int recv_delta_ms = -1;\n        if (!probes_.empty()) {\n          send_delta_ms = send_time_ms - probes_.back().send_time_ms;\n          recv_delta_ms = arrival_time_ms - probes_.back().recv_time_ms;\n        }\n        MS_DEBUG_DEV(\n            \"probe packet received [send time:%\" PRId64\n            \"ms, recv \"\n            \"time:%\" PRId64 \"ms, send delta:%dms, recv delta:%d ms]\",\n            send_time_ms,\n            arrival_time_ms,\n            send_delta_ms,\n            recv_delta_ms);\n      }\n#endif\n\n      probes_.push_back(Probe(send_time_ms, arrival_time_ms, payload_size));\n      ++total_probes_received_;\n      // Make sure that a probe which updated the bitrate immediately has an\n      // effect by calling the OnRembServerAvailableBitrate callback.\n      if (ProcessClusters(now_ms) == ProbeResult::kBitrateUpdated)\n        update_estimate = true;\n    }\n    if (inter_arrival_->ComputeDeltas(timestamp, arrival_time_ms, now_ms,\n                                      payload_size, &ts_delta, &t_delta,\n                                      &size_delta)) {\n      double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift);\n      estimator_->Update(t_delta, ts_delta_ms, size_delta, detector_.State(),\n                         arrival_time_ms);\n      detector_.Detect(estimator_->offset(), ts_delta_ms,\n                       estimator_->num_of_deltas(), arrival_time_ms);\n    }\n\n    if (!update_estimate) {\n      // Check if it's time for a periodic update or if we should update because\n      // of an over-use.\n      if (last_update_ms_ == -1 ||\n          now_ms - last_update_ms_ > remote_rate_.GetFeedbackInterval().ms()) {\n        update_estimate = true;\n      } else if (detector_.State() == BandwidthUsage::kBwOverusing) {\n        absl::optional<uint32_t> incoming_rate =\n            incoming_bitrate_.Rate(arrival_time_ms);\n        if (incoming_rate &&\n            remote_rate_.TimeToReduceFurther(Timestamp::ms(now_ms),\n                                             DataRate::bps(*incoming_rate))) {\n          update_estimate = true;\n        }\n      }\n    }\n\n    if (update_estimate) {\n      // The first overuse should immediately trigger a new estimate.\n      // We also have to update the estimate immediately if we are overusing\n      // and the target bitrate is too high compared to what we are receiving.\n      const RateControlInput input(\n          detector_.State(),\n          OptionalRateFromOptionalBps(incoming_bitrate_.Rate(arrival_time_ms)));\n      target_bitrate_bps =\n          remote_rate_.Update(&input, Timestamp::ms(now_ms)).bps<uint32_t>();\n      update_estimate = remote_rate_.ValidEstimate();\n      ssrcs = Keys(ssrcs_);\n    }\n  }\n  if (update_estimate) {\n    last_update_ms_ = now_ms;\n    available_bitrate = target_bitrate_bps;\n    observer_->OnRembServerAvailableBitrate(this, ssrcs, target_bitrate_bps);\n  }\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::TimeoutStreams(int64_t now_ms) {\n  for (Ssrcs::iterator it = ssrcs_.begin(); it != ssrcs_.end();) {\n    if ((now_ms - it->second) > kStreamTimeOutMs) {\n      ssrcs_.erase(it++);\n    } else {\n      ++it;\n    }\n  }\n  if (ssrcs_.empty()) {\n    // We can't update the estimate if we don't have any active streams.\n    inter_arrival_.reset(\n        new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000,\n                         kTimestampToMs, true));\n    estimator_.reset(new OveruseEstimator(OverUseDetectorOptions()));\n    // We deliberately don't reset the first_packet_time_ms_ here for now since\n    // we only probe for bandwidth in the beginning of a call right now.\n  }\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::RemoveStream(uint32_t ssrc) {\n  ssrcs_.erase(ssrc);\n}\n\nbool RemoteBitrateEstimatorAbsSendTime::LatestEstimate(\n    std::vector<uint32_t>* ssrcs,\n    uint32_t* bitrate_bps) const {\n  // Currently accessed from both the process thread (see\n  // ModuleRtpRtcpImpl::Process()) and the configuration thread (see\n  // Call::GetStats()). Should in the future only be accessed from a single\n  // thread.\n  if (!remote_rate_.ValidEstimate()) {\n    return false;\n  }\n  *ssrcs = Keys(ssrcs_);\n  if (ssrcs_.empty()) {\n    *bitrate_bps = 0;\n  } else {\n    *bitrate_bps = remote_rate_.LatestEstimate().bps<uint32_t>();\n  }\n  return true;\n}\n\nvoid RemoteBitrateEstimatorAbsSendTime::SetMinBitrate(int min_bitrate_bps) {\n  // Called from both the configuration thread and the network thread. Shouldn't\n  // be called from the network thread in the future.\n  remote_rate_.SetMinBitrate(DataRate::bps(min_bitrate_bps));\n}\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_\n#define MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_\n\n#include \"api/transport/field_trial_based_config.h\"\n#include \"modules/remote_bitrate_estimator/aimd_rate_control.h\"\n#include \"modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h\"\n#include \"modules/remote_bitrate_estimator/inter_arrival.h\"\n#include \"modules/remote_bitrate_estimator/overuse_detector.h\"\n#include \"modules/remote_bitrate_estimator/overuse_estimator.h\"\n#include \"rtc_base/constructor_magic.h\"\n#include \"rtc_base/rate_statistics.h\"\n\n#include \"RTC/RTP/Packet.hpp\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <list>\n#include <map>\n#include <memory>\n#include <vector>\n\nnamespace webrtc {\n\nstruct Probe {\n  Probe(int64_t send_time_ms, int64_t recv_time_ms, size_t payload_size)\n      : send_time_ms(send_time_ms),\n        recv_time_ms(recv_time_ms),\n        payload_size(payload_size) {}\n  int64_t send_time_ms;\n  int64_t recv_time_ms;\n  size_t payload_size;\n};\n\nstruct Cluster {\n  Cluster()\n      : send_mean_ms(0.0f),\n        recv_mean_ms(0.0f),\n        mean_size(0),\n        count(0),\n        num_above_min_delta(0) {}\n\n  int GetSendBitrateBps() const {\n    assert(send_mean_ms > 0.0f);\n\n    return mean_size * 8 * 1000 / send_mean_ms;\n  }\n\n  int GetRecvBitrateBps() const {\n    assert(recv_mean_ms > 0.0f);\n\n    return mean_size * 8 * 1000 / recv_mean_ms;\n  }\n\n  float send_mean_ms;\n  float recv_mean_ms;\n  // TODO(holmer): Add some variance metric as well?\n  size_t mean_size;\n  int count;\n  int num_above_min_delta;\n};\n\nclass RemoteBitrateEstimatorAbsSendTime : public RemoteBitrateEstimator {\n public:\n  RemoteBitrateEstimatorAbsSendTime(RemoteBitrateObserver* observer);\n  ~RemoteBitrateEstimatorAbsSendTime();\n\n  void IncomingPacket(\n      int64_t arrivalTimeMs, size_t payloadSize, const RTC::RTP::Packet& packet, uint32_t absSendTime) override;\n  // This class relies on Process() being called periodically (at least once\n  // every other second) for streams to be timed out properly. Therefore it\n  // shouldn't be detached from the ProcessThread except if it's about to be\n  // deleted.\n  // void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override;\n  void RemoveStream(uint32_t ssrc) override;\n  bool LatestEstimate(std::vector<uint32_t>* ssrcs,\n                      uint32_t* bitrate_bps) const override;\n  void SetMinBitrate(int min_bitrate_bps) override;\n\n private:\n  typedef std::map<uint32_t, int64_t> Ssrcs;\n  enum class ProbeResult { kBitrateUpdated, kNoUpdate };\n\n  static bool IsWithinClusterBounds(int send_delta_ms,\n                                    const Cluster& cluster_aggregate);\n\n  static void AddCluster(std::list<Cluster>* clusters, Cluster* cluster);\n\n  void IncomingPacketInfo(int64_t arrival_time_ms,\n                          uint32_t send_time_24bits,\n                          size_t payload_size,\n                          uint32_t ssrc);\n\n  void ComputeClusters(std::list<Cluster>* clusters) const;\n\n  std::list<Cluster>::const_iterator FindBestProbe(\n      const std::list<Cluster>& clusters) const;\n\n  // Returns true if a probe which changed the estimate was detected.\n  ProbeResult ProcessClusters(int64_t now_ms);\n\n  bool IsBitrateImproving(int probe_bitrate_bps) const;\n\n  void TimeoutStreams(int64_t now_ms);\n\n  const FieldTrialBasedConfig field_trials_;\n  RemoteBitrateObserver* const observer_;\n  std::unique_ptr<InterArrival> inter_arrival_;\n  std::unique_ptr<OveruseEstimator> estimator_;\n  OveruseDetector detector_;\n  RateStatistics incoming_bitrate_;\n  bool incoming_bitrate_initialized_;\n  std::vector<int> recent_propagation_delta_ms_;\n  std::vector<int64_t> recent_update_time_ms_;\n  std::list<Probe> probes_;\n  size_t total_probes_received_;\n  int64_t first_packet_time_ms_;\n  int64_t last_update_ms_;\n  bool uma_recorded_;\n\n  Ssrcs ssrcs_;\n  AimdRateControl remote_rate_;\n\n  RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorAbsSendTime);\n};\n\n}  // namespace webrtc\n\n#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc",
    "content": "/*\n *  Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"modules/rtp_rtcp/include/rtp_rtcp_defines.h\"\n\n#include <ctype.h>\n#include <string.h>\n\nnamespace webrtc {\n\nPacketFeedback::PacketFeedback(int64_t arrival_time_ms,\n                               uint16_t sequence_number)\n    : PacketFeedback(-1,\n                     arrival_time_ms,\n                     kNoSendTime,\n                     sequence_number,\n                     0,\n                     0,\n                     0,\n                     PacedPacketInfo()) {}\n\nPacketFeedback::PacketFeedback(int64_t arrival_time_ms,\n                               int64_t send_time_ms,\n                               uint16_t sequence_number,\n                               size_t payload_size,\n                               const PacedPacketInfo& pacing_info)\n    : PacketFeedback(-1,\n                     arrival_time_ms,\n                     send_time_ms,\n                     sequence_number,\n                     payload_size,\n                     0,\n                     0,\n                     pacing_info) {}\n\nPacketFeedback::PacketFeedback(int64_t creation_time_ms,\n                               uint16_t sequence_number,\n                               size_t payload_size,\n                               uint16_t local_net_id,\n                               uint16_t remote_net_id,\n                               const PacedPacketInfo& pacing_info)\n    : PacketFeedback(creation_time_ms,\n                     kNotReceived,\n                     kNoSendTime,\n                     sequence_number,\n                     payload_size,\n                     local_net_id,\n                     remote_net_id,\n                     pacing_info) {}\n\nPacketFeedback::PacketFeedback(int64_t creation_time_ms,\n                               int64_t arrival_time_ms,\n                               int64_t send_time_ms,\n                               uint16_t sequence_number,\n                               size_t payload_size,\n                               uint16_t local_net_id,\n                               uint16_t remote_net_id,\n                               const PacedPacketInfo& pacing_info)\n    : creation_time_ms(creation_time_ms),\n      arrival_time_ms(arrival_time_ms),\n      send_time_ms(send_time_ms),\n      sequence_number(sequence_number),\n      long_sequence_number(0),\n      payload_size(payload_size),\n      unacknowledged_data(0),\n      local_net_id(local_net_id),\n      remote_net_id(remote_net_id),\n      pacing_info(pacing_info),\n      ssrc(0),\n      rtp_sequence_number(0) {}\n\nPacketFeedback::PacketFeedback(const PacketFeedback&) = default;\nPacketFeedback& PacketFeedback::operator=(const PacketFeedback&) = default;\nPacketFeedback::~PacketFeedback() = default;\n\nbool PacketFeedback::operator==(const PacketFeedback& rhs) const {\n  return arrival_time_ms == rhs.arrival_time_ms &&\n         send_time_ms == rhs.send_time_ms &&\n         sequence_number == rhs.sequence_number &&\n         payload_size == rhs.payload_size && pacing_info == rhs.pacing_info;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h",
    "content": "/*\n *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_\n#define MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_\n\n#include \"api/transport/network_types.h\"\n\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <list>\n#include <vector>\n\nnamespace webrtc {\nnamespace rtcp {\nclass TransportFeedback;\n}\n\nstruct RTCPReportBlock {\n  RTCPReportBlock()\n      : sender_ssrc(0),\n        source_ssrc(0),\n        fraction_lost(0),\n        packets_lost(0),\n        extended_highest_sequence_number(0),\n        jitter(0),\n        last_sender_report_timestamp(0),\n        delay_since_last_sender_report(0) {}\n\n  RTCPReportBlock(uint32_t sender_ssrc,\n                  uint32_t source_ssrc,\n                  uint8_t fraction_lost,\n                  int32_t packets_lost,\n                  uint32_t extended_highest_sequence_number,\n                  uint32_t jitter,\n                  uint32_t last_sender_report_timestamp,\n                  uint32_t delay_since_last_sender_report)\n      : sender_ssrc(sender_ssrc),\n        source_ssrc(source_ssrc),\n        fraction_lost(fraction_lost),\n        packets_lost(packets_lost),\n        extended_highest_sequence_number(extended_highest_sequence_number),\n        jitter(jitter),\n        last_sender_report_timestamp(last_sender_report_timestamp),\n        delay_since_last_sender_report(delay_since_last_sender_report) {}\n\n  // Fields as described by RFC 3550 6.4.2.\n  uint32_t sender_ssrc;  // SSRC of sender of this report.\n  uint32_t source_ssrc;  // SSRC of the RTP packet sender.\n  uint8_t fraction_lost;\n  int32_t packets_lost;  // 24 bits valid.\n  uint32_t extended_highest_sequence_number;\n  uint32_t jitter;\n  uint32_t last_sender_report_timestamp;\n  uint32_t delay_since_last_sender_report;\n};\n\ntypedef std::list<RTCPReportBlock> ReportBlockList;\n\nclass RtcpBandwidthObserver {\n public:\n  // REMB or TMMBR\n  virtual void OnReceivedEstimatedBitrate(uint32_t bitrate) = 0;\n\n  virtual void OnReceivedRtcpReceiverReport(\n      const ReportBlockList& report_blocks,\n      int64_t rtt,\n      int64_t now_ms) = 0;\n\n  virtual ~RtcpBandwidthObserver() {}\n};\n\nstruct PacketFeedback {\n  PacketFeedback(int64_t arrival_time_ms, uint16_t sequence_number);\n\n  PacketFeedback(int64_t arrival_time_ms,\n                 int64_t send_time_ms,\n                 uint16_t sequence_number,\n                 size_t payload_size,\n                 const PacedPacketInfo& pacing_info);\n\n  PacketFeedback(int64_t creation_time_ms,\n                 uint16_t sequence_number,\n                 size_t payload_size,\n                 uint16_t local_net_id,\n                 uint16_t remote_net_id,\n                 const PacedPacketInfo& pacing_info);\n\n  PacketFeedback(int64_t creation_time_ms,\n                 int64_t arrival_time_ms,\n                 int64_t send_time_ms,\n                 uint16_t sequence_number,\n                 size_t payload_size,\n                 uint16_t local_net_id,\n                 uint16_t remote_net_id,\n                 const PacedPacketInfo& pacing_info);\n  PacketFeedback(const PacketFeedback&);\n  PacketFeedback& operator=(const PacketFeedback&);\n  ~PacketFeedback();\n\n  static constexpr int kNotAProbe = -1;\n  static constexpr int64_t kNotReceived = -1;\n  static constexpr int64_t kNoSendTime = -1;\n\n  // NOTE! The variable |creation_time_ms| is not used when testing equality.\n  //       This is due to |creation_time_ms| only being used by SendTimeHistory\n  //       for book-keeping, and is of no interest outside that class.\n  // TODO(philipel): Remove |creation_time_ms| from PacketFeedback when cleaning\n  //                 up SendTimeHistory.\n  bool operator==(const PacketFeedback& rhs) const;\n\n  // Time corresponding to when this object was created.\n  int64_t creation_time_ms;\n  // Time corresponding to when the packet was received. Timestamped with the\n  // receiver's clock. For unreceived packet, the sentinel value kNotReceived\n  // is used.\n  int64_t arrival_time_ms;\n  // Time corresponding to when the packet was sent, timestamped with the\n  // sender's clock.\n  int64_t send_time_ms;\n  // Packet identifier, incremented with 1 for every packet generated by the\n  // sender.\n  uint16_t sequence_number;\n  // Session unique packet identifier, incremented with 1 for every packet\n  // generated by the sender.\n  int64_t long_sequence_number;\n  // Size of the packet excluding RTP headers.\n  size_t payload_size;\n  // Size of preceeding packets that are not part of feedback.\n  size_t unacknowledged_data;\n  // The network route ids that this packet is associated with.\n  uint16_t local_net_id;\n  uint16_t remote_net_id;\n  // Pacing information about this packet.\n  PacedPacketInfo pacing_info;\n\n  // The SSRC and RTP sequence number of the packet this feedback refers to.\n  absl::optional<uint32_t> ssrc;\n  uint16_t rtp_sequence_number;\n};\n\nstruct RtpPacketSendInfo {\n public:\n  RtpPacketSendInfo() = default;\n\n  uint16_t transport_sequence_number = 0;\n  uint32_t ssrc = 0;\n  uint16_t rtp_sequence_number = 0;\n  // Get rid of this flag when all code paths populate |rtp_sequence_number|.\n  bool has_rtp_sequence_number = false;\n  size_t length = 0;\n  PacedPacketInfo pacing_info;\n};\n\nclass NetworkStateEstimateObserver {\n public:\n  virtual void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) = 0;\n  virtual ~NetworkStateEstimateObserver() = default;\n};\n\nclass TransportFeedbackObserver {\n public:\n  TransportFeedbackObserver() {}\n  virtual ~TransportFeedbackObserver() {}\n\n  virtual void OnAddPacket(const RtpPacketSendInfo& packet_info) = 0;\n  virtual void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback) = 0;\n};\n\nclass PacketFeedbackObserver {\n public:\n  virtual ~PacketFeedbackObserver() = default;\n\n  virtual void OnPacketAdded(uint32_t ssrc, uint16_t seq_num) = 0;\n  virtual void OnPacketFeedbackVector(\n      const std::vector<PacketFeedback>& packet_feedback_vector) = 0;\n};\n\n// Callback, used to notify an observer whenever the send-side delay is updated.\nclass SendSideDelayObserver {\n public:\n  virtual ~SendSideDelayObserver() {}\n  virtual void SendSideDelayUpdated(int avg_delay_ms,\n                                    int max_delay_ms,\n                                    uint64_t total_delay_ms,\n                                    uint32_t ssrc) = 0;\n};\n\n// Callback, used to notify an observer whenever a packet is sent to the\n// transport.\n// TODO(asapersson): This class will remove the need for SendSideDelayObserver.\n// Remove SendSideDelayObserver once possible.\nclass SendPacketObserver {\n public:\n  virtual ~SendPacketObserver() {}\n  virtual void OnSendPacket(uint16_t packet_id,\n                            int64_t capture_time_ms,\n                            uint32_t ssrc) = 0;\n};\n\n// Status returned from TimeToSendPacket() family of callbacks.\nenum class RtpPacketSendResult {\n  kSuccess,               // Packet sent OK.\n  kTransportUnavailable,  // Network unavailable, try again later.\n  kPacketNotFound  // SSRC/sequence number does not map to an available packet.\n};\n\n}  // namespace webrtc\n#endif  // MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/source/rtp_packet/transport_feedback.h",
    "content": "/*\n *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_\n#define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_\n\n#include <cstdint>\n\nnamespace webrtc {\nnamespace rtcp {\n\n// Convert to multiples of 0.25ms.\nstatic constexpr int kDeltaScaleFactor = 250;\n\nclass ReceivedPacket {\n public:\n  ReceivedPacket(uint16_t sequence_number, int16_t delta_ticks)\n      : sequence_number_(sequence_number),\n        delta_ticks_(delta_ticks),\n        received_(true) {}\n  explicit ReceivedPacket(uint16_t sequence_number)\n      : sequence_number_(sequence_number), received_(false) {}\n  ReceivedPacket(const ReceivedPacket&) = default;\n  ReceivedPacket& operator=(const ReceivedPacket&) = default;\n\n  uint16_t sequence_number() const { return sequence_number_; }\n  int16_t delta_ticks() const { return delta_ticks_; }\n  int32_t delta_us() const { return delta_ticks_ * kDeltaScaleFactor; }\n  bool received() const { return received_; }\n\n private:\n  uint16_t sequence_number_;\n  int16_t delta_ticks_;\n  bool received_;\n};\n}  // namespace rtcp\n}  // namespace webrtc\n#endif  // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/constructor_magic.h",
    "content": "/*\n *  Copyright 2004 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_CONSTRUCTOR_MAGIC_H_\n#define RTC_BASE_CONSTRUCTOR_MAGIC_H_\n\n// Put this in the declarations for a class to be unassignable.\n#define RTC_DISALLOW_ASSIGN(TypeName) \\\n  TypeName& operator=(const TypeName&) = delete\n\n// A macro to disallow the copy constructor and operator= functions. This should\n// be used in the declarations for a class.\n#define RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n  TypeName(const TypeName&) = delete;          \\\n  RTC_DISALLOW_ASSIGN(TypeName)\n\n// A macro to disallow all the implicit constructors, namely the default\n// constructor, copy constructor and operator= functions.\n//\n// This should be used in the declarations for a class that wants to prevent\n// anyone from instantiating it. This is especially useful for classes\n// containing only static methods.\n#define RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \\\n  TypeName() = delete;                               \\\n  RTC_DISALLOW_COPY_AND_ASSIGN(TypeName)\n\n#endif  // RTC_BASE_CONSTRUCTOR_MAGIC_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/alr_experiment.cc",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::AlrExperiment\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"rtc_base/experiments/alr_experiment.h\"\n#include \"api/transport/field_trial_based_config.h\"\n\n#include \"Logger.hpp\"\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <string>\n\nnamespace webrtc {\n\nconst char AlrExperimentSettings::kScreenshareProbingBweExperimentName[] =\n    \"WebRTC-ProbingScreenshareBwe\";\nconst char AlrExperimentSettings::kStrictPacingAndProbingExperimentName[] =\n    \"WebRTC-StrictPacingAndProbing\";\nconst char kDefaultProbingScreenshareBweSettings[] = \"1.0,2875,80,40,-60,3,3000\";\n\nbool AlrExperimentSettings::MaxOneFieldTrialEnabled() {\n  return AlrExperimentSettings::MaxOneFieldTrialEnabled(\n      FieldTrialBasedConfig());\n}\n\nbool AlrExperimentSettings::MaxOneFieldTrialEnabled(\n    const WebRtcKeyValueConfig& key_value_config) {\n  return key_value_config.Lookup(kStrictPacingAndProbingExperimentName)\n             .empty() ||\n         key_value_config.Lookup(kScreenshareProbingBweExperimentName).empty();\n}\n\nabsl::optional<AlrExperimentSettings>\nAlrExperimentSettings::CreateFromFieldTrial(const char* experiment_name) {\n  return AlrExperimentSettings::CreateFromFieldTrial(FieldTrialBasedConfig(),\n                                                     experiment_name);\n}\n\nabsl::optional<AlrExperimentSettings>\nAlrExperimentSettings::CreateFromFieldTrial(\n    const WebRtcKeyValueConfig& key_value_config,\n    const char* experiment_name) {\n  absl::optional<AlrExperimentSettings> ret;\n  std::string group_name = key_value_config.Lookup(experiment_name);\n\n  const std::string kIgnoredSuffix = \"_Dogfood\";\n  std::string::size_type suffix_pos = group_name.rfind(kIgnoredSuffix);\n  if (suffix_pos != std::string::npos &&\n      suffix_pos == group_name.length() - kIgnoredSuffix.length()) {\n    group_name.resize(group_name.length() - kIgnoredSuffix.length());\n  }\n\n  if (group_name.empty()) {\n    if (experiment_name == kScreenshareProbingBweExperimentName) {\n      // This experiment is now default-on with fixed settings.\n      // TODO(sprang): Remove this kill-switch and clean up experiment code.\n      group_name = kDefaultProbingScreenshareBweSettings;\n    } else {\n      return ret;\n    }\n  }\n\n  AlrExperimentSettings settings;\n  if (sscanf(group_name.c_str(), \"%f,%\" PRId64 \",%d,%d,%d,%d,%d\",\n             &settings.pacing_factor, &settings.max_paced_queue_time,\n             &settings.alr_bandwidth_usage_percent,\n             &settings.alr_start_budget_level_percent,\n             &settings.alr_stop_budget_level_percent,\n             &settings.group_id,\n             &settings.alr_timeout) == 7) {\n    ret.emplace(settings);\n    MS_DEBUG_TAG(bwe, \"Using ALR experiment settings: \"\n                      \"pacing factor: %f\"\n                      \", max pacer queue length:%\" PRIi64\n                     \", ALR bandwidth usage percent: %d\"\n                     \", ALR start budget level percent: %d\"\n                     \", ALR end budget level percent: %d\"\n                     \", ALR experiment group ID: %d\"\n                     \", ALR timeout: %d\",\n                     settings.pacing_factor,\n                     settings.max_paced_queue_time,\n                     settings.alr_bandwidth_usage_percent,\n                     settings.alr_start_budget_level_percent,\n                     settings.alr_stop_budget_level_percent,\n                     settings.group_id,\n                     settings.alr_timeout);\n  } else {\n    MS_DEBUG_TAG(bwe, \"Failed to parse ALR experiment: %s\", experiment_name);\n  }\n\n  return ret;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/alr_experiment.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_\n#define RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n\nnamespace webrtc {\nstruct AlrExperimentSettings {\n public:\n  float pacing_factor;\n  int64_t max_paced_queue_time;\n  int alr_bandwidth_usage_percent;\n  int alr_start_budget_level_percent;\n  int alr_stop_budget_level_percent;\n  int alr_timeout;\n  // Will be sent to the receive side for stats slicing.\n  // Can be 0..6, because it's sent as a 3 bits value and there's also\n  // reserved value to indicate absence of experiment.\n  int group_id;\n\n  static const char kScreenshareProbingBweExperimentName[];\n  static const char kStrictPacingAndProbingExperimentName[];\n  static absl::optional<AlrExperimentSettings> CreateFromFieldTrial(\n      const char* experiment_name);\n  static absl::optional<AlrExperimentSettings> CreateFromFieldTrial(\n      const WebRtcKeyValueConfig& key_value_config,\n      const char* experiment_name);\n  static bool MaxOneFieldTrialEnabled();\n  static bool MaxOneFieldTrialEnabled(\n      const WebRtcKeyValueConfig& key_value_config);\n\n private:\n  AlrExperimentSettings() = default;\n};\n}  // namespace webrtc\n\n#endif  // RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_parser.cc",
    "content": "/*\n *  Copyright 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#define MS_CLASS \"webrtc::FieldTrialParser\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\n#include \"Logger.hpp\"\n\n#include <algorithm>\n#include <map>\n#include <type_traits>\n#include <utility>\n\nnamespace webrtc {\nnamespace {\n\nint FindOrEnd(std::string str, size_t start, char delimiter) {\n  size_t pos = str.find(delimiter, start);\n  pos = (pos == std::string::npos) ? str.length() : pos;\n  return static_cast<int>(pos);\n}\n}  // namespace\n\nFieldTrialParameterInterface::FieldTrialParameterInterface(std::string key)\n    : key_(key) {}\nFieldTrialParameterInterface::~FieldTrialParameterInterface() {\n  // RTC_DCHECK(used_) << \"Field trial parameter with key: '\" << key_\n                    // << \"' never used.\";\n}\n\nvoid ParseFieldTrial(\n    std::initializer_list<FieldTrialParameterInterface*> fields,\n    std::string trial_string) {\n  std::map<std::string, FieldTrialParameterInterface*> field_map;\n  FieldTrialParameterInterface* keyless_field = nullptr;\n  for (FieldTrialParameterInterface* field : fields) {\n    field->MarkAsUsed();\n    if (!field->sub_parameters_.empty()) {\n      for (FieldTrialParameterInterface* sub_field : field->sub_parameters_) {\n        // RTC_DCHECK(!sub_field->key_.empty());\n        sub_field->MarkAsUsed();\n        field_map[sub_field->key_] = sub_field;\n      }\n      continue;\n    }\n\n    if (field->key_.empty()) {\n      // RTC_DCHECK(!keyless_field);\n      keyless_field = field;\n    } else {\n      field_map[field->key_] = field;\n    }\n  }\n\n  size_t i = 0;\n  while (i < trial_string.length()) {\n    int val_end = FindOrEnd(trial_string, i, ',');\n    int colon_pos = FindOrEnd(trial_string, i, ':');\n    int key_end = std::min(val_end, colon_pos);\n    int val_begin = key_end + 1;\n    std::string key = trial_string.substr(i, key_end - i);\n    absl::optional<std::string> opt_value;\n    if (val_end >= val_begin)\n      opt_value = trial_string.substr(val_begin, val_end - val_begin);\n    i = val_end + 1;\n    auto field = field_map.find(key);\n    if (field != field_map.end()) {\n      if (!field->second->Parse(std::move(opt_value))) {\n        MS_WARN_TAG(bwe, \"Failed to read field with key: '%s' in trial: \\\"%s\\\"\",\n           key.c_str(), trial_string.c_str());\n      }\n    } else if (!opt_value && keyless_field && !key.empty()) {\n      if (!keyless_field->Parse(key)) {\n        MS_WARN_TAG(bwe, \"Failed to read empty key field with value: '%s' in trial: \\\"%s\\\"\",\n           key.c_str(), trial_string.c_str());\n      }\n    } else {\n      MS_DEBUG_TAG(bwe, \"No field with key: '%s' (found in trial: \\\"%s\\\")\", key.c_str(), trial_string.c_str());\n    }\n  }\n\n  for (FieldTrialParameterInterface* field : fields) {\n    field->ParseDone();\n  }\n}\n\ntemplate <>\nabsl::optional<bool> ParseTypedParameter<bool>(std::string str) {\n  if (str == \"true\" || str == \"1\") {\n    return true;\n  } else if (str == \"false\" || str == \"0\") {\n    return false;\n  }\n  return absl::nullopt;\n}\n\ntemplate <>\nabsl::optional<double> ParseTypedParameter<double>(std::string str) {\n  double value;\n  char unit[2]{0, 0};\n  if (sscanf(str.c_str(), \"%lf%1s\", &value, unit) >= 1) {\n    if (unit[0] == '%')\n      return value / 100;\n    return value;\n  } else {\n    return absl::nullopt;\n  }\n}\n\ntemplate <>\nabsl::optional<int> ParseTypedParameter<int>(std::string str) {\n  int value;\n  if (sscanf(str.c_str(), \"%i\", &value) == 1) {\n    return value;\n  } else {\n    return absl::nullopt;\n  }\n}\n\ntemplate <>\nabsl::optional<std::string> ParseTypedParameter<std::string>(std::string str) {\n  return std::move(str);\n}\n\nFieldTrialFlag::FieldTrialFlag(std::string key) : FieldTrialFlag(key, false) {}\n\nFieldTrialFlag::FieldTrialFlag(std::string key, bool default_value)\n    : FieldTrialParameterInterface(key), value_(default_value) {}\n\nbool FieldTrialFlag::Get() const {\n  return value_;\n}\n\nwebrtc::FieldTrialFlag::operator bool() const {\n  return value_;\n}\n\nbool FieldTrialFlag::Parse(absl::optional<std::string> str_value) {\n  // Only set the flag if there is no argument provided.\n  if (str_value) {\n    absl::optional<bool> opt_value = ParseTypedParameter<bool>(*str_value);\n    if (!opt_value)\n      return false;\n    value_ = *opt_value;\n  } else {\n    value_ = true;\n  }\n  return true;\n}\n\nAbstractFieldTrialEnum::AbstractFieldTrialEnum(\n    std::string key,\n    int default_value,\n    std::map<std::string, int> mapping)\n    : FieldTrialParameterInterface(key),\n      value_(default_value),\n      enum_mapping_(mapping) {\n  for (auto& key_val : enum_mapping_)\n    valid_values_.insert(key_val.second);\n}\nAbstractFieldTrialEnum::AbstractFieldTrialEnum(const AbstractFieldTrialEnum&) =\n    default;\nAbstractFieldTrialEnum::~AbstractFieldTrialEnum() = default;\n\nbool AbstractFieldTrialEnum::Parse(absl::optional<std::string> str_value) {\n  if (str_value) {\n    auto it = enum_mapping_.find(*str_value);\n    if (it != enum_mapping_.end()) {\n      value_ = it->second;\n      return true;\n    }\n    absl::optional<int> value = ParseTypedParameter<int>(*str_value);\n    if (value.has_value() &&\n        (valid_values_.find(*value) != valid_values_.end())) {\n      value_ = *value;\n      return true;\n    }\n  }\n  return false;\n}\n\ntemplate class FieldTrialParameter<bool>;\ntemplate class FieldTrialParameter<double>;\ntemplate class FieldTrialParameter<int>;\ntemplate class FieldTrialParameter<std::string>;\n\ntemplate class FieldTrialConstrained<double>;\ntemplate class FieldTrialConstrained<int>;\n\ntemplate class FieldTrialOptional<double>;\ntemplate class FieldTrialOptional<int>;\ntemplate class FieldTrialOptional<bool>;\ntemplate class FieldTrialOptional<std::string>;\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_parser.h",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_\n#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_\n\n#include <absl/types/optional.h>\n#include <stdint.h>\n#include <initializer_list>\n#include <map>\n#include <set>\n#include <string>\n#include <vector>\n\n// Field trial parser functionality. Provides funcitonality to parse field trial\n// argument strings in key:value format. Each parameter is described using\n// key:value, parameters are separated with a ,. Values can't include the comma\n// character, since there's no quote facility. For most types, white space is\n// ignored. Parameters are declared with a given type for which an\n// implementation of ParseTypedParameter should be provided. The\n// ParseTypedParameter implementation is given whatever is between the : and the\n// ,. If the key is provided without : a FieldTrialOptional will use nullopt.\n\n// Example string: \"my_optional,my_int:3,my_string:hello\"\n\n// For further description of usage and behavior, see the examples in the unit\n// tests.\n\nnamespace webrtc {\nclass FieldTrialParameterInterface {\n public:\n  virtual ~FieldTrialParameterInterface();\n  std::string key() const { return key_; }\n\n protected:\n  // Protected to allow implementations to provide assignment and copy.\n  FieldTrialParameterInterface(const FieldTrialParameterInterface&) = default;\n  FieldTrialParameterInterface& operator=(const FieldTrialParameterInterface&) =\n      default;\n  explicit FieldTrialParameterInterface(std::string key);\n  friend void ParseFieldTrial(\n      std::initializer_list<FieldTrialParameterInterface*> fields,\n      std::string raw_string);\n  void MarkAsUsed() { used_ = true; }\n  virtual bool Parse(absl::optional<std::string> str_value) = 0;\n\n  virtual void ParseDone() {}\n\n  std::vector<FieldTrialParameterInterface*> sub_parameters_;\n\n private:\n  std::string key_;\n  bool used_ = false;\n};\n\n// ParseFieldTrial function parses the given string and fills the given fields\n// with extracted values if available.\nvoid ParseFieldTrial(\n    std::initializer_list<FieldTrialParameterInterface*> fields,\n    std::string raw_string);\n\n// Specialize this in code file for custom types. Should return absl::nullopt if\n// the given string cannot be properly parsed.\ntemplate <typename T>\nabsl::optional<T> ParseTypedParameter(std::string);\n\n// This class uses the ParseTypedParameter function to implement a parameter\n// implementation with an enforced default value.\ntemplate <typename T>\nclass FieldTrialParameter : public FieldTrialParameterInterface {\n public:\n  FieldTrialParameter(std::string key, T default_value)\n      : FieldTrialParameterInterface(key), value_(default_value) {}\n  T Get() const { return value_; }\n  operator T() const { return Get(); }\n  const T* operator->() const { return &value_; }\n\n  void SetForTest(T value) { value_ = value; }\n\n protected:\n  bool Parse(absl::optional<std::string> str_value) override {\n    if (str_value) {\n      absl::optional<T> value = ParseTypedParameter<T>(*str_value);\n      if (value.has_value()) {\n        value_ = value.value();\n        return true;\n      }\n    }\n    return false;\n  }\n\n private:\n  T value_;\n};\n\n// This class uses the ParseTypedParameter function to implement a parameter\n// implementation with an enforced default value and a range constraint. Values\n// outside the configured range will be ignored.\ntemplate <typename T>\nclass FieldTrialConstrained : public FieldTrialParameterInterface {\n public:\n  FieldTrialConstrained(std::string key,\n                        T default_value,\n                        absl::optional<T> lower_limit,\n                        absl::optional<T> upper_limit)\n      : FieldTrialParameterInterface(key),\n        value_(default_value),\n        lower_limit_(lower_limit),\n        upper_limit_(upper_limit) {}\n  T Get() const { return value_; }\n  operator T() const { return Get(); }\n  const T* operator->() const { return &value_; }\n\n protected:\n  bool Parse(absl::optional<std::string> str_value) override {\n    if (str_value) {\n      absl::optional<T> value = ParseTypedParameter<T>(*str_value);\n      if (value && (!lower_limit_ || *value >= *lower_limit_) &&\n          (!upper_limit_ || *value <= *upper_limit_)) {\n        value_ = *value;\n        return true;\n      }\n    }\n    return false;\n  }\n\n private:\n  T value_;\n  absl::optional<T> lower_limit_;\n  absl::optional<T> upper_limit_;\n};\n\nclass AbstractFieldTrialEnum : public FieldTrialParameterInterface {\n public:\n  AbstractFieldTrialEnum(std::string key,\n                         int default_value,\n                         std::map<std::string, int> mapping);\n  ~AbstractFieldTrialEnum() override;\n  AbstractFieldTrialEnum(const AbstractFieldTrialEnum&);\n\n protected:\n  bool Parse(absl::optional<std::string> str_value) override;\n\n protected:\n  int value_;\n  std::map<std::string, int> enum_mapping_;\n  std::set<int> valid_values_;\n};\n\n// The FieldTrialEnum class can be used to quickly define a parser for a\n// specific enum. It handles values provided as integers and as strings if a\n// mapping is provided.\ntemplate <typename T>\nclass FieldTrialEnum : public AbstractFieldTrialEnum {\n public:\n  FieldTrialEnum(std::string key,\n                 T default_value,\n                 std::map<std::string, T> mapping)\n      : AbstractFieldTrialEnum(key,\n                               static_cast<int>(default_value),\n                               ToIntMap(mapping)) {}\n  T Get() const { return static_cast<T>(value_); }\n  operator T() const { return Get(); }\n\n private:\n  static std::map<std::string, int> ToIntMap(std::map<std::string, T> mapping) {\n    std::map<std::string, int> res;\n    for (const auto& it : mapping)\n      res[it.first] = static_cast<int>(it.second);\n    return res;\n  }\n};\n\n// This class uses the ParseTypedParameter function to implement an optional\n// parameter implementation that can default to absl::nullopt.\ntemplate <typename T>\nclass FieldTrialOptional : public FieldTrialParameterInterface {\n public:\n  explicit FieldTrialOptional(std::string key)\n      : FieldTrialParameterInterface(key) {}\n  FieldTrialOptional(std::string key, absl::optional<T> default_value)\n      : FieldTrialParameterInterface(key), value_(default_value) {}\n  absl::optional<T> GetOptional() const { return value_; }\n  const T& Value() const { return value_.value(); }\n  const T& operator*() const { return value_.value(); }\n  const T* operator->() const { return &value_.value(); }\n  explicit operator bool() const { return value_.has_value(); }\n\n protected:\n  bool Parse(absl::optional<std::string> str_value) override {\n    if (str_value) {\n      absl::optional<T> value = ParseTypedParameter<T>(*str_value);\n      if (!value.has_value())\n        return false;\n      value_ = value.value();\n    } else {\n      value_ = absl::nullopt;\n    }\n    return true;\n  }\n\n private:\n  absl::optional<T> value_;\n};\n\n// Equivalent to a FieldTrialParameter<bool> in the case that both key and value\n// are present. If key is missing, evaluates to false. If key is present, but no\n// explicit value is provided, the flag evaluates to true.\nclass FieldTrialFlag : public FieldTrialParameterInterface {\n public:\n  explicit FieldTrialFlag(std::string key);\n  FieldTrialFlag(std::string key, bool default_value);\n  bool Get() const;\n  operator bool() const;\n\n protected:\n  bool Parse(absl::optional<std::string> str_value) override;\n\n private:\n  bool value_;\n};\n\n// Accepts true, false, else parsed with sscanf %i, true if != 0.\nextern template class FieldTrialParameter<bool>;\n// Interpreted using sscanf %lf.\nextern template class FieldTrialParameter<double>;\n// Interpreted using sscanf %i.\nextern template class FieldTrialParameter<int>;\n// Using the given value as is.\nextern template class FieldTrialParameter<std::string>;\n\nextern template class FieldTrialConstrained<double>;\nextern template class FieldTrialConstrained<int>;\n\nextern template class FieldTrialOptional<double>;\nextern template class FieldTrialOptional<int>;\nextern template class FieldTrialOptional<bool>;\nextern template class FieldTrialOptional<std::string>;\n\n}  // namespace webrtc\n\n#endif  // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_units.cc",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#include \"rtc_base/experiments/field_trial_units.h\"\n\n#include <absl/types/optional.h>\n#include <stdio.h>\n#include <limits>\n#include <string>\n\n// Large enough to fit \"seconds\", the longest supported unit name.\n#define RTC_TRIAL_UNIT_LENGTH_STR \"7\"\n#define RTC_TRIAL_UNIT_SIZE 8\n\nnamespace webrtc {\nnamespace {\n\nstruct ValueWithUnit {\n  double value;\n  std::string unit;\n};\n\nabsl::optional<ValueWithUnit> ParseValueWithUnit(std::string str) {\n  if (str == \"inf\") {\n    return ValueWithUnit{std::numeric_limits<double>::infinity(), \"\"};\n  } else if (str == \"-inf\") {\n    return ValueWithUnit{-std::numeric_limits<double>::infinity(), \"\"};\n  } else {\n    double double_val;\n    char unit_char[RTC_TRIAL_UNIT_SIZE];\n    unit_char[0] = 0;\n    if (sscanf(str.c_str(), \"%lf%\" RTC_TRIAL_UNIT_LENGTH_STR \"s\", &double_val,\n               unit_char) >= 1) {\n      return ValueWithUnit{double_val, unit_char};\n    }\n  }\n  return absl::nullopt;\n}\n}  // namespace\n\ntemplate <>\nabsl::optional<DataRate> ParseTypedParameter<DataRate>(std::string str) {\n  absl::optional<ValueWithUnit> result = ParseValueWithUnit(str);\n  if (result) {\n    if (result->unit.empty() || result->unit == \"kbps\") {\n      return DataRate::kbps(result->value);\n    } else if (result->unit == \"bps\") {\n      return DataRate::bps(result->value);\n    }\n  }\n  return absl::nullopt;\n}\n\ntemplate <>\nabsl::optional<DataSize> ParseTypedParameter<DataSize>(std::string str) {\n  absl::optional<ValueWithUnit> result = ParseValueWithUnit(str);\n  if (result) {\n    if (result->unit.empty() || result->unit == \"bytes\")\n      return DataSize::bytes(result->value);\n  }\n  return absl::nullopt;\n}\n\ntemplate <>\nabsl::optional<TimeDelta> ParseTypedParameter<TimeDelta>(std::string str) {\n  absl::optional<ValueWithUnit> result = ParseValueWithUnit(str);\n  if (result) {\n    if (result->unit == \"s\" || result->unit == \"seconds\") {\n      return TimeDelta::seconds(result->value);\n    } else if (result->unit == \"us\") {\n      return TimeDelta::us(result->value);\n    } else if (result->unit.empty() || result->unit == \"ms\") {\n      return TimeDelta::ms(result->value);\n    }\n  }\n  return absl::nullopt;\n}\n\ntemplate class FieldTrialParameter<DataRate>;\ntemplate class FieldTrialParameter<DataSize>;\ntemplate class FieldTrialParameter<TimeDelta>;\n\ntemplate class FieldTrialConstrained<DataRate>;\ntemplate class FieldTrialConstrained<DataSize>;\ntemplate class FieldTrialConstrained<TimeDelta>;\n\ntemplate class FieldTrialOptional<DataRate>;\ntemplate class FieldTrialOptional<DataSize>;\ntemplate class FieldTrialOptional<TimeDelta>;\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_units.h",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_\n#define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_\n\n#include \"api/units/data_rate.h\"\n#include \"api/units/data_size.h\"\n#include \"api/units/time_delta.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n\nnamespace webrtc {\nextern template class FieldTrialParameter<DataRate>;\nextern template class FieldTrialParameter<DataSize>;\nextern template class FieldTrialParameter<TimeDelta>;\n\nextern template class FieldTrialConstrained<DataRate>;\nextern template class FieldTrialConstrained<DataSize>;\nextern template class FieldTrialConstrained<TimeDelta>;\n\nextern template class FieldTrialOptional<DataRate>;\nextern template class FieldTrialOptional<DataSize>;\nextern template class FieldTrialOptional<TimeDelta>;\n}  // namespace webrtc\n\n#endif  // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/rate_control_settings.cc",
    "content": "/*\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"rtc_base/experiments/rate_control_settings.h\"\n#include \"api/transport/field_trial_based_config.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include <inttypes.h>\n#include <stdio.h>\n#include <string>\n\nnamespace webrtc {\n\nnamespace {\n\nconst int kDefaultAcceptedQueueMs = 250;\n\nconst int kDefaultMinPushbackTargetBitrateBps = 30000;\n\n}  // namespace\n\nRateControlSettings::RateControlSettings(\n    const WebRtcKeyValueConfig* const key_value_config)\n    : congestion_window_(\"QueueSize\"),\n      congestion_window_pushback_(\"MinBitrate\"),\n      pacing_factor_(\"pacing_factor\"),\n      alr_probing_(\"alr_probing\", false),\n      probe_max_allocation_(\"probe_max_allocation\", true),\n      bitrate_adjuster_(\"bitrate_adjuster\", false),\n      adjuster_use_headroom_(\"adjuster_use_headroom\", false) {\n  ParseFieldTrial({&congestion_window_, &congestion_window_pushback_},\n                  key_value_config->Lookup(\"WebRTC-CongestionWindow\"));\n  ParseFieldTrial(\n      {&pacing_factor_, &alr_probing_,\n       &probe_max_allocation_, &bitrate_adjuster_, &adjuster_use_headroom_},\n      key_value_config->Lookup(\"WebRTC-VideoRateControl\"));\n}\n\nRateControlSettings::~RateControlSettings() = default;\nRateControlSettings::RateControlSettings(RateControlSettings&&) = default;\n\nRateControlSettings RateControlSettings::ParseFromFieldTrials() {\n  FieldTrialBasedConfig field_trial_config;\n  return RateControlSettings(&field_trial_config);\n}\n\nRateControlSettings RateControlSettings::ParseFromKeyValueConfig(\n    const WebRtcKeyValueConfig* const key_value_config) {\n  FieldTrialBasedConfig field_trial_config;\n  return RateControlSettings(key_value_config ? key_value_config\n                                              : &field_trial_config);\n}\n\nbool RateControlSettings::UseCongestionWindow() const {\n  return static_cast<bool>(congestion_window_);\n}\n\nint64_t RateControlSettings::GetCongestionWindowAdditionalTimeMs() const {\n  return congestion_window_.GetOptional().value_or(kDefaultAcceptedQueueMs);\n}\n\nbool RateControlSettings::UseCongestionWindowPushback() const {\n  return congestion_window_ && congestion_window_pushback_;\n}\n\nuint32_t RateControlSettings::CongestionWindowMinPushbackTargetBitrateBps()\n    const {\n  return congestion_window_pushback_.GetOptional().value_or(\n      kDefaultMinPushbackTargetBitrateBps);\n}\n\nabsl::optional<double> RateControlSettings::GetPacingFactor() const {\n  return pacing_factor_.GetOptional();\n}\n\nbool RateControlSettings::UseAlrProbing() const {\n  return alr_probing_.Get();\n}\n\nbool RateControlSettings::TriggerProbeOnMaxAllocatedBitrateChange() const {\n  return probe_max_allocation_.Get();\n}\n\nbool RateControlSettings::UseEncoderBitrateAdjuster() const {\n  return bitrate_adjuster_.Get();\n}\n\nbool RateControlSettings::BitrateAdjusterCanUseNetworkHeadroom() const {\n  return adjuster_use_headroom_.Get();\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/rate_control_settings.h",
    "content": "/*\n *  Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_\n#define RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_\n\n#include \"api/transport/webrtc_key_value_config.h\"\n#include \"rtc_base/experiments/field_trial_parser.h\"\n// #include \"rtc_base/experiments/field_trial_units.h\"\n\n#include <absl/types/optional.h>\n\nnamespace webrtc {\n\nclass RateControlSettings final {\n public:\n  ~RateControlSettings();\n  RateControlSettings(RateControlSettings&&);\n\n  static RateControlSettings ParseFromFieldTrials();\n  static RateControlSettings ParseFromKeyValueConfig(\n      const WebRtcKeyValueConfig* const key_value_config);\n\n  // When CongestionWindowPushback is enabled, the pacer is oblivious to\n  // the congestion window. The relation between outstanding data and\n  // the congestion window affects encoder allocations directly.\n  bool UseCongestionWindow() const;\n  int64_t GetCongestionWindowAdditionalTimeMs() const;\n  bool UseCongestionWindowPushback() const;\n  uint32_t CongestionWindowMinPushbackTargetBitrateBps() const;\n\n  absl::optional<double> GetPacingFactor() const;\n  bool UseAlrProbing() const;\n\n  bool TriggerProbeOnMaxAllocatedBitrateChange() const;\n  bool UseEncoderBitrateAdjuster() const;\n  bool BitrateAdjusterCanUseNetworkHeadroom() const;\n\n private:\n  explicit RateControlSettings(\n      const WebRtcKeyValueConfig* const key_value_config);\n\n  double GetSimulcastScreenshareHysteresisFactor() const;\n\n  FieldTrialOptional<int> congestion_window_;\n  FieldTrialOptional<int> congestion_window_pushback_;\n  FieldTrialOptional<double> pacing_factor_;\n  FieldTrialParameter<bool> alr_probing_;\n  FieldTrialParameter<bool> probe_max_allocation_;\n  FieldTrialParameter<bool> bitrate_adjuster_;\n  FieldTrialParameter<bool> adjuster_use_headroom_;\n};\n\n}  // namespace webrtc\n\n#endif  // RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/network/sent_packet.cc",
    "content": "/*\n *  Copyright 2018 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"rtc_base/network/sent_packet.h\"\n\nnamespace rtc {\n\nPacketInfo::PacketInfo() = default;\nPacketInfo::PacketInfo(const PacketInfo& info) = default;\nPacketInfo::~PacketInfo() = default;\n\nSentPacket::SentPacket() = default;\nSentPacket::SentPacket(int64_t packet_id, int64_t send_time_ms)\n    : packet_id(packet_id), send_time_ms(send_time_ms) {}\nSentPacket::SentPacket(int64_t packet_id,\n                       int64_t send_time_ms,\n                       const rtc::PacketInfo& info)\n    : packet_id(packet_id), send_time_ms(send_time_ms), info(info) {}\n\n}  // namespace rtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/network/sent_packet.h",
    "content": "/*\n *  Copyright 2018 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_NETWORK_SENT_PACKET_H_\n#define RTC_BASE_NETWORK_SENT_PACKET_H_\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace rtc {\n\nenum class PacketType {\n  kUnknown,\n  kData,\n  kIceConnectivityCheck,\n  kIceConnectivityCheckResponse,\n  kStunMessage,\n  kTurnMessage,\n};\n\nenum class PacketInfoProtocolType {\n  kUnknown,\n  kUdp,\n  kTcp,\n  kSsltcp,\n  kTls,\n};\n\nstruct PacketInfo {\n  PacketInfo();\n  PacketInfo(const PacketInfo& info);\n  ~PacketInfo();\n\n  bool included_in_feedback = false;\n  bool included_in_allocation = false;\n  PacketType packet_type = PacketType::kUnknown;\n  PacketInfoProtocolType protocol = PacketInfoProtocolType::kUnknown;\n  // A unique id assigned by the network manager, and absl::nullopt if not set.\n  absl::optional<uint16_t> network_id;\n  size_t packet_size_bytes = 0;\n  size_t turn_overhead_bytes = 0;\n  size_t ip_overhead_bytes = 0;\n};\n\nstruct SentPacket {\n  SentPacket();\n  SentPacket(int64_t packet_id, int64_t send_time_ms);\n  SentPacket(int64_t packet_id,\n             int64_t send_time_ms,\n             const rtc::PacketInfo& info);\n\n  int64_t packet_id = -1;\n  int64_t send_time_ms = -1;\n  rtc::PacketInfo info;\n};\n\n}  // namespace rtc\n\n#endif  // RTC_BASE_NETWORK_SENT_PACKET_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/percentile_filter.h",
    "content": "/*\n *  Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_\n#define RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_\n\n#include <stdint.h>\n#include <iterator>\n#include <set>\n\nnamespace webrtc {\n\n// Class to efficiently get the percentile value from a group of observations.\n// The percentile is the value below which a given percentage of the\n// observations fall.\ntemplate <typename T>\nclass PercentileFilter {\n public:\n  // Construct filter. |percentile| should be between 0 and 1.\n  explicit PercentileFilter(float percentile);\n\n  // Insert one observation. The complexity of this operation is logarithmic in\n  // the size of the container.\n  void Insert(const T& value);\n\n  // Remove one observation or return false if |value| doesn't exist in the\n  // container. The complexity of this operation is logarithmic in the size of\n  // the container.\n  bool Erase(const T& value);\n\n  // Get the percentile value. The complexity of this operation is constant.\n  T GetPercentileValue() const;\n\n  // Removes all the stored observations.\n  void Reset();\n\n private:\n  // Update iterator and index to point at target percentile value.\n  void UpdatePercentileIterator();\n\n  const float percentile_;\n  std::multiset<T> set_;\n  // Maintain iterator and index of current target percentile value.\n  typename std::multiset<T>::iterator percentile_it_;\n  int64_t percentile_index_;\n};\n\ntemplate <typename T>\nPercentileFilter<T>::PercentileFilter(float percentile)\n    : percentile_(percentile),\n      percentile_it_(set_.begin()),\n      percentile_index_(0) {\n  // RTC_CHECK_GE(percentile, 0.0f);\n  // RTC_CHECK_LE(percentile, 1.0f);\n}\n\ntemplate <typename T>\nvoid PercentileFilter<T>::Insert(const T& value) {\n  // Insert element at the upper bound.\n  set_.insert(value);\n  if (set_.size() == 1u) {\n    // First element inserted - initialize percentile iterator and index.\n    percentile_it_ = set_.begin();\n    percentile_index_ = 0;\n  } else if (value < *percentile_it_) {\n    // If new element is before us, increment |percentile_index_|.\n    ++percentile_index_;\n  }\n  UpdatePercentileIterator();\n}\n\ntemplate <typename T>\nbool PercentileFilter<T>::Erase(const T& value) {\n  typename std::multiset<T>::const_iterator it = set_.lower_bound(value);\n  // Ignore erase operation if the element is not present in the current set.\n  if (it == set_.end() || *it != value)\n    return false;\n  if (it == percentile_it_) {\n    // If same iterator, update to the following element. Index is not\n    // affected.\n    percentile_it_ = set_.erase(it);\n  } else {\n    set_.erase(it);\n    // If erased element was before us, decrement |percentile_index_|.\n    if (value <= *percentile_it_)\n      --percentile_index_;\n  }\n  UpdatePercentileIterator();\n  return true;\n}\n\ntemplate <typename T>\nvoid PercentileFilter<T>::UpdatePercentileIterator() {\n  if (set_.empty())\n    return;\n  const int64_t index = static_cast<int64_t>(percentile_ * (set_.size() - 1));\n  std::advance(percentile_it_, index - percentile_index_);\n  percentile_index_ = index;\n}\n\ntemplate <typename T>\nT PercentileFilter<T>::GetPercentileValue() const {\n  return set_.empty() ? 0 : *percentile_it_;\n}\n\ntemplate <typename T>\nvoid PercentileFilter<T>::Reset() {\n  set_.clear();\n  percentile_it_ = set_.begin();\n  percentile_index_ = 0;\n}\n}  // namespace webrtc\n\n#endif  // RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_compare.h",
    "content": "/*\n *  Copyright 2016 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n// This file defines six constexpr functions:\n//\n//   rtc::SafeEq  // ==\n//   rtc::SafeNe  // !=\n//   rtc::SafeLt  // <\n//   rtc::SafeLe  // <=\n//   rtc::SafeGt  // >\n//   rtc::SafeGe  // >=\n//\n// They each accept two arguments of arbitrary types, and in almost all cases,\n// they simply call the appropriate comparison operator. However, if both\n// arguments are integers, they don't compare them using C++'s quirky rules,\n// but instead adhere to the true mathematical definitions. It is as if the\n// arguments were first converted to infinite-range signed integers, and then\n// compared, although of course nothing expensive like that actually takes\n// place. In practice, for signed/signed and unsigned/unsigned comparisons and\n// some mixed-signed comparisons with a compile-time constant, the overhead is\n// zero; in the remaining cases, it is just a few machine instructions (no\n// branches).\n\n#ifndef RTC_BASE_NUMERICS_SAFE_COMPARE_H_\n#define RTC_BASE_NUMERICS_SAFE_COMPARE_H_\n\n#include \"rtc_base/type_traits.h\"\n\n#include <stddef.h>\n#include <stdint.h>\n#include <type_traits>\n#include <utility>\n\nnamespace rtc {\n\nnamespace safe_cmp_impl {\n\ntemplate <size_t N>\nstruct LargerIntImpl : std::false_type {};\ntemplate <>\nstruct LargerIntImpl<sizeof(int8_t)> : std::true_type {\n  using type = int16_t;\n};\ntemplate <>\nstruct LargerIntImpl<sizeof(int16_t)> : std::true_type {\n  using type = int32_t;\n};\ntemplate <>\nstruct LargerIntImpl<sizeof(int32_t)> : std::true_type {\n  using type = int64_t;\n};\n\n// LargerInt<T1, T2>::value is true iff there's a signed type that's larger\n// than T1 (and no larger than the larger of T2 and int*, for performance\n// reasons); and if there is such a type, LargerInt<T1, T2>::type is an alias\n// for it.\ntemplate <typename T1, typename T2>\nstruct LargerInt\n    : LargerIntImpl<sizeof(T1) < sizeof(T2) || sizeof(T1) < sizeof(int*)\n                        ? sizeof(T1)\n                        : 0> {};\n\ntemplate <typename T>\nconstexpr typename std::make_unsigned<T>::type MakeUnsigned(T a) {\n  return static_cast<typename std::make_unsigned<T>::type>(a);\n}\n\n// Overload for when both T1 and T2 have the same signedness.\ntemplate <typename Op,\n          typename T1,\n          typename T2,\n          typename std::enable_if<std::is_signed<T1>::value ==\n                                  std::is_signed<T2>::value>::type* = nullptr>\nconstexpr bool Cmp(T1 a, T2 b) {\n  return Op::Op(a, b);\n}\n\n// Overload for signed - unsigned comparison that can be promoted to a bigger\n// signed type.\ntemplate <typename Op,\n          typename T1,\n          typename T2,\n          typename std::enable_if<std::is_signed<T1>::value &&\n                                  std::is_unsigned<T2>::value &&\n                                  LargerInt<T2, T1>::value>::type* = nullptr>\nconstexpr bool Cmp(T1 a, T2 b) {\n  return Op::Op(a, static_cast<typename LargerInt<T2, T1>::type>(b));\n}\n\n// Overload for unsigned - signed comparison that can be promoted to a bigger\n// signed type.\ntemplate <typename Op,\n          typename T1,\n          typename T2,\n          typename std::enable_if<std::is_unsigned<T1>::value &&\n                                  std::is_signed<T2>::value &&\n                                  LargerInt<T1, T2>::value>::type* = nullptr>\nconstexpr bool Cmp(T1 a, T2 b) {\n  return Op::Op(static_cast<typename LargerInt<T1, T2>::type>(a), b);\n}\n\n// Overload for signed - unsigned comparison that can't be promoted to a bigger\n// signed type.\ntemplate <typename Op,\n          typename T1,\n          typename T2,\n          typename std::enable_if<std::is_signed<T1>::value &&\n                                  std::is_unsigned<T2>::value &&\n                                  !LargerInt<T2, T1>::value>::type* = nullptr>\nconstexpr bool Cmp(T1 a, T2 b) {\n  return a < 0 ? Op::Op(-1, 0) : Op::Op(safe_cmp_impl::MakeUnsigned(a), b);\n}\n\n// Overload for unsigned - signed comparison that can't be promoted to a bigger\n// signed type.\ntemplate <typename Op,\n          typename T1,\n          typename T2,\n          typename std::enable_if<std::is_unsigned<T1>::value &&\n                                  std::is_signed<T2>::value &&\n                                  !LargerInt<T1, T2>::value>::type* = nullptr>\nconstexpr bool Cmp(T1 a, T2 b) {\n  return b < 0 ? Op::Op(0, -1) : Op::Op(a, safe_cmp_impl::MakeUnsigned(b));\n}\n\n#define RTC_SAFECMP_MAKE_OP(name, op)      \\\n  struct name {                            \\\n    template <typename T1, typename T2>    \\\n    static constexpr bool Op(T1 a, T2 b) { \\\n      return a op b;                       \\\n    }                                      \\\n  };\nRTC_SAFECMP_MAKE_OP(EqOp, ==)\nRTC_SAFECMP_MAKE_OP(NeOp, !=)\nRTC_SAFECMP_MAKE_OP(LtOp, <)\nRTC_SAFECMP_MAKE_OP(LeOp, <=)\nRTC_SAFECMP_MAKE_OP(GtOp, >)\nRTC_SAFECMP_MAKE_OP(GeOp, >=)\n#undef RTC_SAFECMP_MAKE_OP\n\n}  // namespace safe_cmp_impl\n\n#define RTC_SAFECMP_MAKE_FUN(name)                                            \\\n  template <typename T1, typename T2>                                         \\\n  constexpr                                                                   \\\n      typename std::enable_if<IsIntlike<T1>::value && IsIntlike<T2>::value,   \\\n                              bool>::type Safe##name(T1 a, T2 b) {            \\\n    /* Unary plus here turns enums into real integral types. */               \\\n    return safe_cmp_impl::Cmp<safe_cmp_impl::name##Op>(+a, +b);               \\\n  }                                                                           \\\n  template <typename T1, typename T2>                                         \\\n  constexpr                                                                   \\\n      typename std::enable_if<!IsIntlike<T1>::value || !IsIntlike<T2>::value, \\\n                              bool>::type Safe##name(const T1& a,             \\\n                                                     const T2& b) {           \\\n    return safe_cmp_impl::name##Op::Op(a, b);                                 \\\n  }\nRTC_SAFECMP_MAKE_FUN(Eq)\nRTC_SAFECMP_MAKE_FUN(Ne)\nRTC_SAFECMP_MAKE_FUN(Lt)\nRTC_SAFECMP_MAKE_FUN(Le)\nRTC_SAFECMP_MAKE_FUN(Gt)\nRTC_SAFECMP_MAKE_FUN(Ge)\n#undef RTC_SAFECMP_MAKE_FUN\n\n}  // namespace rtc\n\n#endif  // RTC_BASE_NUMERICS_SAFE_COMPARE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_conversions.h",
    "content": "/*\n *  Copyright 2014 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n// Borrowed from Chromium's src/base/numerics/safe_conversions.h.\n\n#ifndef RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_\n#define RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_\n\n#include \"rtc_base/numerics/safe_conversions_impl.h\"\n\n#include <limits>\n\nnamespace rtc {\n\n// Convenience function that returns true if the supplied value is in range\n// for the destination type.\ntemplate <typename Dst, typename Src>\ninline bool IsValueInRangeForNumericType(Src value) {\n  return internal::RangeCheck<Dst>(value) == internal::TYPE_VALID;\n}\n\n// checked_cast<> and dchecked_cast<> are analogous to static_cast<> for\n// numeric types, except that they [D]CHECK that the specified numeric\n// conversion will not overflow or underflow. NaN source will always trigger\n// the [D]CHECK.\ntemplate <typename Dst, typename Src>\ninline Dst checked_cast(Src value) {\n  // RTC_CHECK(IsValueInRangeForNumericType<Dst>(value));\n  return static_cast<Dst>(value);\n}\ntemplate <typename Dst, typename Src>\ninline Dst dchecked_cast(Src value) {\n  // RTC_DCHECK(IsValueInRangeForNumericType<Dst>(value));\n  return static_cast<Dst>(value);\n}\n\n// saturated_cast<> is analogous to static_cast<> for numeric types, except\n// that the specified numeric conversion will saturate rather than overflow or\n// underflow. NaN assignment to an integral will trigger a RTC_CHECK condition.\ntemplate <typename Dst, typename Src>\ninline Dst saturated_cast(Src value) {\n  // Optimization for floating point values, which already saturate.\n  if (std::numeric_limits<Dst>::is_iec559)\n    return static_cast<Dst>(value);\n\n  switch (internal::RangeCheck<Dst>(value)) {\n    case internal::TYPE_VALID:\n      return static_cast<Dst>(value);\n\n    case internal::TYPE_UNDERFLOW:\n      return std::numeric_limits<Dst>::min();\n\n    case internal::TYPE_OVERFLOW:\n      return std::numeric_limits<Dst>::max();\n\n    // Should fail only on attempting to assign NaN to a saturated integer.\n    case internal::TYPE_INVALID:\n      // FATAL();\n      return std::numeric_limits<Dst>::max();\n  }\n\n  // FATAL();\n  return static_cast<Dst>(value);\n}\n\n}  // namespace rtc\n\n#endif  // RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_conversions_impl.h",
    "content": "/*\n *  Copyright 2014 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n// Borrowed from Chromium's src/base/numerics/safe_conversions_impl.h.\n\n#ifndef RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_\n#define RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_\n\n#include <cstddef>\n#include <limits>\n\nnamespace rtc {\nnamespace internal {\n\nenum DstSign { DST_UNSIGNED, DST_SIGNED };\n\nenum SrcSign { SRC_UNSIGNED, SRC_SIGNED };\n\nenum DstRange { OVERLAPS_RANGE, CONTAINS_RANGE };\n\n// Helper templates to statically determine if our destination type can contain\n// all values represented by the source type.\n\ntemplate <typename Dst,\n          typename Src,\n          DstSign IsDstSigned =\n              std::numeric_limits<Dst>::is_signed ? DST_SIGNED : DST_UNSIGNED,\n          SrcSign IsSrcSigned =\n              std::numeric_limits<Src>::is_signed ? SRC_SIGNED : SRC_UNSIGNED>\nstruct StaticRangeCheck {};\n\ntemplate <typename Dst, typename Src>\nstruct StaticRangeCheck<Dst, Src, DST_SIGNED, SRC_SIGNED> {\n  typedef std::numeric_limits<Dst> DstLimits;\n  typedef std::numeric_limits<Src> SrcLimits;\n  // Compare based on max_exponent, which we must compute for integrals.\n  static const size_t kDstMaxExponent =\n      DstLimits::is_iec559 ? DstLimits::max_exponent : (sizeof(Dst) * 8 - 1);\n  static const size_t kSrcMaxExponent =\n      SrcLimits::is_iec559 ? SrcLimits::max_exponent : (sizeof(Src) * 8 - 1);\n  static const DstRange value =\n      kDstMaxExponent >= kSrcMaxExponent ? CONTAINS_RANGE : OVERLAPS_RANGE;\n};\n\ntemplate <typename Dst, typename Src>\nstruct StaticRangeCheck<Dst, Src, DST_UNSIGNED, SRC_UNSIGNED> {\n  static const DstRange value =\n      sizeof(Dst) >= sizeof(Src) ? CONTAINS_RANGE : OVERLAPS_RANGE;\n};\n\ntemplate <typename Dst, typename Src>\nstruct StaticRangeCheck<Dst, Src, DST_SIGNED, SRC_UNSIGNED> {\n  typedef std::numeric_limits<Dst> DstLimits;\n  typedef std::numeric_limits<Src> SrcLimits;\n  // Compare based on max_exponent, which we must compute for integrals.\n  static const size_t kDstMaxExponent =\n      DstLimits::is_iec559 ? DstLimits::max_exponent : (sizeof(Dst) * 8 - 1);\n  static const size_t kSrcMaxExponent = sizeof(Src) * 8;\n  static const DstRange value =\n      kDstMaxExponent >= kSrcMaxExponent ? CONTAINS_RANGE : OVERLAPS_RANGE;\n};\n\ntemplate <typename Dst, typename Src>\nstruct StaticRangeCheck<Dst, Src, DST_UNSIGNED, SRC_SIGNED> {\n  static const DstRange value = OVERLAPS_RANGE;\n};\n\nenum RangeCheckResult {\n  TYPE_VALID = 0,      // Value can be represented by the destination type.\n  TYPE_UNDERFLOW = 1,  // Value would overflow.\n  TYPE_OVERFLOW = 2,   // Value would underflow.\n  TYPE_INVALID = 3     // Source value is invalid (i.e. NaN).\n};\n\n// This macro creates a RangeCheckResult from an upper and lower bound\n// check by taking advantage of the fact that only NaN can be out of range in\n// both directions at once.\n#define BASE_NUMERIC_RANGE_CHECK_RESULT(is_in_upper_bound, is_in_lower_bound) \\\n  RangeCheckResult(((is_in_upper_bound) ? 0 : TYPE_OVERFLOW) |                \\\n                   ((is_in_lower_bound) ? 0 : TYPE_UNDERFLOW))\n\ntemplate <typename Dst,\n          typename Src,\n          DstSign IsDstSigned =\n              std::numeric_limits<Dst>::is_signed ? DST_SIGNED : DST_UNSIGNED,\n          SrcSign IsSrcSigned =\n              std::numeric_limits<Src>::is_signed ? SRC_SIGNED : SRC_UNSIGNED,\n          DstRange IsSrcRangeContained = StaticRangeCheck<Dst, Src>::value>\nstruct RangeCheckImpl {};\n\n// The following templates are for ranges that must be verified at runtime. We\n// split it into checks based on signedness to avoid confusing casts and\n// compiler warnings on signed an unsigned comparisons.\n\n// Dst range always contains the result: nothing to check.\ntemplate <typename Dst, typename Src, DstSign IsDstSigned, SrcSign IsSrcSigned>\nstruct RangeCheckImpl<Dst, Src, IsDstSigned, IsSrcSigned, CONTAINS_RANGE> {\n  static RangeCheckResult Check(Src value) { return TYPE_VALID; }\n};\n\n// Signed to signed narrowing.\ntemplate <typename Dst, typename Src>\nstruct RangeCheckImpl<Dst, Src, DST_SIGNED, SRC_SIGNED, OVERLAPS_RANGE> {\n  static RangeCheckResult Check(Src value) {\n    typedef std::numeric_limits<Dst> DstLimits;\n    return DstLimits::is_iec559\n               ? BASE_NUMERIC_RANGE_CHECK_RESULT(\n                     value <= static_cast<Src>(DstLimits::max()),\n                     value >= static_cast<Src>(DstLimits::max() * -1))\n               : BASE_NUMERIC_RANGE_CHECK_RESULT(\n                     value <= static_cast<Src>(DstLimits::max()),\n                     value >= static_cast<Src>(DstLimits::min()));\n  }\n};\n\n// Unsigned to unsigned narrowing.\ntemplate <typename Dst, typename Src>\nstruct RangeCheckImpl<Dst, Src, DST_UNSIGNED, SRC_UNSIGNED, OVERLAPS_RANGE> {\n  static RangeCheckResult Check(Src value) {\n    typedef std::numeric_limits<Dst> DstLimits;\n    return BASE_NUMERIC_RANGE_CHECK_RESULT(\n        value <= static_cast<Src>(DstLimits::max()), true);\n  }\n};\n\n// Unsigned to signed.\ntemplate <typename Dst, typename Src>\nstruct RangeCheckImpl<Dst, Src, DST_SIGNED, SRC_UNSIGNED, OVERLAPS_RANGE> {\n  static RangeCheckResult Check(Src value) {\n    typedef std::numeric_limits<Dst> DstLimits;\n    return sizeof(Dst) > sizeof(Src)\n               ? TYPE_VALID\n               : BASE_NUMERIC_RANGE_CHECK_RESULT(\n                     value <= static_cast<Src>(DstLimits::max()), true);\n  }\n};\n\n// Signed to unsigned.\ntemplate <typename Dst, typename Src>\nstruct RangeCheckImpl<Dst, Src, DST_UNSIGNED, SRC_SIGNED, OVERLAPS_RANGE> {\n  static RangeCheckResult Check(Src value) {\n    typedef std::numeric_limits<Dst> DstLimits;\n    typedef std::numeric_limits<Src> SrcLimits;\n    // Compare based on max_exponent, which we must compute for integrals.\n    static const size_t kDstMaxExponent = sizeof(Dst) * 8;\n    static const size_t kSrcMaxExponent =\n        SrcLimits::is_iec559 ? SrcLimits::max_exponent : (sizeof(Src) * 8 - 1);\n    return (kDstMaxExponent >= kSrcMaxExponent)\n               ? BASE_NUMERIC_RANGE_CHECK_RESULT(true,\n                                                 value >= static_cast<Src>(0))\n               : BASE_NUMERIC_RANGE_CHECK_RESULT(\n                     value <= static_cast<Src>(DstLimits::max()),\n                     value >= static_cast<Src>(0));\n  }\n};\n\ntemplate <typename Dst, typename Src>\ninline RangeCheckResult RangeCheck(Src value) {\n  static_assert(std::numeric_limits<Src>::is_specialized,\n                \"argument must be numeric\");\n  static_assert(std::numeric_limits<Dst>::is_specialized,\n                \"result must be numeric\");\n  return RangeCheckImpl<Dst, Src>::Check(value);\n}\n\n}  // namespace internal\n}  // namespace rtc\n\n#endif  // RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_minmax.h",
    "content": "/*\n *  Copyright 2017 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n// Minimum and maximum\n// ===================\n//\n//   rtc::SafeMin(x, y)\n//   rtc::SafeMax(x, y)\n//\n// (These are both constexpr.)\n//\n// Accept two arguments of either any two integral or any two floating-point\n// types, and return the smaller and larger value, respectively, with no\n// truncation or wrap-around. If only one of the input types is statically\n// guaranteed to be able to represent the result, the return type is that type;\n// if either one would do, the result type is the smaller type. (One of these\n// two cases always applies.)\n//\n//   * The case with one floating-point and one integral type is not allowed,\n//     because the floating-point type will have greater range, but may not\n//     have sufficient precision to represent the integer value exactly.)\n//\n// Clamp (a.k.a. constrain to a given interval)\n// ============================================\n//\n//   rtc::SafeClamp(x, a, b)\n//\n// Accepts three arguments of any mix of integral types or any mix of\n// floating-point types, and returns the value in the closed interval [a, b]\n// that is closest to x (that is, if x < a it returns a; if x > b it returns b;\n// and if a <= x <= b it returns x). As for SafeMin() and SafeMax(), there is\n// no truncation or wrap-around. The result type\n//\n//   1. is statically guaranteed to be able to represent the result;\n//\n//   2. is no larger than the largest of the three argument types; and\n//\n//   3. has the same signedness as the type of the first argument, if this is\n//      possible without violating the First or Second Law.\n//\n// There is always at least one type that meets criteria 1 and 2. If more than\n// one type meets these criteria equally well, the result type is one of the\n// types that is smallest. Note that unlike SafeMin() and SafeMax(),\n// SafeClamp() will sometimes pick a return type that isn't the type of any of\n// its arguments.\n//\n//   * In this context, a type A is smaller than a type B if it has a smaller\n//     range; that is, if A::max() - A::min() < B::max() - B::min(). For\n//     example, int8_t < int16_t == uint16_t < int32_t, and all integral types\n//     are smaller than all floating-point types.)\n//\n//   * As for SafeMin and SafeMax, mixing integer and floating-point arguments\n//     is not allowed, because floating-point types have greater range than\n//     integer types, but do not have sufficient precision to represent the\n//     values of most integer types exactly.\n//\n// Requesting a specific return type\n// =================================\n//\n// All three functions allow callers to explicitly specify the return type as a\n// template parameter, overriding the default return type. E.g.\n//\n//   rtc::SafeMin<int>(x, y)  // returns an int\n//\n// If the requested type is statically guaranteed to be able to represent the\n// result, then everything's fine, and the return type is as requested. But if\n// the requested type is too small, a static_assert is triggered.\n\n#ifndef RTC_BASE_NUMERICS_SAFE_MINMAX_H_\n#define RTC_BASE_NUMERICS_SAFE_MINMAX_H_\n\n#include \"rtc_base/numerics/safe_compare.h\"\n#include \"rtc_base/type_traits.h\"\n\n#include <limits>\n#include <type_traits>\n\nnamespace rtc {\n\nnamespace safe_minmax_impl {\n\n// Make the range of a type available via something other than a constexpr\n// function, to work around MSVC limitations. See\n// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/\ntemplate <typename T>\nstruct Limits {\n  static constexpr T lowest = std::numeric_limits<T>::lowest();\n  static constexpr T max = std::numeric_limits<T>::max();\n};\n\ntemplate <typename T, bool is_enum = std::is_enum<T>::value>\nstruct UnderlyingType;\n\ntemplate <typename T>\nstruct UnderlyingType<T, false> {\n  using type = T;\n};\n\ntemplate <typename T>\nstruct UnderlyingType<T, true> {\n  using type = typename std::underlying_type<T>::type;\n};\n\n// Given two types T1 and T2, find types that can hold the smallest (in\n// ::min_t) and the largest (in ::max_t) of the two values.\ntemplate <typename T1,\n          typename T2,\n          bool int1 = IsIntlike<T1>::value,\n          bool int2 = IsIntlike<T2>::value>\nstruct MType {\n  static_assert(int1 == int2,\n                \"You may not mix integral and floating-point arguments\");\n};\n\n// Specialization for when neither type is integral (and therefore presumably\n// floating-point).\ntemplate <typename T1, typename T2>\nstruct MType<T1, T2, false, false> {\n  using min_t = typename std::common_type<T1, T2>::type;\n  static_assert(std::is_same<min_t, T1>::value ||\n                    std::is_same<min_t, T2>::value,\n                \"\");\n\n  using max_t = typename std::common_type<T1, T2>::type;\n  static_assert(std::is_same<max_t, T1>::value ||\n                    std::is_same<max_t, T2>::value,\n                \"\");\n};\n\n// Specialization for when both types are integral.\ntemplate <typename T1, typename T2>\nstruct MType<T1, T2, true, true> {\n  // The type with the lowest minimum value. In case of a tie, the type with\n  // the lowest maximum value. In case that too is a tie, the types have the\n  // same range, and we arbitrarily pick T1.\n  using min_t = typename std::conditional<\n      SafeLt(Limits<T1>::lowest, Limits<T2>::lowest),\n      T1,\n      typename std::conditional<\n          SafeGt(Limits<T1>::lowest, Limits<T2>::lowest),\n          T2,\n          typename std::conditional<SafeLe(Limits<T1>::max, Limits<T2>::max),\n                                    T1,\n                                    T2>::type>::type>::type;\n  static_assert(std::is_same<min_t, T1>::value ||\n                    std::is_same<min_t, T2>::value,\n                \"\");\n\n  // The type with the highest maximum value. In case of a tie, the types have\n  // the same range (because in C++, integer types with the same maximum also\n  // have the same minimum).\n  static_assert(SafeNe(Limits<T1>::max, Limits<T2>::max) ||\n                    SafeEq(Limits<T1>::lowest, Limits<T2>::lowest),\n                \"integer types with the same max should have the same min\");\n  using max_t = typename std::\n      conditional<SafeGe(Limits<T1>::max, Limits<T2>::max), T1, T2>::type;\n  static_assert(std::is_same<max_t, T1>::value ||\n                    std::is_same<max_t, T2>::value,\n                \"\");\n};\n\n// A dummy type that we pass around at compile time but never actually use.\n// Declared but not defined.\nstruct DefaultType;\n\n// ::type is A, except we fall back to B if A is DefaultType. We static_assert\n// that the chosen type can hold all values that B can hold.\ntemplate <typename A, typename B>\nstruct TypeOr {\n  using type = typename std::\n      conditional<std::is_same<A, DefaultType>::value, B, A>::type;\n  static_assert(SafeLe(Limits<type>::lowest, Limits<B>::lowest) &&\n                    SafeGe(Limits<type>::max, Limits<B>::max),\n                \"The specified type isn't large enough\");\n  static_assert(IsIntlike<type>::value == IsIntlike<B>::value &&\n                    std::is_floating_point<type>::value ==\n                        std::is_floating_point<type>::value,\n                \"float<->int conversions not allowed\");\n};\n\n}  // namespace safe_minmax_impl\n\ntemplate <\n    typename R = safe_minmax_impl::DefaultType,\n    typename T1 = safe_minmax_impl::DefaultType,\n    typename T2 = safe_minmax_impl::DefaultType,\n    typename R2 = typename safe_minmax_impl::TypeOr<\n        R,\n        typename safe_minmax_impl::MType<\n            typename safe_minmax_impl::UnderlyingType<T1>::type,\n            typename safe_minmax_impl::UnderlyingType<T2>::type>::min_t>::type>\nconstexpr R2 SafeMin(T1 a, T2 b) {\n  static_assert(IsIntlike<T1>::value || std::is_floating_point<T1>::value,\n                \"The first argument must be integral or floating-point\");\n  static_assert(IsIntlike<T2>::value || std::is_floating_point<T2>::value,\n                \"The second argument must be integral or floating-point\");\n  return SafeLt(a, b) ? static_cast<R2>(a) : static_cast<R2>(b);\n}\n\ntemplate <\n    typename R = safe_minmax_impl::DefaultType,\n    typename T1 = safe_minmax_impl::DefaultType,\n    typename T2 = safe_minmax_impl::DefaultType,\n    typename R2 = typename safe_minmax_impl::TypeOr<\n        R,\n        typename safe_minmax_impl::MType<\n            typename safe_minmax_impl::UnderlyingType<T1>::type,\n            typename safe_minmax_impl::UnderlyingType<T2>::type>::max_t>::type>\nconstexpr R2 SafeMax(T1 a, T2 b) {\n  static_assert(IsIntlike<T1>::value || std::is_floating_point<T1>::value,\n                \"The first argument must be integral or floating-point\");\n  static_assert(IsIntlike<T2>::value || std::is_floating_point<T2>::value,\n                \"The second argument must be integral or floating-point\");\n  return SafeGt(a, b) ? static_cast<R2>(a) : static_cast<R2>(b);\n}\n\nnamespace safe_minmax_impl {\n\n// Given three types T, L, and H, let ::type be a suitable return value for\n// SafeClamp(T, L, H). See the docs at the top of this file for details.\ntemplate <typename T,\n          typename L,\n          typename H,\n          bool int1 = IsIntlike<T>::value,\n          bool int2 = IsIntlike<L>::value,\n          bool int3 = IsIntlike<H>::value>\nstruct ClampType {\n  static_assert(int1 == int2 && int1 == int3,\n                \"You may not mix integral and floating-point arguments\");\n};\n\n// Specialization for when all three types are floating-point.\ntemplate <typename T, typename L, typename H>\nstruct ClampType<T, L, H, false, false, false> {\n  using type = typename std::common_type<T, L, H>::type;\n};\n\n// Specialization for when all three types are integral.\ntemplate <typename T, typename L, typename H>\nstruct ClampType<T, L, H, true, true, true> {\n private:\n  // Range of the return value. The return type must be able to represent this\n  // full range.\n  static constexpr auto r_min =\n      SafeMax(Limits<L>::lowest, SafeMin(Limits<H>::lowest, Limits<T>::lowest));\n  static constexpr auto r_max =\n      SafeMin(Limits<H>::max, SafeMax(Limits<L>::max, Limits<T>::max));\n\n  // Is the given type an acceptable return type? (That is, can it represent\n  // all possible return values, and is it no larger than the largest of the\n  // input types?)\n  template <typename A>\n  struct AcceptableType {\n   private:\n    static constexpr bool not_too_large = sizeof(A) <= sizeof(L) ||\n                                          sizeof(A) <= sizeof(H) ||\n                                          sizeof(A) <= sizeof(T);\n    static constexpr bool range_contained =\n        SafeLe(Limits<A>::lowest, r_min) && SafeLe(r_max, Limits<A>::max);\n\n   public:\n    static constexpr bool value = not_too_large && range_contained;\n  };\n\n  using best_signed_type = typename std::conditional<\n      AcceptableType<int8_t>::value,\n      int8_t,\n      typename std::conditional<\n          AcceptableType<int16_t>::value,\n          int16_t,\n          typename std::conditional<AcceptableType<int32_t>::value,\n                                    int32_t,\n                                    int64_t>::type>::type>::type;\n\n  using best_unsigned_type = typename std::conditional<\n      AcceptableType<uint8_t>::value,\n      uint8_t,\n      typename std::conditional<\n          AcceptableType<uint16_t>::value,\n          uint16_t,\n          typename std::conditional<AcceptableType<uint32_t>::value,\n                                    uint32_t,\n                                    uint64_t>::type>::type>::type;\n\n public:\n  // Pick the best type, preferring the same signedness as T but falling back\n  // to the other one if necessary.\n  using type = typename std::conditional<\n      std::is_signed<T>::value,\n      typename std::conditional<AcceptableType<best_signed_type>::value,\n                                best_signed_type,\n                                best_unsigned_type>::type,\n      typename std::conditional<AcceptableType<best_unsigned_type>::value,\n                                best_unsigned_type,\n                                best_signed_type>::type>::type;\n  static_assert(AcceptableType<type>::value, \"\");\n};\n\n}  // namespace safe_minmax_impl\n\ntemplate <\n    typename R = safe_minmax_impl::DefaultType,\n    typename T = safe_minmax_impl::DefaultType,\n    typename L = safe_minmax_impl::DefaultType,\n    typename H = safe_minmax_impl::DefaultType,\n    typename R2 = typename safe_minmax_impl::TypeOr<\n        R,\n        typename safe_minmax_impl::ClampType<\n            typename safe_minmax_impl::UnderlyingType<T>::type,\n            typename safe_minmax_impl::UnderlyingType<L>::type,\n            typename safe_minmax_impl::UnderlyingType<H>::type>::type>::type>\nR2 SafeClamp(T x, L min, H max) {\n  static_assert(IsIntlike<H>::value || std::is_floating_point<H>::value,\n                \"The first argument must be integral or floating-point\");\n  static_assert(IsIntlike<T>::value || std::is_floating_point<T>::value,\n                \"The second argument must be integral or floating-point\");\n  static_assert(IsIntlike<L>::value || std::is_floating_point<L>::value,\n                \"The third argument must be integral or floating-point\");\n  // RTC_DCHECK_LE(min, max);\n  return SafeLe(x, min)\n             ? static_cast<R2>(min)\n             : SafeGe(x, max) ? static_cast<R2>(max) : static_cast<R2>(x);\n}\n\n}  // namespace rtc\n\n#endif  // RTC_BASE_NUMERICS_SAFE_MINMAX_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/rate_statistics.cc",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#include \"rtc_base/rate_statistics.h\"\n\n#include <absl/memory/memory.h>\n#include <algorithm>\n\nnamespace webrtc {\n\nRateStatistics::RateStatistics(int64_t window_size_ms, float scale)\n    : buckets_(new Bucket[window_size_ms]()),\n      accumulated_count_(0),\n      num_samples_(0),\n      oldest_time_(-window_size_ms),\n      oldest_index_(0),\n      scale_(scale),\n      max_window_size_ms_(window_size_ms),\n      current_window_size_ms_(max_window_size_ms_) {}\n\nRateStatistics::RateStatistics(const RateStatistics& other)\n    : accumulated_count_(other.accumulated_count_),\n      num_samples_(other.num_samples_),\n      oldest_time_(other.oldest_time_),\n      oldest_index_(other.oldest_index_),\n      scale_(other.scale_),\n      max_window_size_ms_(other.max_window_size_ms_),\n      current_window_size_ms_(other.current_window_size_ms_) {\n  buckets_ = absl::make_unique<Bucket[]>(other.max_window_size_ms_);\n  std::copy(other.buckets_.get(),\n            other.buckets_.get() + other.max_window_size_ms_, buckets_.get());\n}\n\nRateStatistics::RateStatistics(RateStatistics&& other) = default;\n\nRateStatistics::~RateStatistics() {}\n\nvoid RateStatistics::Reset() {\n  accumulated_count_ = 0;\n  num_samples_ = 0;\n  oldest_time_ = -max_window_size_ms_;\n  oldest_index_ = 0;\n  current_window_size_ms_ = max_window_size_ms_;\n  for (int64_t i = 0; i < max_window_size_ms_; i++)\n    buckets_[i] = Bucket();\n}\n\nvoid RateStatistics::Update(size_t count, int64_t now_ms) {\n  if (now_ms < oldest_time_) {\n    // Too old data is ignored.\n    return;\n  }\n\n  EraseOld(now_ms);\n\n  // First ever sample, reset window to start now.\n  if (!IsInitialized())\n    oldest_time_ = now_ms;\n\n  uint32_t now_offset = static_cast<uint32_t>(now_ms - oldest_time_);\n  uint32_t index = oldest_index_ + now_offset;\n  if (index >= max_window_size_ms_)\n    index -= max_window_size_ms_;\n  buckets_[index].sum += count;\n  ++buckets_[index].samples;\n  accumulated_count_ += count;\n  ++num_samples_;\n}\n\nabsl::optional<uint32_t> RateStatistics::Rate(int64_t now_ms) const {\n  // Yeah, this const_cast ain't pretty, but the alternative is to declare most\n  // of the members as mutable...\n  const_cast<RateStatistics*>(this)->EraseOld(now_ms);\n\n  // If window is a single bucket or there is only one sample in a data set that\n  // has not grown to the full window size, treat this as rate unavailable.\n  int64_t active_window_size = now_ms - oldest_time_ + 1;\n  if (num_samples_ == 0 || active_window_size <= 1 ||\n      (num_samples_ <= 1 && active_window_size < current_window_size_ms_)) {\n    return absl::nullopt;\n  }\n\n  float scale = scale_ / active_window_size;\n  return static_cast<uint32_t>(accumulated_count_ * scale + 0.5f);\n}\n\nvoid RateStatistics::EraseOld(int64_t now_ms) {\n  if (!IsInitialized())\n    return;\n\n  // New oldest time that is included in data set.\n  int64_t new_oldest_time = now_ms - current_window_size_ms_ + 1;\n\n  // New oldest time is older than the current one, no need to cull data.\n  if (new_oldest_time <= oldest_time_)\n    return;\n\n  // Loop over buckets and remove too old data points.\n  while (num_samples_ > 0 && oldest_time_ < new_oldest_time) {\n    const Bucket& oldest_bucket = buckets_[oldest_index_];\n    accumulated_count_ -= oldest_bucket.sum;\n    num_samples_ -= oldest_bucket.samples;\n    buckets_[oldest_index_] = Bucket();\n    if (++oldest_index_ >= max_window_size_ms_)\n      oldest_index_ = 0;\n    ++oldest_time_;\n  }\n  oldest_time_ = new_oldest_time;\n}\n\nbool RateStatistics::SetWindowSize(int64_t window_size_ms, int64_t now_ms) {\n  if (window_size_ms <= 0 || window_size_ms > max_window_size_ms_)\n    return false;\n\n  current_window_size_ms_ = window_size_ms;\n  EraseOld(now_ms);\n  return true;\n}\n\nbool RateStatistics::IsInitialized() const {\n  return oldest_time_ != -max_window_size_ms_;\n}\n\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/rate_statistics.h",
    "content": "/*\n *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_RATE_STATISTICS_H_\n#define RTC_BASE_RATE_STATISTICS_H_\n\n#include <absl/types/optional.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <memory>\n\nnamespace webrtc {\n\nclass RateStatistics {\n public:\n  static constexpr float kBpsScale = 8000.0f;\n\n  // max_window_size_ms = Maximum window size in ms for the rate estimation.\n  //                      Initial window size is set to this, but may be changed\n  //                      to something lower by calling SetWindowSize().\n  // scale = coefficient to convert counts/ms to desired unit\n  //         ex: kBpsScale (8000) for bits/s if count represents bytes.\n  RateStatistics(int64_t max_window_size_ms, float scale);\n\n  RateStatistics(const RateStatistics& other);\n\n  RateStatistics(RateStatistics&& other);\n\n  ~RateStatistics();\n\n  // Reset instance to original state.\n  void Reset();\n\n  // Update rate with a new data point, moving averaging window as needed.\n  void Update(size_t count, int64_t now_ms);\n\n  // Note that despite this being a const method, it still updates the internal\n  // state (moves averaging window), but it doesn't make any alterations that\n  // are observable from the other methods, as long as supplied timestamps are\n  // from a monotonic clock. Ie, it doesn't matter if this call moves the\n  // window, since any subsequent call to Update or Rate would still have moved\n  // the window as much or more.\n  absl::optional<uint32_t> Rate(int64_t now_ms) const;\n\n  // Update the size of the averaging window. The maximum allowed value for\n  // window_size_ms is max_window_size_ms as supplied in the constructor.\n  bool SetWindowSize(int64_t window_size_ms, int64_t now_ms);\n\n private:\n  void EraseOld(int64_t now_ms);\n  bool IsInitialized() const;\n\n  // Counters are kept in buckets (circular buffer), with one bucket\n  // per millisecond.\n  struct Bucket {\n    size_t sum;      // Sum of all samples in this bucket.\n    size_t samples;  // Number of samples in this bucket.\n  };\n  std::unique_ptr<Bucket[]> buckets_;\n\n  // Total count recorded in buckets.\n  size_t accumulated_count_;\n\n  // The total number of samples in the buckets.\n  size_t num_samples_;\n\n  // Oldest time recorded in buckets.\n  int64_t oldest_time_;\n\n  // Bucket index of oldest counter recorded in buckets.\n  uint32_t oldest_index_;\n\n  // To convert counts/ms to desired units\n  const float scale_;\n\n  // The window sizes, in ms, over which the rate is calculated.\n  const int64_t max_window_size_ms_;\n  int64_t current_window_size_ms_;\n};\n}  // namespace webrtc\n\n#endif  // RTC_BASE_RATE_STATISTICS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/system/unused.h",
    "content": "/*\n *  Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_SYSTEM_UNUSED_H_\n#define RTC_BASE_SYSTEM_UNUSED_H_\n\n// Annotate a function indicating the caller must examine the return value.\n// Use like:\n//   int foo() RTC_WARN_UNUSED_RESULT;\n// To explicitly ignore a result, cast to void.\n// TODO(kwiberg): Remove when we can use [[nodiscard]] from C++17.\n#if defined(__clang__)\n#define RTC_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))\n#elif defined(__GNUC__)\n// gcc has a __warn_unused_result__ attribute, but you can't quiet it by\n// casting to void, so we don't use it.\n#define RTC_WARN_UNUSED_RESULT\n#else\n#define RTC_WARN_UNUSED_RESULT\n#endif\n\n// Prevent the compiler from warning about an unused variable. For example:\n//   int result = DoSomething();\n//   assert(result == 17);\n//   RTC_UNUSED(result);\n// Note: In most cases it is better to remove the unused variable rather than\n// suppressing the compiler warning.\n#ifndef RTC_UNUSED\n#define RTC_UNUSED(x) static_cast<void>(x)\n#endif  // RTC_UNUSED\n\n#endif  // RTC_BASE_SYSTEM_UNUSED_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/type_traits.h",
    "content": "/*\n *  Copyright 2016 The WebRTC Project Authors. All rights reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n\n#ifndef RTC_BASE_TYPE_TRAITS_H_\n#define RTC_BASE_TYPE_TRAITS_H_\n\n#include <cstddef>\n#include <type_traits>\n\nnamespace rtc {\n\n// Determines if the given class has zero-argument .data() and .size() methods\n// whose return values are convertible to T* and size_t, respectively.\ntemplate <typename DS, typename T>\nclass HasDataAndSize {\n private:\n  template <\n      typename C,\n      typename std::enable_if<\n          std::is_convertible<decltype(std::declval<C>().data()), T*>::value &&\n          std::is_convertible<decltype(std::declval<C>().size()),\n                              std::size_t>::value>::type* = nullptr>\n  static int Test(int);\n\n  template <typename>\n  static char Test(...);\n\n public:\n  static constexpr bool value = std::is_same<decltype(Test<DS>(0)), int>::value;\n};\n\nnamespace test_has_data_and_size {\n\ntemplate <typename DR, typename SR>\nstruct Test1 {\n  DR data();\n  SR size();\n};\nstatic_assert(HasDataAndSize<Test1<int*, int>, int>::value, \"\");\nstatic_assert(HasDataAndSize<Test1<int*, int>, const int>::value, \"\");\nstatic_assert(HasDataAndSize<Test1<const int*, int>, const int>::value, \"\");\nstatic_assert(!HasDataAndSize<Test1<const int*, int>, int>::value,\n              \"implicit cast of const int* to int*\");\nstatic_assert(!HasDataAndSize<Test1<char*, size_t>, int>::value,\n              \"implicit cast of char* to int*\");\n\nstruct Test2 {\n  int* data;\n  size_t size;\n};\nstatic_assert(!HasDataAndSize<Test2, int>::value,\n              \".data and .size aren't functions\");\n\nstruct Test3 {\n  int* data();\n};\nstatic_assert(!HasDataAndSize<Test3, int>::value, \".size() is missing\");\n\nclass Test4 {\n  int* data();\n  size_t size();\n};\nstatic_assert(!HasDataAndSize<Test4, int>::value,\n              \".data() and .size() are private\");\n\n}  // namespace test_has_data_and_size\n\nnamespace type_traits_impl {\n\n// Determines if the given type is an enum that converts implicitly to\n// an integral type.\ntemplate <typename T>\nstruct IsIntEnum {\n private:\n  // This overload is used if the type is an enum, and unary plus\n  // compiles and turns it into an integral type.\n  template <typename X,\n            typename std::enable_if<\n                std::is_enum<X>::value &&\n                std::is_integral<decltype(+std::declval<X>())>::value>::type* =\n                nullptr>\n  static int Test(int);\n\n  // Otherwise, this overload is used.\n  template <typename>\n  static char Test(...);\n\n public:\n  static constexpr bool value =\n      std::is_same<decltype(Test<typename std::remove_reference<T>::type>(0)),\n                   int>::value;\n};\n\n}  // namespace type_traits_impl\n\n// Determines if the given type is integral, or an enum that\n// converts implicitly to an integral type.\ntemplate <typename T>\nstruct IsIntlike {\n private:\n  using X = typename std::remove_reference<T>::type;\n\n public:\n  static constexpr bool value =\n      std::is_integral<X>::value || type_traits_impl::IsIntEnum<X>::value;\n};\n\nnamespace test_enum_intlike {\n\nenum E1 { e1 };\nenum { e2 };\nenum class E3 { e3 };\nstruct S {};\n\nstatic_assert(type_traits_impl::IsIntEnum<E1>::value, \"\");\nstatic_assert(type_traits_impl::IsIntEnum<decltype(e2)>::value, \"\");\nstatic_assert(!type_traits_impl::IsIntEnum<E3>::value, \"\");\nstatic_assert(!type_traits_impl::IsIntEnum<int>::value, \"\");\nstatic_assert(!type_traits_impl::IsIntEnum<float>::value, \"\");\nstatic_assert(!type_traits_impl::IsIntEnum<S>::value, \"\");\n\nstatic_assert(IsIntlike<E1>::value, \"\");\nstatic_assert(IsIntlike<decltype(e2)>::value, \"\");\nstatic_assert(!IsIntlike<E3>::value, \"\");\nstatic_assert(IsIntlike<int>::value, \"\");\nstatic_assert(!IsIntlike<float>::value, \"\");\nstatic_assert(!IsIntlike<S>::value, \"\");\n\n}  // namespace test_enum_intlike\n\n}  // namespace rtc\n\n#endif  // RTC_BASE_TYPE_TRAITS_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/rtc_base/units/unit_base.h",
    "content": "/*\n *  Copyright 2018 The WebRTC project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE file in the root of the source\n *  tree. An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the AUTHORS file in the root of the source tree.\n */\n#ifndef RTC_BASE_UNITS_UNIT_BASE_H_\n#define RTC_BASE_UNITS_UNIT_BASE_H_\n\n#include \"rtc_base/numerics/safe_conversions.h\"\n\n#include <stdint.h>\n#include <algorithm>\n#include <cmath>\n#include <limits>\n#include <type_traits>\n\nnamespace webrtc {\nnamespace rtc_units_impl {\n\n// UnitBase is a base class for implementing custom value types with a specific\n// unit. It provides type safety and commonly useful operations. The underlying\n// storage is always an int64_t, it's up to the unit implementation to choose\n// what scale it represents.\n//\n// It's used like:\n// class MyUnit: public UnitBase<MyUnit> {...};\n//\n// Unit_T is the subclass representing the specific unit.\ntemplate <class Unit_T>\nclass UnitBase {\n public:\n  UnitBase() = delete;\n  static constexpr Unit_T Zero() { return Unit_T(0); }\n  static constexpr Unit_T PlusInfinity() { return Unit_T(PlusInfinityVal()); }\n  static constexpr Unit_T MinusInfinity() { return Unit_T(MinusInfinityVal()); }\n\n  constexpr bool IsZero() const { return value_ == 0; }\n  constexpr bool IsFinite() const { return !IsInfinite(); }\n  constexpr bool IsInfinite() const {\n    return value_ == PlusInfinityVal() || value_ == MinusInfinityVal();\n  }\n  constexpr bool IsPlusInfinity() const { return value_ == PlusInfinityVal(); }\n  constexpr bool IsMinusInfinity() const {\n    return value_ == MinusInfinityVal();\n  }\n\n  constexpr bool operator==(const Unit_T& other) const {\n    return value_ == other.value_;\n  }\n  constexpr bool operator!=(const Unit_T& other) const {\n    return value_ != other.value_;\n  }\n  constexpr bool operator<=(const Unit_T& other) const {\n    return value_ <= other.value_;\n  }\n  constexpr bool operator>=(const Unit_T& other) const {\n    return value_ >= other.value_;\n  }\n  constexpr bool operator>(const Unit_T& other) const {\n    return value_ > other.value_;\n  }\n  constexpr bool operator<(const Unit_T& other) const {\n    return value_ < other.value_;\n  }\n  Unit_T RoundTo(const Unit_T& resolution) const {\n    //RTC_DCHECK(IsFinite());\n    //RTC_DCHECK(resolution.IsFinite());\n    //RTC_DCHECK_GT(resolution.value_, 0);\n    return Unit_T((value_ + resolution.value_ / 2) / resolution.value_) *\n           resolution.value_;\n  }\n  Unit_T RoundUpTo(const Unit_T& resolution) const {\n    //RTC_DCHECK(IsFinite());\n    //RTC_DCHECK(resolution.IsFinite());\n    //RTC_DCHECK_GT(resolution.value_, 0);\n    return Unit_T((value_ + resolution.value_ - 1) / resolution.value_) *\n           resolution.value_;\n  }\n  Unit_T RoundDownTo(const Unit_T& resolution) const {\n    //RTC_DCHECK(IsFinite());\n    //RTC_DCHECK(resolution.IsFinite());\n    //RTC_DCHECK_GT(resolution.value_, 0);\n    return Unit_T(value_ / resolution.value_) * resolution.value_;\n  }\n\n protected:\n  template <int64_t value>\n  static constexpr Unit_T FromStaticValue() {\n    static_assert(value >= 0 || !Unit_T::one_sided, \"\");\n    static_assert(value > MinusInfinityVal(), \"\");\n    static_assert(value < PlusInfinityVal(), \"\");\n    return Unit_T(value);\n  }\n\n  template <int64_t fraction_value, int64_t Denominator>\n  static constexpr Unit_T FromStaticFraction() {\n    static_assert(fraction_value >= 0 || !Unit_T::one_sided, \"\");\n    static_assert(fraction_value > MinusInfinityVal() / Denominator, \"\");\n    static_assert(fraction_value < PlusInfinityVal() / Denominator, \"\");\n    return Unit_T(fraction_value * Denominator);\n  }\n\n  template <\n      typename T,\n      typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>\n  static Unit_T FromValue(T value) {\n    // if (Unit_T::one_sided)\n      //RTC_DCHECK_GE(value, 0);\n    //RTC_DCHECK_GT(value, MinusInfinityVal());\n    //RTC_DCHECK_LT(value, PlusInfinityVal());\n    return Unit_T(rtc::dchecked_cast<int64_t>(value));\n  }\n  template <typename T,\n            typename std::enable_if<std::is_floating_point<T>::value>::type* =\n                nullptr>\n  static Unit_T FromValue(T value) {\n    if (value == std::numeric_limits<T>::infinity()) {\n      return PlusInfinity();\n    } else if (value == -std::numeric_limits<T>::infinity()) {\n      return MinusInfinity();\n    } else {\n      //RTC_DCHECK(!std::isnan(value));\n      return FromValue(rtc::dchecked_cast<int64_t>(value));\n    }\n  }\n\n  template <\n      int64_t Denominator,\n      typename T,\n      typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>\n  static Unit_T FromFraction(T value) {\n    // if (Unit_T::one_sided)\n      //RTC_DCHECK_GE(value, 0);\n    //RTC_DCHECK_GT(value, MinusInfinityVal() / Denominator);\n    //RTC_DCHECK_LT(value, PlusInfinityVal() / Denominator);\n    return Unit_T(rtc::dchecked_cast<int64_t>(value * Denominator));\n  }\n  template <int64_t Denominator,\n            typename T,\n            typename std::enable_if<std::is_floating_point<T>::value>::type* =\n                nullptr>\n  static Unit_T FromFraction(T value) {\n    return FromValue(value * Denominator);\n  }\n\n  template <typename T = int64_t>\n  typename std::enable_if<std::is_integral<T>::value, T>::type ToValue() const {\n    //RTC_DCHECK(IsFinite());\n    return rtc::dchecked_cast<T>(value_);\n  }\n  template <typename T>\n  constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type\n  ToValue() const {\n    return IsPlusInfinity()\n               ? std::numeric_limits<T>::infinity()\n               : IsMinusInfinity() ? -std::numeric_limits<T>::infinity()\n                                   : value_;\n  }\n  template <typename T>\n  constexpr T ToValueOr(T fallback_value) const {\n    return IsFinite() ? value_ : fallback_value;\n  }\n\n  template <int64_t Denominator, typename T = int64_t>\n  typename std::enable_if<std::is_integral<T>::value, T>::type ToFraction()\n      const {\n    //RTC_DCHECK(IsFinite());\n    if (Unit_T::one_sided) {\n      return rtc::dchecked_cast<T>(\n          DivRoundPositiveToNearest(value_, Denominator));\n    } else {\n      return rtc::dchecked_cast<T>(DivRoundToNearest(value_, Denominator));\n    }\n  }\n  template <int64_t Denominator, typename T>\n  constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type\n  ToFraction() const {\n    return ToValue<T>() * (1 / static_cast<T>(Denominator));\n  }\n\n  template <int64_t Denominator>\n  constexpr int64_t ToFractionOr(int64_t fallback_value) const {\n    return IsFinite() ? Unit_T::one_sided\n                            ? DivRoundPositiveToNearest(value_, Denominator)\n                            : DivRoundToNearest(value_, Denominator)\n                      : fallback_value;\n  }\n\n  template <int64_t Factor, typename T = int64_t>\n  typename std::enable_if<std::is_integral<T>::value, T>::type ToMultiple()\n      const {\n    //RTC_DCHECK_GE(ToValue(), std::numeric_limits<T>::min() / Factor);\n    //RTC_DCHECK_LE(ToValue(), std::numeric_limits<T>::max() / Factor);\n    return rtc::dchecked_cast<T>(ToValue() * Factor);\n  }\n  template <int64_t Factor, typename T>\n  constexpr typename std::enable_if<std::is_floating_point<T>::value, T>::type\n  ToMultiple() const {\n    return ToValue<T>() * Factor;\n  }\n\n  explicit constexpr UnitBase(int64_t value) : value_(value) {}\n\n private:\n  template <class RelativeUnit_T>\n  friend class RelativeUnit;\n\n  static inline constexpr int64_t PlusInfinityVal() {\n    return std::numeric_limits<int64_t>::max();\n  }\n  static inline constexpr int64_t MinusInfinityVal() {\n    return std::numeric_limits<int64_t>::min();\n  }\n\n  Unit_T& AsSubClassRef() { return reinterpret_cast<Unit_T&>(*this); }\n  constexpr const Unit_T& AsSubClassRef() const {\n    return reinterpret_cast<const Unit_T&>(*this);\n  }\n  // Assumes that n >= 0 and d > 0.\n  static constexpr int64_t DivRoundPositiveToNearest(int64_t n, int64_t d) {\n    return (n + d / 2) / d;\n  }\n  // Assumes that d > 0.\n  static constexpr int64_t DivRoundToNearest(int64_t n, int64_t d) {\n    return (n + (n >= 0 ? d / 2 : -d / 2)) / d;\n  }\n\n  int64_t value_;\n};\n\n// Extends UnitBase to provide operations for relative units, that is, units\n// that have a meaningful relation between values such that a += b is a\n// sensible thing to do. For a,b <- same unit.\ntemplate <class Unit_T>\nclass RelativeUnit : public UnitBase<Unit_T> {\n public:\n  Unit_T Clamped(Unit_T min_value, Unit_T max_value) const {\n    return std::max(min_value,\n                    std::min(UnitBase<Unit_T>::AsSubClassRef(), max_value));\n  }\n  void Clamp(Unit_T min_value, Unit_T max_value) {\n    *this = Clamped(min_value, max_value);\n  }\n  Unit_T operator+(const Unit_T other) const {\n    if (this->IsPlusInfinity() || other.IsPlusInfinity()) {\n      //RTC_DCHECK(!this->IsMinusInfinity());\n      //RTC_DCHECK(!other.IsMinusInfinity());\n      return this->PlusInfinity();\n    } else if (this->IsMinusInfinity() || other.IsMinusInfinity()) {\n      //RTC_DCHECK(!this->IsPlusInfinity());\n      //RTC_DCHECK(!other.IsPlusInfinity());\n      return this->MinusInfinity();\n    }\n    return UnitBase<Unit_T>::FromValue(this->ToValue() + other.ToValue());\n  }\n  Unit_T operator-(const Unit_T other) const {\n    if (this->IsPlusInfinity() || other.IsMinusInfinity()) {\n      //RTC_DCHECK(!this->IsMinusInfinity());\n      //RTC_DCHECK(!other.IsPlusInfinity());\n      return this->PlusInfinity();\n    } else if (this->IsMinusInfinity() || other.IsPlusInfinity()) {\n      //RTC_DCHECK(!this->IsPlusInfinity());\n      //RTC_DCHECK(!other.IsMinusInfinity());\n      return this->MinusInfinity();\n    }\n    return UnitBase<Unit_T>::FromValue(this->ToValue() - other.ToValue());\n  }\n  Unit_T& operator+=(const Unit_T other) {\n    *this = *this + other;\n    return this->AsSubClassRef();\n  }\n  Unit_T& operator-=(const Unit_T other) {\n    *this = *this - other;\n    return this->AsSubClassRef();\n  }\n  constexpr double operator/(const Unit_T other) const {\n    return UnitBase<Unit_T>::template ToValue<double>() /\n           other.template ToValue<double>();\n  }\n  template <typename T>\n  typename std::enable_if<std::is_arithmetic<T>::value, Unit_T>::type operator/(\n      const T& scalar) const {\n    return UnitBase<Unit_T>::FromValue(\n        std::round(UnitBase<Unit_T>::template ToValue<int64_t>() / scalar));\n  }\n  Unit_T operator*(const double scalar) const {\n    return UnitBase<Unit_T>::FromValue(std::round(this->ToValue() * scalar));\n  }\n  Unit_T operator*(const int64_t scalar) const {\n    return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);\n  }\n  Unit_T operator*(const int32_t scalar) const {\n    return UnitBase<Unit_T>::FromValue(this->ToValue() * scalar);\n  }\n\n protected:\n  using UnitBase<Unit_T>::UnitBase;\n};\n\ntemplate <class Unit_T>\ninline Unit_T operator*(const double scalar, const RelativeUnit<Unit_T> other) {\n  return other * scalar;\n}\ntemplate <class Unit_T>\ninline Unit_T operator*(const int64_t scalar,\n                        const RelativeUnit<Unit_T> other) {\n  return other * scalar;\n}\ntemplate <class Unit_T>\ninline Unit_T operator*(const int32_t& scalar,\n                        const RelativeUnit<Unit_T> other) {\n  return other * scalar;\n}\n\n}  // namespace rtc_units_impl\n\n}  // namespace webrtc\n\n#endif  // RTC_BASE_UNITS_UNIT_BASE_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/system_wrappers/source/field_trial.cc",
    "content": "// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n//\n// Use of this source code is governed by a BSD-style license\n// that can be found in the LICENSE file in the root of the source\n// tree. An additional intellectual property rights grant can be found\n// in the file PATENTS.  All contributing project authors may\n// be found in the AUTHORS file in the root of the source tree.\n//\n\n#define MS_CLASS \"webrtc::FieldTrial\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"system_wrappers/source/field_trial.h\"\n\n#include \"Logger.hpp\"\n\n#include <absl/strings/string_view.h>\n#include <stddef.h>\n#include <map>\n#include <string>\n\n// Simple field trial implementation, which allows client to\n// specify desired flags in InitFieldTrialsFromString.\nnamespace webrtc {\nnamespace field_trial {\n\nstatic const char* trials_init_string = NULL;\n\n#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT\nnamespace {\nconstexpr char kPersistentStringSeparator = '/';\n// Validates the given field trial string.\n//  E.g.:\n//    \"WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/\"\n//    Assigns the process to group \"Enabled\" on WebRTCExperimentFoo trial\n//    and to group \"Enabled100kbps\" on WebRTCExperimentBar.\n//\n//  E.g. invalid config:\n//    \"WebRTC-experiment1/Enabled\"  (note missing / separator at the end).\nbool FieldTrialsStringIsValid(const absl::string_view trials) {\n  if (trials.empty())\n    return true;\n\n  size_t next_item = 0;\n  std::map<absl::string_view, absl::string_view> field_trials;\n  while (next_item < trials.length()) {\n    size_t name_end = trials.find(kPersistentStringSeparator, next_item);\n    if (name_end == trials.npos || next_item == name_end)\n      return false;\n    size_t group_name_end =\n        trials.find(kPersistentStringSeparator, name_end + 1);\n    if (group_name_end == trials.npos || name_end + 1 == group_name_end)\n      return false;\n    absl::string_view name = trials.substr(next_item, name_end - next_item);\n    absl::string_view group_name =\n        trials.substr(name_end + 1, group_name_end - name_end - 1);\n\n    next_item = group_name_end + 1;\n\n    // Fail if duplicate with different group name.\n    if (field_trials.find(name) != field_trials.end() &&\n        field_trials.find(name)->second != group_name) {\n      return false;\n    }\n\n    field_trials[name] = group_name;\n  }\n\n  return true;\n}\n}  // namespace\n\nstd::string FindFullName(const std::string& name) {\n  if (trials_init_string == NULL)\n    return std::string();\n\n  std::string trials_string(trials_init_string);\n  if (trials_string.empty())\n    return std::string();\n\n  size_t next_item = 0;\n  while (next_item < trials_string.length()) {\n    // Find next name/value pair in field trial configuration string.\n    size_t field_name_end =\n        trials_string.find(kPersistentStringSeparator, next_item);\n    if (field_name_end == trials_string.npos || field_name_end == next_item)\n      break;\n    size_t field_value_end =\n        trials_string.find(kPersistentStringSeparator, field_name_end + 1);\n    if (field_value_end == trials_string.npos ||\n        field_value_end == field_name_end + 1)\n      break;\n    std::string field_name(trials_string, next_item,\n                           field_name_end - next_item);\n    std::string field_value(trials_string, field_name_end + 1,\n                            field_value_end - field_name_end - 1);\n    next_item = field_value_end + 1;\n\n    if (name == field_name)\n      return field_value;\n  }\n  return std::string();\n}\n#endif  // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT\n\n// Optionally initialize field trial from a string.\nvoid InitFieldTrialsFromString(const char* trials_string) {\n  MS_DEBUG_TAG(bwe, \"Setting field trial string: %s\", trials_string);\n#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT\n  if (trials_string) {\n    // RTC_DCHECK(FieldTrialsStringIsValid(trials_string))\n        // << \"Invalid field trials string:\" << trials_string;\n    MS_ASSERT(\n      FieldTrialsStringIsValid(trials_string), \"invalid field trials string: '%s'\", trials_string);\n  };\n#endif  // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT\n  trials_init_string = trials_string;\n}\n\nconst char* GetFieldTrialString() {\n  return trials_init_string;\n}\n\n}  // namespace field_trial\n}  // namespace webrtc\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc/system_wrappers/source/field_trial.h",
    "content": "//\n// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.\n//\n// Use of this source code is governed by a BSD-style license\n// that can be found in the LICENSE file in the root of the source\n// tree. An additional intellectual property rights grant can be found\n// in the file PATENTS.  All contributing project authors may\n// be found in the AUTHORS file in the root of the source tree.\n//\n\n#ifndef SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_\n#define SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_\n\n#include <string>\n\n// Field trials allow webrtc clients (such as Chrome) to turn on feature code\n// in binaries out in the field and gather information with that.\n//\n// By default WebRTC provides an implementaion of field trials that can be\n// found in system_wrappers/source/field_trial.cc. If clients want to provide\n// a custom version, they will have to:\n//\n// 1. Compile WebRTC defining the preprocessor macro\n//    WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT (if GN is used this can be achieved\n//    by setting the GN arg rtc_exclude_field_trial_default to true).\n// 2. Provide an implementation of:\n//    std::string webrtc::field_trial::FindFullName(const std::string& trial).\n//\n// They are designed to wire up directly to chrome field trials and to speed up\n// developers by reducing the need to wire APIs to control whether a feature is\n// on/off. E.g. to experiment with a new method that could lead to a different\n// trade-off between CPU/bandwidth:\n//\n// 1 - Develop the feature with default behaviour off:\n//\n//   if (FieldTrial::FindFullName(\"WebRTCExperimentMethod2\") == \"Enabled\")\n//     method2();\n//   else\n//     method1();\n//\n// 2 - Once the changes are rolled to chrome, the new code path can be\n//     controlled as normal chrome field trials.\n//\n// 3 - Evaluate the new feature and clean the code paths.\n//\n// Notes:\n//   - NOT every feature is a candidate to be controlled by this mechanism as\n//     it may require negotation between involved parties (e.g. SDP).\n//\n// TODO(andresp): since chrome --force-fieldtrials does not marks the trial\n//     as active it does not gets propaged to renderer process. For now one\n//     needs to push a config with start_active:true or run a local finch\n//     server.\n//\n// TODO(andresp): find out how to get bots to run tests with trials enabled.\n\nnamespace webrtc {\nnamespace field_trial {\n\n// Returns the group name chosen for the named trial, or the empty string\n// if the trial does not exists.\n//\n// Note: To keep things tidy append all the trial names with WebRTC.\nstd::string FindFullName(const std::string& name);\n\n// Convenience method, returns true iff FindFullName(name) return a string that\n// starts with \"Enabled\".\n// TODO(tommi): Make sure all implementations support this.\ninline bool IsEnabled(const char* name) {\n  return FindFullName(name).find(\"Enabled\") == 0;\n}\n\n// Convenience method, returns true iff FindFullName(name) return a string that\n// starts with \"Disabled\".\ninline bool IsDisabled(const char* name) {\n  return FindFullName(name).find(\"Disabled\") == 0;\n}\n\n// Optionally initialize field trial from a string.\n// This method can be called at most once before any other call into webrtc.\n// E.g. before the peer connection factory is constructed.\n// Note: trials_string must never be destroyed.\nvoid InitFieldTrialsFromString(const char* trials_string);\n\nconst char* GetFieldTrialString();\n\n}  // namespace field_trial\n}  // namespace webrtc\n\n#endif  // SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_\n"
  },
  {
    "path": "worker/deps/libwebrtc/libwebrtc.gyp",
    "content": "{\n  'target_defaults': {\n    'dependencies':\n    [\n      'deps/abseil-cpp/abseil-cpp.gyp:abseil',\n      '../libuv/uv.gyp:libuv',\n      '../openssl/openssl.gyp:openssl'\n    ],\n    'direct_dependent_settings': {\n      'include_dirs':\n      [\n        '.',\n        'libwebrtc'\n      ]\n    },\n    'sources':\n    [\n      # C++ source files.\n      'libwebrtc/system_wrappers/source/field_trial.cc',\n      'libwebrtc/rtc_base/rate_statistics.cc',\n      'libwebrtc/rtc_base/experiments/field_trial_parser.cc',\n      'libwebrtc/rtc_base/experiments/alr_experiment.cc',\n      'libwebrtc/rtc_base/experiments/field_trial_units.cc',\n      'libwebrtc/rtc_base/experiments/rate_control_settings.cc',\n      'libwebrtc/rtc_base/network/sent_packet.cc',\n      'libwebrtc/call/rtp_transport_controller_send.cc',\n      'libwebrtc/api/transport/bitrate_settings.cc',\n      'libwebrtc/api/transport/field_trial_based_config.cc',\n      'libwebrtc/api/transport/network_types.cc',\n      'libwebrtc/api/transport/goog_cc_factory.cc',\n      'libwebrtc/api/units/timestamp.cc',\n      'libwebrtc/api/units/time_delta.cc',\n      'libwebrtc/api/units/data_rate.cc',\n      'libwebrtc/api/units/data_size.cc',\n      'libwebrtc/api/units/frequency.cc',\n      'libwebrtc/api/network_state_predictor.cc',\n      'libwebrtc/modules/pacing/interval_budget.cc',\n      'libwebrtc/modules/pacing/bitrate_prober.cc',\n      'libwebrtc/modules/pacing/paced_sender.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc',\n      'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc',\n      'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc',\n      'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc',\n      'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc',\n      'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc',\n      'libwebrtc/modules/congestion_controller/rtp/send_time_history.cc',\n      'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc',\n      'libwebrtc/modules/congestion_controller/rtp/control_handler.cc',\n      # C++ include files.\n      'libwebrtc/system_wrappers/source/field_trial.h',\n      'libwebrtc/rtc_base/rate_statistics.h',\n      'libwebrtc/rtc_base/experiments/field_trial_parser.h',\n      'libwebrtc/rtc_base/experiments/field_trial_units.h',\n      'libwebrtc/rtc_base/experiments/alr_experiment.h',\n      'libwebrtc/rtc_base/experiments/rate_control_settings.h',\n      'libwebrtc/rtc_base/network/sent_packet.h',\n      'libwebrtc/rtc_base/units/unit_base.h',\n      'libwebrtc/rtc_base/constructor_magic.h',\n      'libwebrtc/rtc_base/numerics/safe_minmax.h',\n      'libwebrtc/rtc_base/numerics/safe_conversions.h',\n      'libwebrtc/rtc_base/numerics/safe_conversions_impl.h',\n      'libwebrtc/rtc_base/numerics/percentile_filter.h',\n      'libwebrtc/rtc_base/numerics/safe_compare.h',\n      'libwebrtc/rtc_base/system/unused.h',\n      'libwebrtc/rtc_base/type_traits.h',\n      'libwebrtc/call/rtp_transport_controller_send.h',\n      'libwebrtc/call/rtp_transport_controller_send_interface.h',\n      'libwebrtc/api/transport/webrtc_key_value_config.h',\n      'libwebrtc/api/transport/network_types.h',\n      'libwebrtc/api/transport/bitrate_settings.h',\n      'libwebrtc/api/transport/network_control.h',\n      'libwebrtc/api/transport/field_trial_based_config.h',\n      'libwebrtc/api/transport/goog_cc_factory.h',\n      'libwebrtc/api/bitrate_constraints.h',\n      'libwebrtc/api/units/frequency.h',\n      'libwebrtc/api/units/data_size.h',\n      'libwebrtc/api/units/time_delta.h',\n      'libwebrtc/api/units/data_rate.h',\n      'libwebrtc/api/units/timestamp.h',\n      'libwebrtc/api/network_state_predictor.h',\n      'libwebrtc/modules/include/module_common_types_public.h',\n      'libwebrtc/modules/pacing/interval_budget.h',\n      'libwebrtc/modules/pacing/paced_sender.h',\n      'libwebrtc/modules/pacing/packet_router.h',\n      'libwebrtc/modules/pacing/bitrate_prober.h',\n      'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.h',\n      'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.h',\n      'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.h',\n      'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.h',\n      'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.h',\n      'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h',\n      'libwebrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h',\n      'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h',\n      'libwebrtc/modules/rtp_rtcp/source/rtp_packet/transport_feedback.h',\n      'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.h',\n      'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.h',\n      'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.h',\n      'libwebrtc/modules/congestion_controller/rtp/send_time_history.h',\n      'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.h',\n      'libwebrtc/modules/congestion_controller/rtp/control_handler.h',\n      'libwebrtc/mediasoup_helpers.h'\n    ],\n    'include_dirs':\n    [\n      'libwebrtc',\n      '../../include',\n      '../json/single_include'\n    ],\n    'conditions':\n    [\n      # Endianness.\n      [ 'node_byteorder == \"big\"', {\n          # Define Big Endian.\n          'defines': [ 'MS_BIG_ENDIAN' ]\n        }, {\n          # Define Little Endian.\n          'defines': [ 'MS_LITTLE_ENDIAN' ]\n      }],\n\n      # Platform-specifics.\n\n      [ 'OS != \"win\"', {\n        'cflags': [ '-std=c++11' ]\n      }],\n\n      [ 'OS == \"mac\"', {\n        'xcode_settings':\n        {\n          'OTHER_CPLUSPLUSFLAGS' : [ '-std=c++11' ]\n        }\n      }]\n    ]\n  },\n  'targets':\n  [\n    {\n      'target_name': 'libwebrtc',\n      'type': 'static_library'\n    }\n  ]\n}\n"
  },
  {
    "path": "worker/deps/libwebrtc/meson.build",
    "content": "libwebrtc_sources = [\n  'libwebrtc/system_wrappers/source/field_trial.cc',\n  'libwebrtc/rtc_base/rate_statistics.cc',\n  'libwebrtc/rtc_base/experiments/field_trial_parser.cc',\n  'libwebrtc/rtc_base/experiments/alr_experiment.cc',\n  'libwebrtc/rtc_base/experiments/field_trial_units.cc',\n  'libwebrtc/rtc_base/experiments/rate_control_settings.cc',\n  'libwebrtc/rtc_base/network/sent_packet.cc',\n  'libwebrtc/call/rtp_transport_controller_send.cc',\n  'libwebrtc/api/transport/bitrate_settings.cc',\n  'libwebrtc/api/transport/field_trial_based_config.cc',\n  'libwebrtc/api/transport/network_types.cc',\n  'libwebrtc/api/transport/goog_cc_factory.cc',\n  'libwebrtc/api/units/timestamp.cc',\n  'libwebrtc/api/units/time_delta.cc',\n  'libwebrtc/api/units/data_rate.cc',\n  'libwebrtc/api/units/data_size.cc',\n  'libwebrtc/api/units/frequency.cc',\n  'libwebrtc/api/network_state_predictor.cc',\n  'libwebrtc/modules/pacing/interval_budget.cc',\n  'libwebrtc/modules/pacing/bitrate_prober.cc',\n  'libwebrtc/modules/pacing/paced_sender.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc',\n  'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc',\n  'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc',\n  'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc',\n  'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc',\n  'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc',\n  'libwebrtc/modules/congestion_controller/rtp/send_time_history.cc',\n  'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc',\n  'libwebrtc/modules/congestion_controller/rtp/control_handler.cc',\n]\n\nabseil_cpp_proj = subproject(\n  'abseil-cpp',\n  default_options: [\n    'warning_level=0',\n  ],\n)\nlocal_include_directories = declare_dependency(\n  include_directories: include_directories('libwebrtc')\n)\n\nlibwebrtc = library(\n  'libwebrtc',\n  libwebrtc_sources,\n  dependencies: [\n    local_include_directories,\n    openssl_proj.get_variable('openssl_dep'),\n    abseil_cpp_proj.get_variable('absl_strings_dep'),\n    abseil_cpp_proj.get_variable('absl_types_dep'),\n    flatbuffers_proj.get_variable('flatbuffers_dep'),\n    libuv_proj.get_variable('libuv_dep'),\n  ],\n  include_directories: libwebrtc_include_directories,\n  cpp_args: cpp_args,\n)\n\nlibwebrtc_dep = declare_dependency(\n  dependencies: [\n    local_include_directories,\n    abseil_cpp_proj.get_variable('absl_strings_dep'),\n    abseil_cpp_proj.get_variable('absl_types_dep'),\n  ],\n  include_directories: include_directories('.'),\n  link_with: libwebrtc,\n)\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/README.md",
    "content": "# webrtc-fuzzer-corpora\n\nThis repository contains [libFuzzer](http://libfuzzer.info) corpus files useful for WebRTC developers to test their implementations.\n\nThe `corpora` folders contains subfolders with corpus files.\n\nSome corpus files are taken from the Chromium project (those into the [webrtc/test/fuzzer/](https://chromium.googlesource.com/external/webrtc/+/master/test/fuzzers/corpora/) folder), so credits to the Chromium guys.\n\nThe `reports` folder contains some relevant fuzzer generated crash and memory leak reports contributed by the community.\n\n\n## SHA1 hash and file duplicates\n\nIt is desiderable to avoid duplicate files in this repository in order to not pollute the folders with samples that might lead to usless testing iterations (e.g. file with different names but same binary content).\n\nTo help managing this kind of issues, the script `add_sha1.sh` can be used to append the sha1 hashsum to the corpora and results files.\n\n```\n$ ./add_sha1.sh .\nrenamed './corpora/agc-corpus/agc-1' -> './corpora/agc-corpus/agc-1-b35a35f11d86e802f694f84c45e48da96158f73f'\nrenamed './corpora/agc-corpus/agc-2' -> './corpora/agc-corpus/agc-2-f3d59a997d30890da3239b377bba3bc502d18f1e'\n...\nrenamed './corpora/rtcp-corpus/0.rtcp' -> './corpora/rtcp-corpus/0-01b13c2eb549daadeec1eb7eb0846e9a2f5729eb.rtcp'\n```\n\nTarget folder can be specified as first input. File extensions will not be touched.\n\nThe script will detect an already hashed file by looking for the sha1sum in the filename. In this cases the output will be like:\n\n```\n> ./add_sha1.sh . | grep OK\n./corpora/pseudotcp-corpus/785b96587d0eb44dd5d75b7a886f37e2ac504511 -> OK\n./corpora/rtcp-corpus/0-01b13c2eb549daadeec1eb7eb0846e9a2f5729eb.rtcp -> OK\n./corpora/rtp-corpus/rtp-0-951641f47532884149e6ebe9a87386226d8bf4fe -> OK\n```\n\nThis script will alse reveal duplicate files in the same subfolder by matching the file sha1sum with other files names:\n\n```\n> ./add_sha1.sh . | grep DUPLICATE\n./corpora/rtp-corpus/rtp-4 -> DUPLICATE ./corpora/rtp-corpus/rtp-1-5a709d82364ddf4f9350104c83994dded1c9f91c\n./corpora/sdp-corpus/4.sdp -> DUPLICATE ./corpora/sdp-corpus/2-60495c33fc8758c5ce44f896c5f508bc026842c2.sdp\n./corpora/sdp-corpus/8.sdp -> DUPLICATE ./corpora/sdp-corpus/2-60495c33fc8758c5ce44f896c5f508bc026842c2.sdp\n```\n\nAnyway `add_sha1` will only detect duplicates in the same subfolder.\nAnother way to quickly reveal file duplicates across all subfolders is the following command:\n\n```\n> find . \\( ! -regex '.*/\\..*' \\) -type f -exec sha1sum {} + | sort | uniq -w40 -dD\n131d620296fa9eb2c372c7fd71b8e58f8a10abd5  ./corpora/sdp-corpus/46-131d620296fa9eb2c372c7fd71b8e58f8a10abd5.sdp\n131d620296fa9eb2c372c7fd71b8e58f8a10abd5  ./corpora/sdp-corpus/50.sdp\n271138fdddb4f02313f25b47581f9f8f4a2eb201  ./corpora/sdp-corpus/12-271138fdddb4f02313f25b47581f9f8f4a2eb201.sdp\n271138fdddb4f02313f25b47581f9f8f4a2eb201  ./corpora/sdp-corpus/15.sdp\n271138fdddb4f02313f25b47581f9f8f4a2eb201  ./corpora/sdp-corpus/40.sdp\n...\n461a0e9201a7ea5ea6a43511571bdafce10b8185  ./corpora/rtcp-corpus/17-461a0e9201a7ea5ea6a43511571bdafce10b8185.rtcp\n461a0e9201a7ea5ea6a43511571bdafce10b8185  ./reports/crashes/rtcp/crash-461a0e9201a7ea5ea6a43511571bdafce10b8185\n```\n\n\n## License\n\nMIT or BSD or ISC or something like that (**let me focus on just fuzzing things!!**).\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/add_sha1.sh",
    "content": "#!/bin/bash\n\nif [ -z $1 ]; then\n  echo \"Usage ./add_sha1.sh corpus_folder\"\n  exit 1\nfi\n\nSAVEIFS=$IFS\nIFS=$(echo -en \"\\n\\b\")\ncrpfiles=$(find $1 \\( ! -regex '.*/\\..*' \\) -not -iname \"*.md\" -not -iname \"*.sh\" -not -iname \"*README*\" -not -iname \"*LICENSE*\" -not -iname \"*COPYRIGHT*\" -type f)\nfor file in $crpfiles; do\n  sha1=$(sha1sum $file | awk '{print $1}')\n  if [[ $file != *\"$sha1\"* ]]; then\n    basedir=$(dirname $file)\n    basefile=$(basename $file)\n    name=\"${basefile%.*}\"\n    ext=\"${basefile##*.}\"\n    duplicates=$(find $basedir -type f -name \"*$sha1*\")\n    if [ ! -z $duplicates ]; then echo \"$file -> DUPLICATE $duplicates\"; continue; fi\n    if [[ $name == \"$ext\" ]]; then ext=\"\"; else ext=\".$ext\"; fi\n    mv -vn \"$file\" \"$basedir/$name-$sha1$ext\"\n  else\n    echo \"$file -> OK\"\n  fi\ndone\nIFS=$SAVEIFS\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus/47.rtcp",
    "content": ""
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus/55.rtcp",
    "content": ""
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/10.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 97\na=rtpmap:0 PCMU/8000\na=rtpmap:97 iLBC/8000\nm=audio 49172 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=sendonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/11.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=audio 49174 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=recvonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/12.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/13.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49174 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 49170 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/14.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 newhost.biloxi.example.com\nt=0 0\nm=audio 49178 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 49188 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/15.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/16.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=audio 51372 RTP/AVP 97 101\na=rtpmap:97 iLBC/8000\na=rtpmap:101 telephone-event/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/17.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 0 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=audio 49170 RTP/AVP 97 101\na=rtpmap:97 iLBC/8000\na=rtpmap:101 telephone-event/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/18.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 99\na=rtpmap:99 iLBC/8000\nm=video 51372 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/19.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 99\na=rtpmap:99 iLBC/8000\nm=video 51374 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/2.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 8 97\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/20.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 99\na=rtpmap:99 iLBC/8000\nm=video 51372 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/21.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 99\na=rtpmap:99 iLBC/8000\nm=video 51374 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/22.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 8 97\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/23.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49174 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 49172 RTP/AVP 32\nc=IN IP4 otherhost.biloxi.example.com\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/24.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 97\na=rtpmap:0 PCMU/8000\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/25.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 placeholder.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\na=sendonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/26.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/27.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49178 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/28.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 97\na=rtpmap:0 PCMU/8000\na=rtpmap:97 iLBC/8000\nm=audio 49172 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=sendonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/29.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=audio 49174 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=recvonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/3.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49174 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 49170 RTP/AVP 32\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/30.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\na=sendonly\nm=audio 49174 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=recvonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/31.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 97\na=rtpmap:0 PCMU/8000\na=rtpmap:97 iLBC/8000\nm=audio 49172 RTP/AVP 98\na=rtpmap:98 telephone-event/8000\na=sendonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/32.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 97\na=rtpmap:0 PCMU/8000\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/33.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/34.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=audio 48282 RTP/AVP 98\nc=IN IP4 mediaserver.biloxi.example.com\na=rtpmap:98 telephone-event/8000\na=recvonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/35.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=audio 49172 RTP/AVP 98\nc=IN IP4 host.atlanta.example.com\na=rtpmap:98 telephone-event/8000\na=sendonly\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/36.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0\na=rtpmap:0 PCMU/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/37.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 0\na=rtpmap:0 PCMU/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/38.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 49172 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/39.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 49168 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/4.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 8 97\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/40.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/41.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49174 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 49170 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/42.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49174 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 0 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/43.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\nm=video 0 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/44.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/45.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/46.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/47.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/48.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 0.0.0.0\nt=0 0\nm=audio 23442 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/49.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/5.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 0 8\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\nm=video 0 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/50.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/51.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/52.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/53.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 0.0.0.0\nt=0 0\nm=audio 9322 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/54.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/55.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 97\na=rtpmap:97 iLBC/8000\n\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/6.sdp",
    "content": "v=0\no=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 51372 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 0 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/7.sdp",
    "content": "v=0\no=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 0\na=rtpmap:0 PCMU/8000\nm=video 0 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/8.sdp",
    "content": "v=0\no=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com\ns=\nc=IN IP4 host.atlanta.example.com\nt=0 0\nm=audio 49170 RTP/AVP 0 8 97\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:97 iLBC/8000\nm=video 51372 RTP/AVP 31 32\na=rtpmap:31 H261/90000\na=rtpmap:32 MPV/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/9.sdp",
    "content": "v=0\no=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com\ns=\nc=IN IP4 host.biloxi.example.com\nt=0 0\nm=audio 49172 RTP/AVP 99\na=rtpmap:99 iLBC/8000\nm=video 51374 RTP/AVP 31\na=rtpmap:31 H261/90000\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/firefox-1.sdp",
    "content": "v=0\no=mozilla...THIS_IS_SDPARTA-46.0.1 5115930144083302970 0 IN IP4 0.0.0.0\ns=-\nt=0 0\na=fingerprint:sha-256 24:67:5E:1B:9A:B9:CF:36:C5:30:8F:35:F7:B1:50:66:88:81:92:CB:29:BA:53:A5:02:C8:0A:A5:4E:9C:AE:D9\na=group:BUNDLE sdparta_0 sdparta_1 sdparta_2\na=ice-options:trickle\na=msid-semantic:WMS *\nm=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8\nc=IN IP4 0.0.0.0\na=sendrecv\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=fmtp:109 maxplaybackrate=48000;stereo=1\na=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3\na=ice-ufrag:3edc9012\na=mid:sdparta_0\na=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {bd1fafff-bfd0-40d4-b0a3-2a87cff307ee}\na=rtcp-mux\na=rtpmap:109 opus/48000/2\na=rtpmap:9 G722/8000/1\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=setup:actpass\na=ssrc:2121669360 cname:{387b0735-bde2-43a4-8484-7f5663b60f24}\nm=video 9 UDP/TLS/RTP/SAVPF 120 126 97\nc=IN IP4 0.0.0.0\na=sendrecv\na=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1\na=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1\na=fmtp:120 max-fs=12288;max-fr=60\na=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3\na=ice-ufrag:3edc9012\na=mid:sdparta_1\na=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {9e8f5867-a9aa-4489-8bd4-3a8a57a5e592}\na=rtcp-fb:120 nack\na=rtcp-fb:120 nack pli\na=rtcp-fb:120 ccm fir\na=rtcp-fb:126 nack\na=rtcp-fb:126 nack pli\na=rtcp-fb:126 ccm fir\na=rtcp-fb:97 nack\na=rtcp-fb:97 nack pli\na=rtcp-fb:97 ccm fir\na=rtcp-mux\na=rtpmap:120 VP8/90000\na=rtpmap:126 H264/90000\na=rtpmap:97 H264/90000\na=setup:actpass\na=ssrc:2158832026 cname:{387b0735-bde2-43a4-8484-7f5663b60f24}\nm=application 9 DTLS/SCTP 5000\nc=IN IP4 0.0.0.0\na=sendrecv\na=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3\na=ice-ufrag:3edc9012\na=mid:sdparta_2\na=sctpmap:5000 webrtc-datachannel 256\na=setup:actpass\na=ssrc:2670959794 cname:{387b0735-bde2-43a4-8484-7f5663b60f24}\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/firefox-2.sdp",
    "content": "v=0\no=mozilla...THIS_IS_SDPARTA-46.0.1 3068771576687940834 0 IN IP4 0.0.0.0\ns=-\nt=0 0\na=fingerprint:sha-256 AD:87:B3:11:E4:E2:BA:EF:D2:3F:2E:AC:24:57:8E:DC:1F:67:41:29:44:C4:96:E3:62:90:CC:90:59:CA:2C:84\na=group:BUNDLE sdparta_0 sdparta_1 sdparta_2\na=ice-options:trickle\na=msid-semantic:WMS *\nm=audio 9 UDP/TLS/RTP/SAVPF 109\nc=IN IP4 0.0.0.0\na=recvonly\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=fmtp:109 maxplaybackrate=48000;stereo=1\na=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5\na=ice-ufrag:a539544b\na=mid:sdparta_0\na=rtcp-mux\na=rtpmap:109 opus/48000/2\na=setup:active\na=ssrc:600995474 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a}\nm=video 9 UDP/TLS/RTP/SAVPF 120\nc=IN IP4 0.0.0.0\na=recvonly\na=fmtp:120 max-fs=12288;max-fr=60\na=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5\na=ice-ufrag:a539544b\na=mid:sdparta_1\na=rtcp-fb:120 nack\na=rtcp-fb:120 nack pli\na=rtcp-fb:120 ccm fir\na=rtcp-mux\na=rtpmap:120 VP8/90000\na=setup:active\na=ssrc:3480150809 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a}\nm=application 9 DTLS/SCTP 5000\nc=IN IP4 0.0.0.0\na=sendrecv\na=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5\na=ice-ufrag:a539544b\na=mid:sdparta_2\na=sctpmap:5000 webrtc-datachannel 256\na=setup:active\na=ssrc:3021788991 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a}\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/opera-1.sdp",
    "content": "v=0\no=- 1656229333038673902 2 IN IP4 127.0.0.1\ns=-\nt=0 0\na=group:BUNDLE audio video data\na=msid-semantic: WMS Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:1Jyk4q3nLIL5NiMx\na=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:actpass\na=mid:audio\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=sendrecv\na=rtcp-mux\na=rtpmap:111 opus/48000/2\na=rtcp-fb:111 transport-cc\na=fmtp:111 minptime=10; useinbandfec=1\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=rtpmap:9 G722/8000\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:106 CN/32000\na=rtpmap:105 CN/16000\na=rtpmap:13 CN/8000\na=rtpmap:126 telephone-event/8000\na=maxptime:60\na=ssrc:2233075910 cname:VhHMGYCjn4alR9zP\na=ssrc:2233075910 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 689d3496-0896-4d52-bce6-8e90512a368b\na=ssrc:2233075910 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4\na=ssrc:2233075910 label:689d3496-0896-4d52-bce6-8e90512a368b\nm=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:1Jyk4q3nLIL5NiMx\na=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:actpass\na=mid:video\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=extmap:4 urn:3gpp:video-orientation\na=sendrecv\na=rtcp-mux\na=rtpmap:100 VP8/90000\na=rtcp-fb:100 ccm fir\na=rtcp-fb:100 nack\na=rtcp-fb:100 nack pli\na=rtcp-fb:100 goog-remb\na=rtcp-fb:100 transport-cc\na=rtpmap:101 VP9/90000\na=rtcp-fb:101 ccm fir\na=rtcp-fb:101 nack\na=rtcp-fb:101 nack pli\na=rtcp-fb:101 goog-remb\na=rtcp-fb:101 transport-cc\na=rtpmap:116 red/90000\na=rtpmap:117 ulpfec/90000\na=rtpmap:96 rtx/90000\na=fmtp:96 apt=100\na=rtpmap:97 rtx/90000\na=fmtp:97 apt=101\na=rtpmap:98 rtx/90000\na=fmtp:98 apt=116\na=ssrc-group:FID 50498894 2399294607\na=ssrc:50498894 cname:VhHMGYCjn4alR9zP\na=ssrc:50498894 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70\na=ssrc:50498894 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4\na=ssrc:50498894 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70\na=ssrc:2399294607 cname:VhHMGYCjn4alR9zP\na=ssrc:2399294607 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70\na=ssrc:2399294607 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4\na=ssrc:2399294607 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70\nm=application 9 DTLS/SCTP 5000\nc=IN IP4 0.0.0.0\na=ice-ufrag:1Jyk4q3nLIL5NiMx\na=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:actpass\na=mid:data\na=sctpmap:5000 webrtc-datachannel 1024\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/opera-2.sdp",
    "content": "v=0\no=- 2013283641453412290 2 IN IP4 127.0.0.1\ns=-\nt=0 0\na=group:BUNDLE audio video data\na=msid-semantic: WMS\nm=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:YVa3KTlFDCwsfPOQ\na=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:active\na=mid:audio\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=recvonly\na=rtcp-mux\na=rtpmap:111 opus/48000/2\na=rtcp-fb:111 transport-cc\na=fmtp:111 minptime=10; useinbandfec=1\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=rtpmap:9 G722/8000\na=rtpmap:0 PCMU/8000\na=rtpmap:8 PCMA/8000\na=rtpmap:106 CN/32000\na=rtpmap:105 CN/16000\na=rtpmap:13 CN/8000\na=rtpmap:126 telephone-event/8000\na=maxptime:60\nm=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:YVa3KTlFDCwsfPOQ\na=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:active\na=mid:video\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\na=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\na=extmap:4 urn:3gpp:video-orientation\na=recvonly\na=rtcp-mux\na=rtpmap:100 VP8/90000\na=rtcp-fb:100 ccm fir\na=rtcp-fb:100 nack\na=rtcp-fb:100 nack pli\na=rtcp-fb:100 goog-remb\na=rtcp-fb:100 transport-cc\na=rtpmap:101 VP9/90000\na=rtcp-fb:101 ccm fir\na=rtcp-fb:101 nack\na=rtcp-fb:101 nack pli\na=rtcp-fb:101 goog-remb\na=rtcp-fb:101 transport-cc\na=rtpmap:116 red/90000\na=rtpmap:117 ulpfec/90000\na=rtpmap:96 rtx/90000\na=fmtp:96 apt=100\na=rtpmap:97 rtx/90000\na=fmtp:97 apt=101\na=rtpmap:98 rtx/90000\na=fmtp:98 apt=116\nm=application 9 DTLS/SCTP 5000\nc=IN IP4 0.0.0.0\nb=AS:30\na=ice-ufrag:YVa3KTlFDCwsfPOQ\na=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa\na=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA\na=setup:active\na=mid:data\na=sctpmap:5000 webrtc-datachannel 1024\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-1.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS local_stream_1\nm=audio 2345 RTP/SAVPF 111 103 104\nc=IN IP4 74.125.127.126\na=rtcp:2347 IN IP4 74.125.127.126\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2\na=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2\na=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2\na=ice-ufrag:ufrag_voice\na=ice-pwd:pwd_voice\na=mid:audio_content_name\na=sendrecv\na=rtcp-mux\na=rtcp-rsize\na=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params\na=rtpmap:111 opus/48000/2\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=ssrc:1 cname:stream_1_cname\na=ssrc:1 msid:local_stream_1 audio_track_id_1\na=ssrc:1 mslabel:local_stream_1\na=ssrc:1 label:audio_track_id_1\nm=video 3457 RTP/SAVPF 120\nc=IN IP4 74.125.224.39\na=rtcp:3456 IN IP4 74.125.224.39\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2\na=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2\na=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2\na=ice-ufrag:ufrag_video\na=ice-pwd:pwd_video\na=mid:video_content_name\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\na=rtpmap:120 VP8/90000\na=ssrc-group:FEC 2 3\na=ssrc:2 cname:stream_1_cname\na=ssrc:2 msid:local_stream_1 video_track_id_1\na=ssrc:2 mslabel:local_stream_1\na=ssrc:2 label:video_track_id_1\na=ssrc:3 cname:stream_1_cname\na=ssrc:3 msid:local_stream_1 video_track_id_1\na=ssrc:3 mslabel:local_stream_1\na=ssrc:3 label:video_track_id_1\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-2.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS local_stream_1\nm=audio 9 RTP/SAVPF 111 103 104\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:ufrag_voice\na=ice-pwd:pwd_voice\na=mid:audio_content_name\na=sendrecv\na=rtcp-mux\na=rtcp-rsize\na=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params\na=rtpmap:111 opus/48000/2\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=ssrc:1 cname:stream_1_cname\na=ssrc:1 msid:local_stream_1 audio_track_id_1\na=ssrc:1 mslabel:local_stream_1\na=ssrc:1 label:audio_track_id_1\nm=video 9 RTP/SAVPF 120\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:ufrag_video\na=ice-pwd:pwd_video\na=mid:video_content_name\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\na=rtpmap:120 VP8/90000\na=ssrc-group:FEC 2 3\na=ssrc:2 cname:stream_1_cname\na=ssrc:2 msid:local_stream_1 video_track_id_1\na=ssrc:2 mslabel:local_stream_1\na=ssrc:2 label:video_track_id_1\na=ssrc:3 cname:stream_1_cname\na=ssrc:3 msid:local_stream_1 video_track_id_1\na=ssrc:3 mslabel:local_stream_1\na=ssrc:3 label:video_track_id_1\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-3.sdp",
    "content": "m=application 9 RTP/SAVPF 101\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:ufrag_data\na=ice-pwd:pwd_data\na=mid:data_content_name\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5\na=rtpmap:101 google-data/90000\na=ssrc:10 cname:data_channel_cname\na=ssrc:10 msid:data_channel data_channeld0\na=ssrc:10 mslabel:data_channel\na=ssrc:10 label:data_channeld0\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-4.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS\nm=audio 9 RTP/SAVPF 111 103 104\nc=IN IP4 0.0.0.0\na=x-google-flag:conference\nm=video 9 RTP/SAVPF 120\nc=IN IP4 0.0.0.0\na=x-google-flag:conference\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-5.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS local_stream\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-6.sdp",
    "content": "m=audio 9 RTP/SAVPF 111\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:ufrag_voice\na=ice-pwd:pwd_voice\na=mid:audio_content_name\na=sendrecv\na=rtpmap:111 opus/48000/2\na=ssrc:1 cname:stream_1_cname\na=ssrc:1 msid:local_stream audio_track_id_1\na=ssrc:1 mslabel:local_stream\na=ssrc:1 label:audio_track_id_1\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-7.sdp",
    "content": "m=video 9 RTP/SAVPF 120\nc=IN IP4 0.0.0.0\na=rtcp:9 IN IP4 0.0.0.0\na=ice-ufrag:ufrag_video\na=ice-pwd:pwd_video\na=mid:video_content_name\na=sendrecv\na=rtpmap:120 VP8/90000\na=ssrc:2 cname:stream_1_cname\na=ssrc:2 msid:local_stream video_track_id_1\na=ssrc:2 mslabel:local_stream\na=ssrc:2 label:video_track_id_1\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-8.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS local_stream_1 local_stream_2\nm=audio 2345 RTP/SAVPF 111 103 104\nc=IN IP4 74.125.127.126\na=rtcp:2347 IN IP4 74.125.127.126\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2\na=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2\na=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2\na=ice-ufrag:ufrag_voice\na=ice-pwd:pwd_voice\na=mid:audio_content_name\na=sendrecv\na=rtcp-mux\na=rtcp-rsize\na=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params\na=rtpmap:111 opus/48000/2\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=ssrc:1 cname:stream_1_cname\na=ssrc:1 msid:local_stream_1 audio_track_id_1\na=ssrc:1 mslabel:local_stream_1\na=ssrc:1 label:audio_track_id_1\na=ssrc:4 cname:stream_2_cname\na=ssrc:4 msid:local_stream_2 audio_track_id_2\na=ssrc:4 mslabel:local_stream_2\na=ssrc:4 label:audio_track_id_2\nm=video 3457 RTP/SAVPF 120\nc=IN IP4 74.125.224.39\na=rtcp:3456 IN IP4 74.125.224.39\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2\na=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2\na=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2\na=ice-ufrag:ufrag_video\na=ice-pwd:pwd_video\na=mid:video_content_name\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\na=rtpmap:120 VP8/90000\na=ssrc-group:FEC 2 3\na=ssrc:2 cname:stream_1_cname\na=ssrc:2 msid:local_stream_1 video_track_id_1\na=ssrc:2 mslabel:local_stream_1\na=ssrc:2 label:video_track_id_1\na=ssrc:3 cname:stream_1_cname\na=ssrc:3 msid:local_stream_1 video_track_id_1\na=ssrc:3 mslabel:local_stream_1\na=ssrc:3 label:video_track_id_1\na=ssrc:5 cname:stream_2_cname\na=ssrc:5 msid:local_stream_2 video_track_id_2\na=ssrc:5 mslabel:local_stream_2\na=ssrc:5 label:video_track_id_2\na=ssrc:6 cname:stream_2_cname\na=ssrc:6 msid:local_stream_2 video_track_id_3\na=ssrc:6 mslabel:local_stream_2\na=ssrc:6 label:video_track_id_3\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-9.sdp",
    "content": "v=0\no=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1\ns=-\nt=0 0\na=msid-semantic: WMS local_stream_1 local_stream_2\nm=audio 2345 RTP/SAVPF 111 103 104\nc=IN IP4 74.125.127.126\na=rtcp:2347 IN IP4 74.125.127.126\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2\na=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2\na=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2\na=ice-ufrag:ufrag_voice\na=ice-pwd:pwd_voice\na=mid:audio_content_name\na=msid:local_stream_1 audio_track_id_1\na=sendrecv\na=rtcp-mux\na=rtcp-rsize\na=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params\na=rtpmap:111 opus/48000/2\na=rtpmap:103 ISAC/16000\na=rtpmap:104 ISAC/32000\na=ssrc:1 cname:stream_1_cname\na=ssrc:1 msid:local_stream_1 audio_track_id_1\na=ssrc:1 mslabel:local_stream_1\na=ssrc:1 label:audio_track_id_1\na=ssrc:4 cname:stream_2_cname\na=ssrc:4 msid:local_stream_2 audio_track_id_2\na=ssrc:4 mslabel:local_stream_2\na=ssrc:4 label:audio_track_id_2\nm=video 3457 RTP/SAVPF 120\nc=IN IP4 74.125.224.39\na=rtcp:3456 IN IP4 74.125.224.39\na=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2\na=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2\na=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2\na=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2\na=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2\na=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2\na=ice-ufrag:ufrag_video\na=ice-pwd:pwd_video\na=mid:video_content_name\na=msid:local_stream_1 video_track_id_1\na=sendrecv\na=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32\na=rtpmap:120 VP8/90000\na=ssrc-group:FEC 2 3\na=ssrc:2 cname:stream_1_cname\na=ssrc:2 msid:local_stream_1 video_track_id_1\na=ssrc:2 mslabel:local_stream_1\na=ssrc:2 label:video_track_id_1\na=ssrc:3 cname:stream_1_cname\na=ssrc:3 msid:local_stream_1 video_track_id_1\na=ssrc:3 mslabel:local_stream_1\na=ssrc:3 label:video_track_id_1\na=ssrc:5 cname:stream_2_cname\na=ssrc:5 msid:local_stream_2 video_track_id_2\na=ssrc:5 mslabel:local_stream_2\na=ssrc:5 label:video_track_id_2\na=ssrc:6 cname:stream_2_cname\na=ssrc:6 msid:local_stream_2 video_track_id_3\na=ssrc:6 mslabel:local_stream_2\na=ssrc:6 label:video_track_id_3\n"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/reports/crashes/.placeholder",
    "content": ""
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/reports/crashes/rtp/crash-7e2d460edd5d5d7f5548922f10489f468d1638bf",
    "content": "(print_fusDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/reports/memory-leaks/.placeholder",
    "content": ""
  },
  {
    "path": "worker/deps/webrtc-fuzzer-corpora/reports/timeouts/.placeholder",
    "content": ""
  },
  {
    "path": "worker/fbs/activeSpeakerObserver.fbs",
    "content": "namespace FBS.ActiveSpeakerObserver;\n\ntable ActiveSpeakerObserverOptions {\n    interval: uint16;\n}\n\n// Notifications from Worker.\n\ntable DominantSpeakerNotification {\n    producer_id: string (required);\n}\n"
  },
  {
    "path": "worker/fbs/audioLevelObserver.fbs",
    "content": "namespace FBS.AudioLevelObserver;\n\ntable AudioLevelObserverOptions {\n    max_entries: uint16;\n    threshold: int8;\n    interval: uint16;\n}\n\n// Notifications from Worker.\n\ntable Volume {\n    producer_id: string (required);\n    volume: int8;\n}\n\ntable VolumesNotification {\n    volumes: [Volume] (required);\n}\n"
  },
  {
    "path": "worker/fbs/common.fbs",
    "content": "namespace FBS.Common;\n\ntable StringString {\n    key: string (required);\n    value: string (required);\n}\n\ntable StringUint8 {\n    key: string (required);\n    value: uint8;\n}\n\ntable Uint16String {\n    key: uint16;\n    value: string (required);\n}\n\ntable Uint32String {\n    key: uint32;\n    value: string (required);\n}\n\ntable StringStringArray {\n    key: string (required);\n    values: [string] (required);\n}\n\n// NOTE (windows): IN|OUT are macros defined in windef.h.\nenum TraceDirection: uint8 {\n    DIRECTION_IN = 0,\n    DIRECTION_OUT\n}\n"
  },
  {
    "path": "worker/fbs/consumer.fbs",
    "content": "include \"common.fbs\";\ninclude \"rtpPacket.fbs\";\ninclude \"rtpParameters.fbs\";\ninclude \"rtpStream.fbs\";\n\nnamespace FBS.Consumer;\n\ntable ConsumerLayers {\n    spatial_layer: uint8;\n    temporal_layer: uint8 = null;\n}\n\ntable ConsumerScore {\n    score: uint8;\n    producer_score: uint8;\n    producer_scores: [uint8] (required);\n}\n\ntable SetPreferredLayersRequest {\n    preferred_layers: ConsumerLayers (required);\n}\n\ntable SetPreferredLayersResponse {\n    preferred_layers: ConsumerLayers;\n}\n\ntable SetPriorityRequest {\n    priority: uint8;\n}\n\ntable SetPriorityResponse {\n    priority: uint8;\n}\n\nenum TraceEventType: uint8 {\n    KEYFRAME = 0,\n    FIR,\n    NACK,\n    PLI,\n    RTP,\n}\n\ntable EnableTraceEventRequest {\n    events: [TraceEventType] (required);\n}\n\ntable DumpResponse {\n    data: ConsumerDump (required);\n}\n\ntable BaseConsumerDump {\n    id: string (required);\n    type: FBS.RtpParameters.Type;\n    producer_id: string (required);\n    kind: FBS.RtpParameters.MediaKind;\n    rtp_parameters: FBS.RtpParameters.RtpParameters (required);\n    consumable_rtp_encodings: [FBS.RtpParameters.RtpEncodingParameters] (required);\n    supported_codec_payload_types: [uint8] (required);\n    trace_event_types: [TraceEventType] (required);\n    paused: bool;\n    producer_paused: bool;\n    priority: uint8;\n}\n\ntable ConsumerDump {\n    base: BaseConsumerDump (required);\n    rtp_streams: [FBS.RtpStream.Dump] (required);\n    preferred_spatial_layer: int16 = null;\n    target_spatial_layer: int16 = null;\n    current_spatial_layer: int16 = null;\n    preferred_temporal_layer: int16 = null;\n    target_temporal_layer: int16 = null;\n    current_temporal_layer: int16 = null;\n}\n\ntable GetStatsResponse {\n    stats: [FBS.RtpStream.Stats] (required);\n}\n\n// Notifications from Worker.\n\ntable LayersChangeNotification {\n    layers: ConsumerLayers;\n}\n\ntable RtpNotification {\n    data: [ubyte] (required);\n}\n\ntable ScoreNotification {\n    score: ConsumerScore (required);\n}\n\nunion TraceInfo {\n    KeyFrameTraceInfo,\n    FirTraceInfo,\n    PliTraceInfo,\n    RtpTraceInfo,\n}\n\ntable KeyFrameTraceInfo {\n    rtp_packet: FBS.RtpPacket.Dump (required);\n    is_rtx: bool;\n}\n\ntable FirTraceInfo {\n    ssrc: uint32;\n}\n\ntable PliTraceInfo {\n    ssrc: uint32;\n}\n\ntable RtpTraceInfo {\n    rtp_packet: FBS.RtpPacket.Dump (required);\n    is_rtx: bool;\n}\n\ntable TraceNotification {\n    type: TraceEventType;\n    timestamp: uint64;\n    direction: FBS.Common.TraceDirection;\n    info: TraceInfo;\n}\n"
  },
  {
    "path": "worker/fbs/dataConsumer.fbs",
    "content": "include \"common.fbs\";\ninclude \"dataProducer.fbs\";\ninclude \"sctpParameters.fbs\";\n\nnamespace FBS.DataConsumer;\n\ntable GetBufferedAmountResponse {\n    buffered_amount: uint32;\n}\n\ntable SetBufferedAmountLowThresholdRequest {\n    threshold: uint32;\n}\n\ntable DumpResponse {\n    id: string (required);\n    data_producer_id: string (required);\n    type: FBS.DataProducer.Type;\n    sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters;\n    label: string (required);\n    protocol: string (required);\n    buffered_amount_low_threshold: uint32;\n    paused: bool;\n    data_producer_paused: bool;\n    subchannels: [uint16] (required);\n}\n\ntable GetStatsResponse {\n    timestamp: uint64;\n    label: string (required);\n    protocol: string (required);\n    messages_sent: uint64;\n    bytes_sent: uint64;\n    buffered_amount: uint32;\n}\n\ntable SendRequest {\n    ppid: uint32;\n    data: [uint8] (required);\n}\n\ntable SetSubchannelsRequest {\n    subchannels: [uint16] (required);\n}\n\ntable SetSubchannelsResponse {\n    subchannels: [uint16] (required);\n}\n\ntable AddSubchannelRequest {\n    subchannel: uint16;\n}\n\ntable AddSubchannelResponse {\n    subchannels: [uint16] (required);\n}\n\ntable RemoveSubchannelRequest {\n    subchannel: uint16;\n}\n\ntable RemoveSubchannelResponse {\n    subchannels: [uint16] (required);\n}\n\n// Notifications from Worker.\n\ntable BufferedAmountLowNotification {\n    buffered_amount: uint32;\n}\n\ntable MessageNotification {\n    ppid: uint32;\n    data: [uint8] (required);\n}\n"
  },
  {
    "path": "worker/fbs/dataProducer.fbs",
    "content": "include \"sctpParameters.fbs\";\n\nnamespace FBS.DataProducer;\n\nenum Type: uint8 {\n    SCTP,\n    DIRECT\n}\n\ntable DumpResponse {\n    id: string (required);\n    type: Type;\n    sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters;\n    label: string (required);\n    protocol: string (required);\n    paused: bool;\n}\n\ntable GetStatsResponse {\n    timestamp: uint64;\n    label: string (required);\n    protocol: string (required);\n    messages_received: uint64;\n    bytes_received: uint64;\n    buffered_amount: uint32;\n}\n\ntable SendNotification {\n    ppid: uint32;\n    data: [uint8] (required);\n    subchannels: [uint16];\n    required_subchannel: uint16 = null;\n}\n"
  },
  {
    "path": "worker/fbs/directTransport.fbs",
    "content": "include \"transport.fbs\";\n\nnamespace FBS.DirectTransport;\n\ntable DirectTransportOptions {\n    base: FBS.Transport.Options (required);\n}\n\ntable DumpResponse {\n    base: FBS.Transport.Dump (required);\n}\n\ntable GetStatsResponse {\n    base: FBS.Transport.Stats (required);\n}\n\n// Notifications from Worker.\n\ntable RtcpNotification {\n    data: [uint8] (required);\n}\n"
  },
  {
    "path": "worker/fbs/liburing.fbs",
    "content": "namespace FBS.LibUring;\n\ntable Dump {\n    sqe_process_count: uint64;\n    sqe_miss_count: uint64;\n    user_data_miss_count: uint64;\n}\n"
  },
  {
    "path": "worker/fbs/log.fbs",
    "content": "namespace FBS.Log;\n\ntable Log {\n    data: string (required);\n}\n"
  },
  {
    "path": "worker/fbs/meson.build",
    "content": "flatbuffers_schemas = [\n  'activeSpeakerObserver.fbs',\n  'audioLevelObserver.fbs',\n  'common.fbs',\n  'consumer.fbs',\n  'dataConsumer.fbs',\n  'dataProducer.fbs',\n  'directTransport.fbs',\n  'liburing.fbs',\n  'log.fbs',\n  'message.fbs',\n  'notification.fbs',\n  'pipeTransport.fbs',\n  'plainTransport.fbs',\n  'producer.fbs',\n  'request.fbs',\n  'response.fbs',\n  'router.fbs',\n  'rtpObserver.fbs',\n  'rtpPacket.fbs',\n  'rtpParameters.fbs',\n  'rtpStream.fbs',\n  'rtxStream.fbs',\n  'sctpAssociation.fbs',\n  'sctpParameters.fbs',\n  'srtpParameters.fbs',\n  'transport.fbs',\n  'webRtcServer.fbs',\n  'webRtcTransport.fbs',\n  'worker.fbs',\n]\n\n# Directory from which worker code will include the header files.\nflatbuffers_cpp_out_dir = 'FBS'\n\nflatc = find_program('flatc')\nflatbuffers_generator = custom_target('flatbuffers-generator',\n  output: flatbuffers_cpp_out_dir,\n  input: flatbuffers_schemas,\n  command : [\n    flatc,\n    '--cpp',\n    '--cpp-field-case-style', 'lower',\n    '--reflect-names',\n    '--scoped-enums',\n    '--filename-suffix', '',\n    '-o', '@OUTPUT@',\n    '@INPUT@'\n  ],\n  build_by_default: true,\n)\n\nflatbuffers_generator_dep = declare_dependency(\n  include_directories: '.',\n)\n"
  },
  {
    "path": "worker/fbs/message.fbs",
    "content": "include \"log.fbs\";\ninclude \"notification.fbs\";\ninclude \"request.fbs\";\ninclude \"response.fbs\";\n\nnamespace FBS.Message;\n\nunion Body {\n    Request: FBS.Request.Request,\n    Response: FBS.Response.Response,\n    Notification: FBS.Notification.Notification,\n    Log: FBS.Log.Log,\n}\n\ntable Message {\n    data: Body (required);\n}\n\nroot_type Message;\n"
  },
  {
    "path": "worker/fbs/notification.fbs",
    "content": "include \"transport.fbs\";\ninclude \"webRtcTransport.fbs\";\ninclude \"plainTransport.fbs\";\ninclude \"directTransport.fbs\";\ninclude \"producer.fbs\";\ninclude \"dataProducer.fbs\";\ninclude \"dataConsumer.fbs\";\ninclude \"activeSpeakerObserver.fbs\";\ninclude \"audioLevelObserver.fbs\";\n\nnamespace FBS.Notification;\n\nenum Event: uint8 {\n    // Notifications to worker.\n    WORKER_CLOSE = 0,\n    TRANSPORT_SEND_RTCP,\n    PRODUCER_SEND,\n    DATAPRODUCER_SEND,\n\n    // Notifications from worker.\n    WORKER_RUNNING,\n    TRANSPORT_SCTP_STATE_CHANGE,\n    TRANSPORT_TRACE,\n    WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE,\n    WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n    WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n    PLAINTRANSPORT_TUPLE,\n    PLAINTRANSPORT_RTCP_TUPLE,\n    DIRECTTRANSPORT_RTCP,\n    PRODUCER_SCORE,\n    PRODUCER_TRACE,\n    PRODUCER_VIDEO_ORIENTATION_CHANGE,\n    CONSUMER_PRODUCER_PAUSE,\n    CONSUMER_PRODUCER_RESUME,\n    CONSUMER_PRODUCER_CLOSE,\n    CONSUMER_LAYERS_CHANGE,\n    CONSUMER_RTP,\n    CONSUMER_SCORE,\n    CONSUMER_TRACE,\n    DATACONSUMER_BUFFERED_AMOUNT_LOW,\n    DATACONSUMER_SCTP_SENDBUFFER_FULL,\n    DATACONSUMER_DATAPRODUCER_PAUSE,\n    DATACONSUMER_DATAPRODUCER_RESUME,\n    DATACONSUMER_DATAPRODUCER_CLOSE,\n    DATACONSUMER_MESSAGE,\n    ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER,\n    AUDIOLEVELOBSERVER_SILENCE,\n    AUDIOLEVELOBSERVER_VOLUMES,\n}\n\nunion Body {\n    // Notifications to worker.\n    Transport_SendRtcpNotification: FBS.Transport.SendRtcpNotification,\n    Transport_SctpStateChangeNotification: FBS.Transport.SctpStateChangeNotification,\n    Producer_SendNotification: FBS.Producer.SendNotification,\n    DataProducer_SendNotification: FBS.DataProducer.SendNotification,\n\n    // Notifications from worker.\n    Transport_TraceNotification: FBS.Transport.TraceNotification,\n    WebRtcTransport_IceSelectedTupleChangeNotification: FBS.WebRtcTransport.IceSelectedTupleChangeNotification,\n    WebRtcTransport_IceStateChangeNotification: FBS.WebRtcTransport.IceStateChangeNotification,\n    WebRtcTransport_DtlsStateChangeNotification: FBS.WebRtcTransport.DtlsStateChangeNotification,\n    PlainTransport_TupleNotification: FBS.PlainTransport.TupleNotification,\n    PlainTransport_RtcpTupleNotification: FBS.PlainTransport.RtcpTupleNotification,\n    DirectTransport_RtcpNotification: FBS.DirectTransport.RtcpNotification,\n    Producer_ScoreNotification: FBS.Producer.ScoreNotification,\n    Producer_TraceNotification: FBS.Producer.TraceNotification,\n    Producer_VideoOrientationChangeNotification: FBS.Producer.VideoOrientationChangeNotification,\n    Consumer_LayersChangeNotification: FBS.Consumer.LayersChangeNotification,\n    Consumer_RtpNotification: FBS.Consumer.RtpNotification,\n    Consumer_ScoreNotification: FBS.Consumer.ScoreNotification,\n    Consumer_TraceNotification: FBS.Consumer.TraceNotification,\n    DataConsumer_MessageNotification: FBS.DataConsumer.MessageNotification,\n    DataConsumer_BufferedAmountLowNotification: FBS.DataConsumer.BufferedAmountLowNotification,\n    ActiveSpeakerObserver_DominantSpeakerNotification: FBS.ActiveSpeakerObserver.DominantSpeakerNotification,\n    AudioLevelObserver_VolumesNotification: FBS.AudioLevelObserver.VolumesNotification,\n}\n\ntable Notification {\n    handler_id: string (required);\n    event: Event;\n    body: Body;\n}\n"
  },
  {
    "path": "worker/fbs/pipeTransport.fbs",
    "content": "include \"transport.fbs\";\ninclude \"srtpParameters.fbs\";\n\nnamespace FBS.PipeTransport;\n\ntable PipeTransportOptions {\n    base: FBS.Transport.Options (required);\n    listen_info: FBS.Transport.ListenInfo (required);\n    enable_rtx: bool;\n    enable_srtp: bool;\n}\n\ntable ConnectRequest {\n    ip: string (required);\n    port: uint16 = null;\n    srtp_parameters: FBS.SrtpParameters.SrtpParameters;\n}\n\ntable ConnectResponse {\n    tuple: FBS.Transport.Tuple (required);\n}\n\ntable DumpResponse {\n    base: FBS.Transport.Dump (required);\n    tuple: FBS.Transport.Tuple (required);\n    rtx: bool;\n    srtp_parameters: FBS.SrtpParameters.SrtpParameters;\n}\n\ntable GetStatsResponse {\n    base: FBS.Transport.Stats (required);\n    tuple: FBS.Transport.Tuple (required);\n}\n"
  },
  {
    "path": "worker/fbs/plainTransport.fbs",
    "content": "include \"transport.fbs\";\ninclude \"sctpParameters.fbs\";\ninclude \"srtpParameters.fbs\";\n\nnamespace FBS.PlainTransport;\n\ntable PlainTransportOptions {\n    base: FBS.Transport.Options (required);\n    listen_info: FBS.Transport.ListenInfo (required);\n    rtcp_listen_info: FBS.Transport.ListenInfo;\n    rtcp_mux: bool;\n    comedia: bool;\n    enable_srtp: bool;\n    srtp_crypto_suite: FBS.SrtpParameters.SrtpCryptoSuite = null;\n}\n\ntable ConnectRequest {\n    ip: string;\n    port: uint16 = null;\n    rtcp_port: uint16 = null;\n    srtp_parameters: FBS.SrtpParameters.SrtpParameters;\n}\n\ntable ConnectResponse {\n    tuple: FBS.Transport.Tuple (required);\n    rtcp_tuple: FBS.Transport.Tuple;\n    srtp_parameters: FBS.SrtpParameters.SrtpParameters;\n}\n\ntable DumpResponse {\n    base: FBS.Transport.Dump (required);\n    rtcp_mux: bool;\n    comedia: bool;\n    tuple: FBS.Transport.Tuple (required);\n    rtcp_tuple: FBS.Transport.Tuple;\n    srtp_parameters: FBS.SrtpParameters.SrtpParameters;\n}\n\ntable GetStatsResponse {\n    base: FBS.Transport.Stats (required);\n    rtcp_mux: bool;\n    comedia: bool;\n    tuple: FBS.Transport.Tuple (required);\n    rtcp_tuple: FBS.Transport.Tuple;\n}\n\n// Notifications from Worker.\n\ntable TupleNotification {\n    tuple: FBS.Transport.Tuple (required);\n}\n\ntable RtcpTupleNotification {\n    tuple: FBS.Transport.Tuple (required);\n}\n"
  },
  {
    "path": "worker/fbs/producer.fbs",
    "content": "include \"common.fbs\";\ninclude \"rtpPacket.fbs\";\ninclude \"rtpParameters.fbs\";\ninclude \"rtpStream.fbs\";\n\nnamespace FBS.Producer;\n\nenum TraceEventType: uint8 {\n    KEYFRAME = 0,\n    FIR,\n    NACK,\n    PLI,\n    RTP,\n    SR,\n}\n\ntable EnableTraceEventRequest {\n    events: [TraceEventType] (required);\n}\n\ntable DumpResponse {\n    id: string (required);\n    kind: FBS.RtpParameters.MediaKind;\n    type: FBS.RtpParameters.Type;\n    rtp_parameters: FBS.RtpParameters.RtpParameters (required);\n    rtp_mapping: FBS.RtpParameters.RtpMapping (required);\n    rtp_streams: [FBS.RtpStream.Dump] (required);\n    trace_event_types: [TraceEventType] (required);\n    paused: bool;\n}\n\ntable GetStatsResponse {\n    stats: [FBS.RtpStream.Stats] (required);\n}\n\ntable SendNotification {\n    data: [uint8] (required);\n}\n\n// Notifications from Worker.\n\ntable Score {\n    encoding_idx: uint32;\n    ssrc: uint32;\n    rid: string;\n    score: uint8;\n}\n\ntable ScoreNotification {\n    scores: [Score] (required);\n}\n\ntable VideoOrientationChangeNotification {\n    camera: bool;\n    flip: bool;\n    rotation: uint16;\n}\n\nunion TraceInfo {\n    KeyFrameTraceInfo,\n    FirTraceInfo,\n    PliTraceInfo,\n    RtpTraceInfo,\n    SrTraceInfo,\n}\n\ntable KeyFrameTraceInfo {\n    rtp_packet: FBS.RtpPacket.Dump (required);\n    is_rtx: bool;\n}\n\ntable FirTraceInfo {\n    ssrc: uint32;\n}\n\ntable PliTraceInfo {\n    ssrc: uint32;\n}\n\ntable RtpTraceInfo {\n    rtp_packet: FBS.RtpPacket.Dump (required);\n    is_rtx: bool;\n}\n\ntable SrTraceInfo {\n    ssrc: uint32;\n    ntp_sec: uint32;\n    ntp_frac: uint32;\n    rtp_ts: uint32;\n    packet_count: uint32;\n    octet_count: uint32;\n}\n\ntable TraceNotification {\n    type: TraceEventType;\n    timestamp: uint64;\n    direction: FBS.Common.TraceDirection;\n    info: TraceInfo;\n}\n"
  },
  {
    "path": "worker/fbs/request.fbs",
    "content": "include \"worker.fbs\";\ninclude \"router.fbs\";\ninclude \"transport.fbs\";\ninclude \"producer.fbs\";\ninclude \"consumer.fbs\";\ninclude \"dataConsumer.fbs\";\ninclude \"rtpObserver.fbs\";\n\nnamespace FBS.Request;\n\nenum Method: uint8 {\n    WORKER_DUMP = 0,\n    WORKER_GET_RESOURCE_USAGE,\n    WORKER_UPDATE_SETTINGS,\n    WORKER_CREATE_WEBRTCSERVER,\n    WORKER_CREATE_ROUTER,\n    WORKER_WEBRTCSERVER_CLOSE,\n    WORKER_CLOSE_ROUTER,\n    WEBRTCSERVER_DUMP,\n    ROUTER_DUMP,\n    ROUTER_CREATE_WEBRTCTRANSPORT,\n    ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER,\n    ROUTER_CREATE_PLAINTRANSPORT,\n    ROUTER_CREATE_PIPETRANSPORT,\n    ROUTER_CREATE_DIRECTTRANSPORT,\n    ROUTER_CLOSE_TRANSPORT,\n    ROUTER_CREATE_ACTIVESPEAKEROBSERVER,\n    ROUTER_CREATE_AUDIOLEVELOBSERVER,\n    ROUTER_CLOSE_RTPOBSERVER,\n    TRANSPORT_DUMP,\n    TRANSPORT_GET_STATS,\n    TRANSPORT_CONNECT,\n    TRANSPORT_SET_MAX_INCOMING_BITRATE,\n    TRANSPORT_SET_MAX_OUTGOING_BITRATE,\n    TRANSPORT_SET_MIN_OUTGOING_BITRATE,\n    TRANSPORT_RESTART_ICE,\n    TRANSPORT_PRODUCE,\n    TRANSPORT_PRODUCE_DATA,\n    TRANSPORT_CONSUME,\n    TRANSPORT_CONSUME_DATA,\n    TRANSPORT_ENABLE_TRACE_EVENT,\n    TRANSPORT_CLOSE_PRODUCER,\n    TRANSPORT_CLOSE_CONSUMER,\n    TRANSPORT_CLOSE_DATAPRODUCER,\n    TRANSPORT_CLOSE_DATACONSUMER,\n    PLAINTRANSPORT_CONNECT,\n    PIPETRANSPORT_CONNECT,\n    WEBRTCTRANSPORT_CONNECT,\n    PRODUCER_DUMP,\n    PRODUCER_GET_STATS,\n    PRODUCER_PAUSE,\n    PRODUCER_RESUME,\n    PRODUCER_ENABLE_TRACE_EVENT,\n    CONSUMER_DUMP,\n    CONSUMER_GET_STATS,\n    CONSUMER_PAUSE,\n    CONSUMER_RESUME,\n    CONSUMER_SET_PREFERRED_LAYERS,\n    CONSUMER_SET_PRIORITY,\n    CONSUMER_REQUEST_KEY_FRAME,\n    CONSUMER_ENABLE_TRACE_EVENT,\n    DATAPRODUCER_DUMP,\n    DATAPRODUCER_GET_STATS,\n    DATAPRODUCER_PAUSE,\n    DATAPRODUCER_RESUME,\n    DATACONSUMER_DUMP,\n    DATACONSUMER_GET_STATS,\n    DATACONSUMER_PAUSE,\n    DATACONSUMER_RESUME,\n    DATACONSUMER_GET_BUFFERED_AMOUNT,\n    DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD,\n    DATACONSUMER_SEND,\n    DATACONSUMER_SET_SUBCHANNELS,\n    DATACONSUMER_ADD_SUBCHANNEL,\n    DATACONSUMER_REMOVE_SUBCHANNEL,\n    RTPOBSERVER_PAUSE,\n    RTPOBSERVER_RESUME,\n    RTPOBSERVER_ADD_PRODUCER,\n    RTPOBSERVER_REMOVE_PRODUCER,\n}\n\nunion Body {\n    Worker_UpdateSettingsRequest: FBS.Worker.UpdateSettingsRequest,\n    Worker_CreateWebRtcServerRequest: FBS.Worker.CreateWebRtcServerRequest,\n    Worker_CloseWebRtcServerRequest: FBS.Worker.CloseWebRtcServerRequest,\n    Worker_CreateRouterRequest: FBS.Worker.CreateRouterRequest,\n    Worker_CloseRouterRequest: FBS.Worker.CloseRouterRequest,\n    Router_CreateWebRtcTransportRequest: FBS.Router.CreateWebRtcTransportRequest,\n    Router_CreatePlainTransportRequest: FBS.Router.CreatePlainTransportRequest,\n    Router_CreatePipeTransportRequest: FBS.Router.CreatePipeTransportRequest,\n    Router_CreateDirectTransportRequest: FBS.Router.CreateDirectTransportRequest,\n    Router_CreateActiveSpeakerObserverRequest: FBS.Router.CreateActiveSpeakerObserverRequest,\n    Router_CreateAudioLevelObserverRequest: FBS.Router.CreateAudioLevelObserverRequest,\n    Router_CloseTransportRequest: FBS.Router.CloseTransportRequest,\n    Router_CloseRtpObserverRequest: FBS.Router.CloseRtpObserverRequest,\n    Transport_SetMaxIncomingBitrateRequest: FBS.Transport.SetMaxIncomingBitrateRequest,\n    Transport_SetMaxOutgoingBitrateRequest: FBS.Transport.SetMaxOutgoingBitrateRequest,\n    Transport_SetMinOutgoingBitrateRequest: FBS.Transport.SetMinOutgoingBitrateRequest,\n    Transport_ProduceRequest: FBS.Transport.ProduceRequest,\n    Transport_ConsumeRequest: FBS.Transport.ConsumeRequest,\n    Transport_ProduceDataRequest: FBS.Transport.ProduceDataRequest,\n    Transport_ConsumeDataRequest: FBS.Transport.ConsumeDataRequest,\n    Transport_EnableTraceEventRequest: FBS.Transport.EnableTraceEventRequest,\n    Transport_CloseProducerRequest: FBS.Transport.CloseProducerRequest,\n    Transport_CloseConsumerRequest: FBS.Transport.CloseConsumerRequest,\n    Transport_CloseDataProducerRequest: FBS.Transport.CloseDataProducerRequest,\n    Transport_CloseDataConsumerRequest: FBS.Transport.CloseDataConsumerRequest,\n    PlainTransport_ConnectRequest: FBS.PlainTransport.ConnectRequest,\n    PipeTransport_ConnectRequest: FBS.PipeTransport.ConnectRequest,\n    WebRtcTransport_ConnectRequest: FBS.WebRtcTransport.ConnectRequest,\n    Producer_EnableTraceEventRequest: FBS.Producer.EnableTraceEventRequest,\n    Consumer_SetPreferredLayersRequest: FBS.Consumer.SetPreferredLayersRequest,\n    Consumer_SetPriorityRequest: FBS.Consumer.SetPriorityRequest,\n    Consumer_EnableTraceEventRequest: FBS.Consumer.EnableTraceEventRequest,\n    DataConsumer_SetBufferedAmountLowThresholdRequest: FBS.DataConsumer.SetBufferedAmountLowThresholdRequest,\n    DataConsumer_SendRequest: FBS.DataConsumer.SendRequest,\n    DataConsumer_SetSubchannelsRequest: FBS.DataConsumer.SetSubchannelsRequest,\n    DataConsumer_AddSubchannelRequest: FBS.DataConsumer.AddSubchannelRequest,\n    DataConsumer_RemoveSubchannelRequest: FBS.DataConsumer.RemoveSubchannelRequest,\n    RtpObserver_AddProducerRequest: FBS.RtpObserver.AddProducerRequest,\n    RtpObserver_RemoveProducerRequest: FBS.RtpObserver.RemoveProducerRequest,\n}\n\ntable Request {\n    id: uint32;\n    method: Method;\n    handler_id: string (required);\n    body: Body;\n}\n\nroot_type Request;\n"
  },
  {
    "path": "worker/fbs/response.fbs",
    "content": "include \"worker.fbs\";\ninclude \"router.fbs\";\ninclude \"webRtcServer.fbs\";\ninclude \"transport.fbs\";\ninclude \"producer.fbs\";\ninclude \"consumer.fbs\";\ninclude \"dataProducer.fbs\";\ninclude \"dataConsumer.fbs\";\n\nnamespace FBS.Response;\n\nunion Body {\n    Worker_DumpResponse: FBS.Worker.DumpResponse,\n    Worker_ResourceUsageResponse: FBS.Worker.ResourceUsageResponse,\n    WebRtcServer_DumpResponse: FBS.WebRtcServer.DumpResponse,\n    Router_DumpResponse: FBS.Router.DumpResponse,\n    Transport_ProduceResponse: FBS.Transport.ProduceResponse,\n    Transport_ConsumeResponse: FBS.Transport.ConsumeResponse,\n    Transport_RestartIceResponse: FBS.Transport.RestartIceResponse,\n    PlainTransport_ConnectResponse: FBS.PlainTransport.ConnectResponse,\n    PlainTransport_DumpResponse: FBS.PlainTransport.DumpResponse,\n    PlainTransport_GetStatsResponse: FBS.PlainTransport.GetStatsResponse,\n    PipeTransport_ConnectResponse: FBS.PipeTransport.ConnectResponse,\n    PipeTransport_DumpResponse: FBS.PipeTransport.DumpResponse,\n    PipeTransport_GetStatsResponse: FBS.PipeTransport.GetStatsResponse,\n    DirectTransport_DumpResponse: FBS.DirectTransport.DumpResponse,\n    DirectTransport_GetStatsResponse: FBS.DirectTransport.GetStatsResponse,\n    WebRtcTransport_ConnectResponse: FBS.WebRtcTransport.ConnectResponse,\n    WebRtcTransport_DumpResponse: FBS.WebRtcTransport.DumpResponse,\n    WebRtcTransport_GetStatsResponse: FBS.WebRtcTransport.GetStatsResponse,\n    Producer_DumpResponse: FBS.Producer.DumpResponse,\n    Producer_GetStatsResponse: FBS.Producer.GetStatsResponse,\n    Consumer_DumpResponse: FBS.Consumer.DumpResponse,\n    Consumer_GetStatsResponse: FBS.Consumer.GetStatsResponse,\n    Consumer_SetPreferredLayersResponse: FBS.Consumer.SetPreferredLayersResponse,\n    Consumer_SetPriorityResponse: FBS.Consumer.SetPriorityResponse,\n    DataProducer_DumpResponse: FBS.DataProducer.DumpResponse,\n    DataProducer_GetStatsResponse: FBS.DataProducer.GetStatsResponse,\n    DataConsumer_GetBufferedAmountResponse: FBS.DataConsumer.GetBufferedAmountResponse,\n    DataConsumer_DumpResponse: FBS.DataConsumer.DumpResponse,\n    DataConsumer_GetStatsResponse: FBS.DataConsumer.GetStatsResponse,\n    DataConsumer_SetSubchannelsResponse: FBS.DataConsumer.SetSubchannelsResponse,\n    DataConsumer_AddSubchannelResponse: FBS.DataConsumer.AddSubchannelResponse,\n    DataConsumer_RemoveSubchannelResponse: FBS.DataConsumer.RemoveSubchannelResponse\n}\n\ntable Response {\n    id: uint32;\n    accepted: bool;\n    body: Body;\n    error: string;\n    reason: string;\n}\n\n"
  },
  {
    "path": "worker/fbs/router.fbs",
    "content": "include \"common.fbs\";\ninclude \"activeSpeakerObserver.fbs\";\ninclude \"audioLevelObserver.fbs\";\ninclude \"transport.fbs\";\ninclude \"pipeTransport.fbs\";\ninclude \"plainTransport.fbs\";\ninclude \"webRtcTransport.fbs\";\ninclude \"directTransport.fbs\";\n\nnamespace FBS.Router;\n\ntable DumpResponse {\n    id: string (required);\n    transport_ids: [string] (required);\n    rtp_observer_ids: [string] (required);\n    map_producer_id_consumer_ids: [FBS.Common.StringStringArray] (required);\n    map_consumer_id_producer_id: [FBS.Common.StringString] (required);\n    map_producer_id_observer_ids: [FBS.Common.StringStringArray] (required);\n    map_data_producer_id_data_consumer_ids: [FBS.Common.StringStringArray] (required);\n    map_data_consumer_id_data_producer_id: [FBS.Common.StringString] (required);\n}\n\ntable CreatePipeTransportRequest {\n    transport_id: string (required);\n    options: FBS.PipeTransport.PipeTransportOptions (required);\n}\n\ntable CreatePlainTransportRequest {\n    transport_id: string (required);\n    options: FBS.PlainTransport.PlainTransportOptions (required);\n}\n\ntable CreateWebRtcTransportRequest {\n    transport_id: string (required);\n    options: FBS.WebRtcTransport.WebRtcTransportOptions (required);\n}\n\ntable CreateDirectTransportRequest {\n    transport_id: string (required);\n    options: FBS.DirectTransport.DirectTransportOptions (required);\n}\n\ntable CreateAudioLevelObserverRequest {\n    rtp_observer_id: string (required);\n    options: FBS.AudioLevelObserver.AudioLevelObserverOptions (required);\n}\n\ntable CreateActiveSpeakerObserverRequest {\n    rtp_observer_id: string (required);\n    options: FBS.ActiveSpeakerObserver.ActiveSpeakerObserverOptions (required);\n}\n\ntable CloseTransportRequest {\n    transport_id: string (required);\n}\n\ntable CloseRtpObserverRequest {\n    rtp_observer_id: string (required);\n}\n\n"
  },
  {
    "path": "worker/fbs/rtpObserver.fbs",
    "content": "namespace FBS.RtpObserver;\n\ntable AddProducerRequest {\n    producer_id: string (required);\n}\n\ntable RemoveProducerRequest {\n    producer_id: string (required);\n}\n\n"
  },
  {
    "path": "worker/fbs/rtpPacket.fbs",
    "content": "include \"common.fbs\";\n\nnamespace FBS.RtpPacket;\n\ntable Dump {\n    payload_type: uint8;\n    sequence_number: uint16;\n    timestamp: uint32;\n    marker: bool;\n    ssrc: uint32;\n    is_key_frame: bool;\n    size: uint64;\n    payload_size: uint64;\n    spatial_layer: uint8;\n    temporal_layer: uint8;\n    mid: string;\n    rid: string;\n    rrid: string;\n    wide_sequence_number: uint16 = null;\n}\n\n"
  },
  {
    "path": "worker/fbs/rtpParameters.fbs",
    "content": "namespace FBS.RtpParameters;\n\nenum MediaKind: uint8 {\n    AUDIO,\n    VIDEO\n}\n\nenum Type: uint8 {\n    SIMPLE,\n    SIMULCAST,\n    SVC,\n    PIPE\n}\n\n// Boolean is a uint8/byte type.\ntable Boolean {\n    value: uint8;\n}\n\ntable Integer32 {\n    value: int32;\n}\n\ntable Integer32Array {\n    value: [int32];\n}\n\ntable Double {\n    value: double;\n}\n\ntable String {\n    value: string (required);\n}\n\nunion Value {\n    Boolean,\n    Integer32,\n    Double,\n    String,\n    Integer32Array,\n}\n\ntable Parameter {\n    name: string (required);\n    value: Value (required);\n}\n\ntable RtcpFeedback {\n    // TODO: Create a specifc type.\n    type: string (required);\n    parameter: string;\n}\n\ntable RtpCodecParameters {\n    mime_type: string (required);\n    payload_type: uint8;\n    clock_rate: uint32;\n    channels: uint8 = null;\n    parameters: [Parameter];\n    rtcp_feedback: [RtcpFeedback];\n}\n\nenum RtpHeaderExtensionUri: uint8 {\n    Mid,\n    RtpStreamId,\n    RepairRtpStreamId,\n    AbsSendTime,\n    TransportWideCcDraft01,\n    SsrcAudioLevel,\n    DependencyDescriptor,\n    VideoOrientation,\n    TimeOffset,\n    AbsCaptureTime,\n    PlayoutDelay,\n    MediasoupPacketId,\n}\n\ntable RtpHeaderExtensionParameters {\n    uri: RtpHeaderExtensionUri;\n    id: uint8;\n    encrypt: bool = false;\n    parameters: [Parameter];\n}\n\ntable Rtx {\n    ssrc: uint32;\n}\n\ntable RtpEncodingParameters {\n    ssrc: uint32 = null;\n    rid: string;\n    codec_payload_type: uint8 = null;\n    rtx: Rtx;\n    dtx: bool = false;\n    scalability_mode: string;\n    max_bitrate: uint32 = null;\n}\n\ntable RtcpParameters {\n    cname: string;\n    reduced_size: bool = true;\n}\n\ntable RtpParameters {\n    mid: string;\n    codecs: [RtpCodecParameters] (required);\n    header_extensions: [RtpHeaderExtensionParameters] (required);\n    encodings: [RtpEncodingParameters] (required);\n    rtcp: RtcpParameters (required);\n    msid: string;\n}\n\ntable CodecMapping {\n    payload_type: uint8;\n    mapped_payload_type: uint8;\n}\n\ntable EncodingMapping {\n    rid: string;\n    ssrc: uint32 = null;\n    scalability_mode: string;\n    mapped_ssrc: uint32;\n}\n\ntable RtpMapping {\n    codecs: [CodecMapping] (required);\n    encodings: [EncodingMapping] (required);\n}\n"
  },
  {
    "path": "worker/fbs/rtpStream.fbs",
    "content": "include \"rtpParameters.fbs\";\ninclude \"rtxStream.fbs\";\n\nnamespace FBS.RtpStream;\n\ntable Params {\n    encoding_idx: uint32;\n    ssrc: uint32;\n    payload_type: uint8;\n    mime_type: string (required);\n    clock_rate: uint32;\n    rid: string;\n    cname: string (required);\n    rtx_ssrc: uint32 = null;\n    rtx_payload_type: uint8 = null;\n    use_nack: bool;\n    use_pli: bool;\n    use_fir: bool;\n    use_in_band_fec: bool;\n    use_dtx: bool;\n    spatial_layers: uint8;\n    temporal_layers: uint8;\n}\n\ntable Dump {\n    params: Params (required);\n    score: uint8;\n    rtx_stream: FBS.RtxStream.RtxDump;\n}\n\ntable BitrateByLayer {\n    layer: string (required);\n    bitrate: uint32;\n}\n\nunion StatsData {\n    BaseStats,\n    RecvStats,\n    SendStats,\n}\n\ntable Stats {\n    data: StatsData (required);\n}\n\ntable BaseStats {\n    timestamp: uint64;\n    ssrc: uint32;\n    kind: FBS.RtpParameters.MediaKind;\n    mime_type: string (required);\n    packets_lost: int32;\n    fraction_lost: uint8;\n    jitter: uint32;\n    packets_discarded: uint64;\n    packets_retransmitted: uint64;\n    packets_repaired: uint64;\n    nack_count: uint64;\n    nack_packet_count: uint64;\n    pli_count: uint64;\n    fir_count: uint64;\n    rid: string;\n    rtx_ssrc: uint32 = null;\n    rtx_packets_discarded: uint64;\n    round_trip_time: float;\n    score: uint8;\n}\n\ntable RecvStats {\n    base: Stats (required);\n    packet_count: uint64;\n    byte_count: uint64;\n    bitrate: uint32;\n    bitrate_by_layer: [BitrateByLayer] (required);\n}\n\ntable SendStats {\n    base: Stats (required);\n    packet_count: uint64;\n    byte_count: uint64;\n    bitrate: uint32;\n}\n\n"
  },
  {
    "path": "worker/fbs/rtxStream.fbs",
    "content": "namespace FBS.RtxStream;\n\ntable Params {\n    ssrc: uint32;\n    payload_type: uint8;\n    mime_type: string (required);\n    clock_rate: uint32;\n    rrid: string;\n    cname: string (required);\n}\n\n// NOTE: Naming this Dump collides in TS generation for Producer.DumpResponse.\ntable RtxDump {\n    params: Params (required);\n}\n\n"
  },
  {
    "path": "worker/fbs/sctpAssociation.fbs",
    "content": "namespace FBS.SctpAssociation;\n\nenum SctpState: uint8 {\n    NEW = 0,\n    CONNECTING,\n    CONNECTED,\n    FAILED,\n    CLOSED,\n}\n\n"
  },
  {
    "path": "worker/fbs/sctpParameters.fbs",
    "content": "namespace FBS.SctpParameters;\n\ntable NumSctpStreams {\n    os: uint16 = 1024;\n    mis: uint16 = 1024;\n}\n\ntable SctpParameters {\n    // Port is always 5000.\n    port: uint16 = 5000;\n    os: uint16;\n    mis: uint16;\n    max_message_size: uint32;\n    send_buffer_size: uint32;\n    sctp_buffered_amount: uint32;\n    is_data_channel: bool;\n}\n\ntable SctpStreamParameters {\n    stream_id: uint16;\n    ordered: bool = null;\n    max_packet_life_time: uint16 = null;\n    max_retransmits: uint16 = null;\n}\n\n"
  },
  {
    "path": "worker/fbs/srtpParameters.fbs",
    "content": "namespace FBS.SrtpParameters;\n\nenum SrtpCryptoSuite: uint8 {\n    AEAD_AES_256_GCM,\n    AEAD_AES_128_GCM,\n    AES_CM_128_HMAC_SHA1_80,\n    AES_CM_128_HMAC_SHA1_32,\n}\n\ntable SrtpParameters {\n    crypto_suite: FBS.SrtpParameters.SrtpCryptoSuite;\n    key_base64: string (required);\n}\n\n"
  },
  {
    "path": "worker/fbs/transport.fbs",
    "content": "include \"common.fbs\";\ninclude \"consumer.fbs\";\ninclude \"dataProducer.fbs\";\ninclude \"rtpParameters.fbs\";\ninclude \"sctpAssociation.fbs\";\ninclude \"sctpParameters.fbs\";\ninclude \"srtpParameters.fbs\";\n\nnamespace FBS.Transport;\n\nenum Protocol: uint8 {\n    UDP = 1,\n    TCP\n}\n\ntable PortRange {\n    min: uint16 = 0;\n    max: uint16 = 0;\n}\n\ntable SocketFlags {\n    ipv6_only: bool = false;\n    udp_reuse_port: bool = false;\n}\n\ntable ListenInfo {\n    protocol: Protocol = UDP;\n    ip: string (required);\n    announced_address: string;\n    expose_internal_ip: bool;\n    port: uint16 = 0;\n    port_range: PortRange (required);\n    flags: SocketFlags (required);\n    send_buffer_size: uint32 = 0;\n    recv_buffer_size: uint32 = 0;\n}\n\ntable RestartIceResponse {\n    username_fragment: string (required);\n    password: string (required);\n    ice_lite: bool;\n}\n\ntable ProduceRequest {\n    producer_id: string (required);\n    kind: FBS.RtpParameters.MediaKind;\n    rtp_parameters: FBS.RtpParameters.RtpParameters (required);\n    rtp_mapping: FBS.RtpParameters.RtpMapping (required);\n    paused: bool = false;\n    key_frame_request_delay: uint32;\n    enable_mediasoup_packet_id_header_extension: bool = false;\n}\n\ntable ProduceResponse {\n    type: FBS.RtpParameters.Type;\n}\n\ntable ConsumeRequest {\n    consumer_id: string (required);\n    producer_id: string (required);\n    kind: FBS.RtpParameters.MediaKind;\n    rtp_parameters: FBS.RtpParameters.RtpParameters (required);\n    type: FBS.RtpParameters.Type;\n    consumable_rtp_encodings: [FBS.RtpParameters.RtpEncodingParameters] (required);\n    paused: bool = false;\n    preferred_layers: FBS.Consumer.ConsumerLayers;\n    ignore_dtx: bool = false;\n}\n\ntable ConsumeResponse {\n    paused: bool;\n    producer_paused: bool;\n    score: FBS.Consumer.ConsumerScore (required);\n    preferred_layers: FBS.Consumer.ConsumerLayers;\n}\n\ntable ProduceDataRequest {\n    data_producer_id: string (required);\n    type: FBS.DataProducer.Type;\n    sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters;\n    label: string;\n    protocol: string;\n    paused: bool = false;\n}\n\ntable ConsumeDataRequest {\n    data_consumer_id: string (required);\n    data_producer_id: string (required);\n    type: FBS.DataProducer.Type;\n    sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters;\n    label: string;\n    protocol: string;\n    paused: bool = false;\n    subchannels: [uint16];\n}\n\ntable Tuple {\n    local_address: string (required);\n    local_port: uint16;\n    remote_ip: string;\n    remote_port: uint16;\n    protocol: Protocol = UDP;\n}\n\ntable RtpListener {\n    ssrc_table: [FBS.Common.Uint32String] (required);\n    mid_table: [FBS.Common.StringString] (required);\n    rid_table: [FBS.Common.StringString] (required);\n}\n\ntable SctpListener {\n    stream_id_table: [FBS.Common.Uint16String] (required);\n}\n\ntable RecvRtpHeaderExtensions {\n    mid: uint8 = null;\n    rid: uint8 = null;\n    rrid: uint8 = null;\n    abs_send_time: uint8 = null;\n    transport_wide_cc01: uint8 = null;\n}\n\ntable Options {\n    direct: bool = false;\n\n    /// Only needed for DirectTransport. This value is handled by base Transport.\n    max_message_size: uint32 = null;\n    initial_available_outgoing_bitrate: uint32 = null;\n    enable_sctp: bool = false;\n    num_sctp_streams: FBS.SctpParameters.NumSctpStreams;\n    max_sctp_message_size: uint32;\n    sctp_send_buffer_size: uint32;\n    is_data_channel: bool = false;\n}\n\nenum TraceEventType: uint8 {\n    PROBATION = 0,\n    BWE\n}\n\ntable Dump {\n    id: string (required);\n    direct: bool = false;\n    producer_ids: [string] (required);\n    consumer_ids: [string] (required);\n    map_ssrc_consumer_id: [FBS.Common.Uint32String] (required);\n    map_rtx_ssrc_consumer_id: [FBS.Common.Uint32String] (required);\n    data_producer_ids: [string] (required);\n    data_consumer_ids: [string] (required);\n    recv_rtp_header_extensions: RecvRtpHeaderExtensions (required);\n    rtp_listener: RtpListener (required);\n    max_message_size: uint32;\n    sctp_parameters: FBS.SctpParameters.SctpParameters;\n    sctp_state: FBS.SctpAssociation.SctpState = null;\n    sctp_listener: SctpListener;\n    trace_event_types: [TraceEventType] (required);\n}\n\ntable Stats {\n    transport_id: string (required);\n    timestamp: uint64;\n    sctp_state: FBS.SctpAssociation.SctpState = null;\n    bytes_received: uint64;\n    recv_bitrate: uint32;\n    bytes_sent: uint64;\n    send_bitrate: uint32;\n    rtp_bytes_received: uint64;\n    rtp_recv_bitrate: uint32;\n    rtp_bytes_sent: uint64;\n    rtp_send_bitrate: uint32;\n    rtx_bytes_received: uint64;\n    rtx_recv_bitrate: uint32;\n    rtx_bytes_sent: uint64;\n    rtx_send_bitrate: uint32;\n    probation_bytes_sent: uint64;\n    probation_send_bitrate: uint32;\n    available_outgoing_bitrate: uint32 = null;\n    available_incoming_bitrate: uint32 = null;\n    max_incoming_bitrate: uint32 = null;\n    max_outgoing_bitrate: uint32 = null;\n    min_outgoing_bitrate: uint32 = null;\n    rtp_packet_loss_received: float64 = null;\n    rtp_packet_loss_sent: float64 = null;\n}\n\ntable SetMaxIncomingBitrateRequest {\n    max_incoming_bitrate: uint32;\n}\n\ntable SetMaxOutgoingBitrateRequest {\n    max_outgoing_bitrate: uint32;\n}\n\ntable SetMinOutgoingBitrateRequest {\n    min_outgoing_bitrate: uint32;\n}\n\ntable EnableTraceEventRequest {\n    events: [TraceEventType] (required);\n}\n\ntable CloseProducerRequest {\n    producer_id: string (required);\n}\n\ntable CloseConsumerRequest {\n    consumer_id: string (required);\n}\n\ntable CloseDataProducerRequest {\n    data_producer_id: string (required);\n}\n\ntable CloseDataConsumerRequest {\n    data_consumer_id: string (required);\n}\n\n// Notifications to Worker.\n\ntable SendRtcpNotification {\n    data: [uint8] (required);\n}\n\n// Notifications from Worker.\n\ntable SctpStateChangeNotification {\n    sctp_state: FBS.SctpAssociation.SctpState;\n}\n\nunion TraceInfo {\n    BweTraceInfo,\n}\n\nenum BweType: uint8 {\n    TRANSPORT_CC = 0,\n    REMB\n}\n\ntable BweTraceInfo {\n    bwe_type: BweType;\n    desired_bitrate: uint32;\n    effective_desired_bitrate: uint32;\n    min_bitrate: uint32;\n    max_bitrate: uint32;\n    start_bitrate: uint32;\n    max_padding_bitrate: uint32;\n    available_bitrate: uint32;\n}\n\ntable TraceNotification {\n    type: TraceEventType;\n    timestamp: uint64;\n    direction: FBS.Common.TraceDirection;\n    info: TraceInfo;\n}\n\n"
  },
  {
    "path": "worker/fbs/webRtcServer.fbs",
    "content": "include \"transport.fbs\";\n\nnamespace FBS.WebRtcServer;\n\ntable IpPort {\n    ip: string (required);\n    port: uint16;\n}\n\ntable IceUserNameFragment {\n    local_ice_username_fragment: string (required);\n    web_rtc_transport_id: string (required);\n}\n\ntable TupleHash {\n    tuple_hash: uint64;\n    web_rtc_transport_id: string (required);\n}\n\ntable DumpResponse {\n    id: string (required);\n    udp_sockets: [IpPort] (required);\n    tcp_servers: [IpPort] (required);\n    web_rtc_transport_ids: [string] (required);\n    local_ice_username_fragments: [IceUserNameFragment] (required);\n    tuple_hashes: [TupleHash] (required);\n}\n\n"
  },
  {
    "path": "worker/fbs/webRtcTransport.fbs",
    "content": "include \"transport.fbs\";\ninclude \"sctpParameters.fbs\";\n\nnamespace FBS.WebRtcTransport;\n\ntable ListenIndividual {\n    listen_infos: [FBS.Transport.ListenInfo] (required);\n}\n\ntable ListenServer {\n    web_rtc_server_id: string (required);\n}\n\nunion Listen {\n    ListenIndividual,\n    ListenServer,\n}\n\ntable WebRtcTransportOptions {\n    base: FBS.Transport.Options (required);\n    listen: Listen (required);\n    enable_udp: bool = true;\n    enable_tcp: bool = true;\n    prefer_udp: bool = false;\n    prefer_tcp: bool = false;\n    ice_consent_timeout: uint8 = 30;\n}\n\nenum FingerprintAlgorithm: uint8 {\n    SHA1,\n    SHA224,\n    SHA256,\n    SHA384,\n    SHA512,\n}\n\ntable Fingerprint {\n    algorithm: FingerprintAlgorithm;\n    value: string (required);\n}\n\nenum DtlsRole: uint8 {\n    AUTO,\n    CLIENT,\n    SERVER\n}\n\nenum DtlsState: uint8 {\n    NEW,\n    CONNECTING,\n    CONNECTED,\n    FAILED,\n    CLOSED\n}\n\ntable DtlsParameters {\n    fingerprints: [Fingerprint] (required);\n    role: DtlsRole = AUTO;\n}\n\ntable IceParameters {\n    username_fragment: string (required);\n    password: string (required);\n    ice_lite: bool = true;\n}\n\nenum IceCandidateType: uint8 {\n    HOST\n}\n\nenum IceCandidateTcpType: uint8 {\n    PASSIVE\n}\n\nenum IceRole: uint8 {\n    CONTROLLED,\n    CONTROLLING\n}\n\nenum IceState: uint8 {\n    NEW,\n    CONNECTED,\n    COMPLETED,\n    DISCONNECTED,\n}\n\ntable IceCandidate {\n    foundation: string (required);\n    priority: uint32;\n    address: string (required);\n    protocol: FBS.Transport.Protocol = UDP;\n    port: uint16;\n    type: IceCandidateType;\n    tcp_type: IceCandidateTcpType = null;\n}\n\ntable ConnectRequest {\n    dtls_parameters: DtlsParameters (required);\n}\n\ntable ConnectResponse {\n    dtls_local_role: DtlsRole;\n}\n\ntable DumpResponse {\n    base: FBS.Transport.Dump (required);\n    ice_role: IceRole;\n    ice_parameters: IceParameters (required);\n    ice_candidates: [IceCandidate] (required);\n    ice_state: IceState;\n    ice_selected_tuple: FBS.Transport.Tuple;\n    dtls_parameters: DtlsParameters (required);\n    dtls_state: DtlsState;\n}\n\ntable GetStatsResponse {\n    base: FBS.Transport.Stats (required);\n    ice_role: IceRole;\n    ice_state: IceState;\n    ice_selected_tuple: FBS.Transport.Tuple;\n    dtls_state: DtlsState;\n}\n\n// Notifications from Worker.\n\ntable IceSelectedTupleChangeNotification {\n    tuple: FBS.Transport.Tuple (required);\n}\n\ntable IceStateChangeNotification {\n    ice_state: IceState;\n}\n\ntable DtlsStateChangeNotification {\n    dtls_state: DtlsState;\n    remote_cert: string;\n}\n\n"
  },
  {
    "path": "worker/fbs/worker.fbs",
    "content": "include \"liburing.fbs\";\ninclude \"transport.fbs\";\n\nnamespace FBS.Worker;\n\ntable ChannelMessageHandlers {\n    channel_request_handlers: [string] (required);\n    channel_notification_handlers: [string] (required);\n}\n\ntable DumpResponse {\n    pid: uint32;\n    web_rtc_server_ids: [string] (required);\n    router_ids: [string] (required);\n    channel_message_handlers: ChannelMessageHandlers (required);\n    liburing: FBS.LibUring.Dump;\n}\n\ntable ResourceUsageResponse {\n    ru_utime: uint64;\n    ru_stime: uint64;\n    ru_maxrss: uint64;\n    ru_ixrss: uint64;\n    ru_idrss: uint64;\n    ru_isrss: uint64;\n    ru_minflt: uint64;\n    ru_majflt: uint64;\n    ru_nswap: uint64;\n    ru_inblock: uint64;\n    ru_oublock: uint64;\n    ru_msgsnd: uint64;\n    ru_msgrcv: uint64;\n    ru_nsignals: uint64;\n    ru_nvcsw: uint64;\n    ru_nivcsw: uint64;\n}\n\ntable UpdateSettingsRequest {\n    log_level: string;\n    log_tags: [string];\n}\n\ntable CreateWebRtcServerRequest {\n    web_rtc_server_id: string (required);\n    listen_infos: [FBS.Transport.ListenInfo];\n}\n\ntable CloseWebRtcServerRequest {\n    web_rtc_server_id: string (required);\n}\n\ntable CreateRouterRequest {\n    router_id: string (required);\n}\n\ntable CloseRouterRequest {\n    router_id: string (required);\n}\n"
  },
  {
    "path": "worker/fuzzer/include/FuzzerUtils.hpp",
    "content": "#ifndef MS_FUZZER_UTILS_HPP\n#define MS_FUZZER_UTILS_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerUtils\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerUtils\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/FuzzerDtlsTransport.hpp",
    "content": "#ifndef MS_FUZZER_RTC_DTLS_TRANSPORT_HPP\n#define MS_FUZZER_RTC_DTLS_TRANSPORT_HPP\n\n#include \"common.hpp\"\n#include \"RTC/DtlsTransport.hpp\"\n\nnamespace FuzzerRtcDtlsTransport\n{\n\tclass DtlsTransportListener : public RTC::DtlsTransport::Listener\n\t{\n\t\t/* Pure virtual methods inherited from RTC::DtlsTransport::Listener. */\n\tpublic:\n\t\tvoid OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportConnected(\n\t\t  const RTC::DtlsTransport* dtlsTransport,\n\t\t  RTC::SrtpSession::CryptoSuite srtpCryptoSuite,\n\t\t  uint8_t* srtpLocalKey,\n\t\t  size_t srtpLocalKeyLen,\n\t\t  uint8_t* srtpRemoteKey,\n\t\t  size_t srtpRemoteKeyLen,\n\t\t  std::string& remoteCert) override;\n\t\tvoid OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportSendData(\n\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override;\n\t\tvoid OnDtlsTransportApplicationDataReceived(\n\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override;\n\t};\n\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerRtcDtlsTransport\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/FuzzerRateCalculator.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RATE_CALCULATOR_HPP\n#define MS_FUZZER_RTC_RATE_CALCULATOR_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRateCalculator\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/FuzzerSeqManager.hpp",
    "content": "#ifndef MS_FUZZER_RTC_SEQ_MANAGER_HPP\n#define MS_FUZZER_RTC_SEQ_MANAGER_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcSeqManager\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/FuzzerTrendCalculator.hpp",
    "content": "#ifndef MS_FUZZER_RTC_TREND_CALCULATOR_HPP\n#define MS_FUZZER_RTC_TREND_CALCULATOR_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcTrendCalculator\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/ICE/FuzzerStunPacket.hpp",
    "content": "#ifndef MS_FUZZER_RTC_ICE_STUN_PACKET_HPP\n#define MS_FUZZER_RTC_ICE_STUN_PACKET_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcIceStunPacket\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerBye.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_BYE\n#define MS_FUZZER_RTC_RTCP_BYE\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Bye.hpp\"\n\nnamespace FuzzerRtcRtcpBye\n{\n\tvoid Fuzz(RTC::RTCP::ByePacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPs.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPs\n{\n\tvoid Fuzz(RTC::RTCP::Packet* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsAfb.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_AFB\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_AFB\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsAfb\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsAfbPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsFir.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_FIR\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_FIR\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsFir\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsFirPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsLei.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_LEI\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_LEI\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsLei.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsLei\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsLeiPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsPli.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_PLI\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_PLI\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsPli.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsPli\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsPliPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsRemb.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_REMB\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_REMB\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsRemb\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsRembPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsRpsi.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_RPSI\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_RPSI\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsRpsi.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsRpsi\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsRpsiPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsSli.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_SLI\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_SLI\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsSli.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsSli\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsSliPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsTst.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_TST\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_TST\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsTst.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsTstn\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsTstnPacket* packet);\n}\n\nnamespace FuzzerRtcRtcpFeedbackPsTstr\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsTstrPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsVbcm.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_VBCM\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_VBCM\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsVbcm.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackPsVbcm\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackPsVbcmPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtp.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtp\n{\n\tvoid Fuzz(RTC::RTCP::Packet* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpEcn.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_ECN\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_ECN\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpEcn.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpEcn\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpEcnPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpNack.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_NACK\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_NACK\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpNack\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpNackPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_SR_REQ\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_SR_REQ\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpSrReq.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpSrReq\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpSrReqPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTllei.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TLLEI\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TLLEI\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTllei.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpTllei\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpTlleiPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TMMB\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TMMB\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTmmb.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpTmmbn\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpTmmbnPacket* packet);\n}\n\nnamespace FuzzerRtcRtcpFeedbackRtpTmmbr\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpTmmbrPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTransport.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TRANSPORT\n#define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TRANSPORT\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n\nnamespace FuzzerRtcRtcpFeedbackRtpTransport\n{\n\tvoid Fuzz(RTC::RTCP::FeedbackRtpTransportPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerPacket.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_PACKET_HPP\n#define MS_FUZZER_RTC_RTCP_PACKET_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtcpPacket\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerReceiverReport.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_RECEIVER_REPORT\n#define MS_FUZZER_RTC_RTCP_RECEIVER_REPORT\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n\nnamespace FuzzerRtcRtcpReceiverReport\n{\n\tvoid Fuzz(RTC::RTCP::ReceiverReportPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerSdes.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_SDES\n#define MS_FUZZER_RTC_RTCP_SDES\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Sdes.hpp\"\n\nnamespace FuzzerRtcRtcpSdes\n{\n\tvoid Fuzz(RTC::RTCP::SdesPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerSenderReport.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_SENDER_REPORT\n#define MS_FUZZER_RTC_RTCP_SENDER_REPORT\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n\nnamespace FuzzerRtcRtcpSenderReport\n{\n\tvoid Fuzz(RTC::RTCP::SenderReportPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTCP/FuzzerXr.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTCP_XR_PACKET\n#define MS_FUZZER_RTC_RTCP_XR_PACKET\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/XR.hpp\"\n\nnamespace FuzzerRtcRtcpExtendedReport\n{\n\tvoid Fuzz(RTC::RTCP::ExtendedReportPacket* packet);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerAV1.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_AV1_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_AV1_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpCodecsAV1\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/DependencyDescriptor.hpp\"\n\nnamespace FuzzerRtcRtpCodecsDependencyDescriptor\n{\n\tclass DependencyDescriptorListener : public RTC::RTP::Codecs::DependencyDescriptor::Listener\n\t{\n\tpublic:\n\t\tvoid OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override\n\t\t{\n\t\t}\n\t};\n\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerRtcRtpCodecsDependencyDescriptor\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerH264.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_H264_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_H264_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpCodecsH264\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerOpus.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_OPUS_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_OPUS_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpCodecsOpus\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerVP8.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_VP8_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_VP8_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpCodecsVP8\n{\n\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/Codecs/FuzzerVP9.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_CODECS_VP9_HPP\n#define MS_FUZZER_RTC_RTP_CODECS_VP9_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpCodecsVP9\n{\n\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/FuzzerPacket.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_PACKET_HPP\n#define MS_FUZZER_RTC_RTP_PACKET_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtcPacket\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/FuzzerProbationGenerator.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_PROBATION_PACKET_HPP\n#define MS_FUZZER_RTC_RTP_PROBATION_PACKET_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpProbationGenerator\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/FuzzerRetransmissionBuffer.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_RETRANSMISSION_BUFFER_HPP\n#define MS_FUZZER_RTC_RTP_RETRANSMISSION_BUFFER_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcRtpRetransmissionBuffer\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n}\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/RTP/FuzzerRtpStreamSend.hpp",
    "content": "#ifndef MS_FUZZER_RTC_RTP_STREAM_SEND_HPP\n#define MS_FUZZER_RTC_RTP_STREAM_SEND_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStreamSend.hpp\"\n\nnamespace FuzzerRtcRtpStreamSend\n{\n\tclass TestRtpStreamListener : public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tpublic:\n\t\tvoid OnRtpStreamScore(\n\t\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override\n\t\t{\n\t\t}\n\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) override\n\t\t{\n\t\t}\n\t};\n\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerRtcRtpStreamSend\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/SCTP/FuzzerStateCookie.hpp",
    "content": "#ifndef MS_FUZZER_RTC_SCTP_STATE_COOKIE_HPP\n#define MS_FUZZER_RTC_SCTP_STATE_COOKIE_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcSctpStateCookie\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerRtcSctpStateCookie\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/include/RTC/SCTP/packet/FuzzerPacket.hpp",
    "content": "#ifndef MS_FUZZER_RTC_SCTP_PACKET_HPP\n#define MS_FUZZER_RTC_SCTP_PACKET_HPP\n\n#include \"common.hpp\"\n\nnamespace FuzzerRtcSctpPacket\n{\n\tvoid Fuzz(const uint8_t* data, size_t len);\n} // namespace FuzzerRtcSctpPacket\n\n#endif\n"
  },
  {
    "path": "worker/fuzzer/new-corpus/.placeholder",
    "content": ""
  },
  {
    "path": "worker/fuzzer/reports/.placeholder",
    "content": ""
  },
  {
    "path": "worker/fuzzer/reports/crash-7e7caf72377ad55d353719f28febb5238eadfc9e",
    "content": "88\u0018t"
  },
  {
    "path": "worker/fuzzer/reports/crash-91572165de5ef12fe8415b150e40457eccca0362",
    "content": "%345DEFG%8EFX"
  },
  {
    "path": "worker/fuzzer/reports/crash-ac5d03e5d918b7f714c0452a59ad9c0e1ca3e501",
    "content": "::a:"
  },
  {
    "path": "worker/fuzzer/reports/crash-b75c1208384621922270e954b4902442592c3ca9",
    "content": "dtat"
  },
  {
    "path": "worker/fuzzer/reports/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "content": ""
  },
  {
    "path": "worker/fuzzer/reports/crash-dcfd05592934ab472c98a1813256aabb9bb43bfb",
    "content": "''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''"
  },
  {
    "path": "worker/fuzzer/reports/crash-ddfab2c0dd845e8d3e8f8d27e1f4cb49d92d279a",
    "content": "::1:"
  },
  {
    "path": "worker/fuzzer/src/FuzzerUtils.cpp",
    "content": "#include \"FuzzerUtils.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memcpy()\n#include <string>\n\nnamespace\n{\n\talignas(4) thread_local uint8_t DataBuffer[65536];\n}\n\nvoid FuzzerUtils::Fuzz(const uint8_t* data, size_t len)\n{\n\t// NOTE: We need to copy given data into another buffer because we are gonna\n\t// write into it.\n\tstd::memcpy(DataBuffer, data, len);\n\n\t/* IP class. */\n\n\tstd::string ip;\n\n\tip = std::string(reinterpret_cast<const char*>(DataBuffer), INET6_ADDRSTRLEN / 2);\n\tUtils::IP::GetFamily(ip);\n\n\tip = std::string(reinterpret_cast<const char*>(DataBuffer), INET6_ADDRSTRLEN);\n\tUtils::IP::GetFamily(ip);\n\n\tip = std::string(reinterpret_cast<const char*>(DataBuffer), INET6_ADDRSTRLEN * 2);\n\tUtils::IP::GetFamily(ip);\n\n\t// Protect with try/catch since throws are legit.\n\ttry\n\t{\n\t\tauto ip = std::string(reinterpret_cast<const char*>(DataBuffer), len);\n\n\t\tUtils::IP::NormalizeIp(ip);\n\t}\n\tcatch (const MediaSoupError& error) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\t/* Byte class. */\n\n\tUtils::Byte::Get1Byte(DataBuffer, len);\n\tUtils::Byte::Get2Bytes(DataBuffer, len);\n\tUtils::Byte::Get3Bytes(DataBuffer, len);\n\tUtils::Byte::Get4Bytes(DataBuffer, len);\n\tUtils::Byte::Get8Bytes(DataBuffer, len);\n\tUtils::Byte::Set1Byte(DataBuffer, len, uint8_t{ 6u });\n\tUtils::Byte::Set2Bytes(DataBuffer, len, uint16_t{ 66u });\n\tUtils::Byte::Set3Bytes(DataBuffer, len, uint32_t{ 666u });\n\tUtils::Byte::Set4Bytes(DataBuffer, len, uint32_t{ 666u });\n\tUtils::Byte::Set8Bytes(DataBuffer, len, uint64_t{ 6666u });\n\tUtils::Byte::PadTo4Bytes(static_cast<uint8_t>(len));\n\tUtils::Byte::PadTo4Bytes(static_cast<uint16_t>(len));\n\tUtils::Byte::PadTo4Bytes(static_cast<uint32_t>(len));\n\tUtils::Byte::PadTo4Bytes(static_cast<uint64_t>(len));\n\tUtils::Byte::PadTo4Bytes(len);\n\tUtils::Byte::PadTo8Bytes(static_cast<uint8_t>(len));\n\tUtils::Byte::PadTo8Bytes(static_cast<uint16_t>(len));\n\tUtils::Byte::PadTo8Bytes(static_cast<uint32_t>(len));\n\tUtils::Byte::PadTo8Bytes(static_cast<uint64_t>(len));\n\tUtils::Byte::PadTo8Bytes(len);\n\n\t/* Bits class. */\n\n\tUtils::Bits::CountSetBits(static_cast<uint16_t>(len));\n\n\t/* Crypto class. */\n\n\tUtils::Crypto::GetRandomUInt<uint32_t>(\n\t  static_cast<uint32_t>(len), static_cast<uint32_t>(len + 1000000));\n\tUtils::Crypto::GetRandomUInt<uint64_t>(\n\t  static_cast<uint64_t>(len), static_cast<uint64_t>(len + 1000000));\n\tUtils::Crypto::GetRandomUInt<size_t>(len, len + 1000000);\n\tUtils::Crypto::GetRandomString(len);\n\tUtils::Crypto::GetCRC32(DataBuffer, len);\n\n\t/* String class. */\n\n\t// Protect with try/catch since throws are legit.\n\ttry\n\t{\n\t\tsize_t outLen;\n\n\t\tUtils::String::Base64Encode(DataBuffer, len);\n\t\tUtils::String::Base64Decode(DataBuffer, len, outLen);\n\t}\n\tcatch (const MediaSoupError& error) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\t/* Time class. */\n\n\tauto ntp = Utils::Time::TimeMs2Ntp(static_cast<uint64_t>(len));\n\n\tUtils::Time::Ntp2TimeMs(ntp);\n\tUtils::Time::TimeMsToAbsSendTime(static_cast<uint64_t>(len));\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/FuzzerDtlsTransport.cpp",
    "content": "#define MS_CLASS \"FuzzerRtcDtlsTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/FuzzerDtlsTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n\nnamespace\n{\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tthread_local mocks::MockShared shared(/*getTimeMs*/\n\t                                      []()\n\t                                      {\n\t\t                                      return 1000;\n\t                                      });\n\n\t// DtlsTransport instance. It's reset every time DTLS handshake fails or DTLS\n\t// is closed.\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tthread_local RTC::DtlsTransport* dtlsTransportSingleton{ nullptr };\n\n\t// DtlsTransport Listener instance. It's reset every time the DtlsTransport\n\t// singletonDTLS is reset.\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tthread_local FuzzerRtcDtlsTransport::DtlsTransportListener* dtlsTransportListenerSingleton{ nullptr };\n} // namespace\n\nvoid FuzzerRtcDtlsTransport::Fuzz(const uint8_t* data, size_t len)\n{\n\tif (!RTC::DtlsTransport::IsDtls(data, len))\n\t{\n\t\treturn;\n\t}\n\n\tif (!dtlsTransportSingleton)\n\t{\n\t\tMS_DEBUG_DEV(\"no DtlsTransport singleton, creating it\");\n\n\t\tdelete dtlsTransportListenerSingleton;\n\t\tdtlsTransportListenerSingleton = new DtlsTransportListener();\n\n\t\tdtlsTransportSingleton =\n\t\t  new RTC::DtlsTransport(dtlsTransportListenerSingleton, std::addressof(shared));\n\n\t\tRTC::DtlsTransport::Role localRole;\n\t\tRTC::DtlsTransport::Fingerprint dtlsRemoteFingerprint;\n\n\t\t// Local DTLS role must be 'server' or 'client'. Choose it based on\n\t\t// randomness of first given byte.\n\t\tif (data[0] / 2 == 0)\n\t\t{\n\t\t\tlocalRole = RTC::DtlsTransport::Role::SERVER;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlocalRole = RTC::DtlsTransport::Role::CLIENT;\n\t\t}\n\n\t\t// Remote DTLS fingerprint random generation.\n\t\t// NOTE: Use a random integer in range 1..5 since FingerprintAlgorithm enum\n\t\t// has 5 possible values starting with value 1.\n\t\tdtlsRemoteFingerprint.algorithm = static_cast<RTC::DtlsTransport::FingerprintAlgorithm>(\n\t\t  Utils::Crypto::GetRandomUInt<uint16_t>(1u, 5u));\n\n\t\tdtlsRemoteFingerprint.value =\n\t\t  Utils::Crypto::GetRandomString(Utils::Crypto::GetRandomUInt<uint16_t>(3u, 20u));\n\n\t\tdtlsTransportSingleton->Run(localRole);\n\t\tdtlsTransportSingleton->SetRemoteFingerprint(dtlsRemoteFingerprint);\n\t}\n\n\tdtlsTransportSingleton->ProcessDtlsData(data, len);\n\n\t// DTLS may have failed or closed after ProcessDtlsData(). If so, unset it.\n\tif (\n\t  dtlsTransportSingleton->GetState() == RTC::DtlsTransport::DtlsState::FAILED ||\n\t  dtlsTransportSingleton->GetState() == RTC::DtlsTransport::DtlsState::CLOSED)\n\t{\n\t\tMS_DEBUG_DEV(\"DtlsTransport singleton state is 'failed' or 'closed', unsetting it\");\n\n\t\tdelete dtlsTransportSingleton;\n\t\tdtlsTransportSingleton = nullptr;\n\t}\n\telse\n\t{\n\t\tdtlsTransportSingleton->SendApplicationData(data, len);\n\t}\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportConnecting(\n  const RTC::DtlsTransport* /*dtlsTransport*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton connecting\");\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportConnected(\n  const RTC::DtlsTransport* /*dtlsTransport*/,\n  RTC::SrtpSession::CryptoSuite /*srtpCryptoSuite*/,\n  uint8_t* /*srtpLocalKey*/,\n  size_t /*srtpLocalKeyLen*/,\n  uint8_t* /*srtpRemoteKey*/,\n  size_t /*srtpRemoteKeyLen*/,\n  std::string& /*remoteCert*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton connected\");\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportFailed(\n  const RTC::DtlsTransport* /*dtlsTransport*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton failed\");\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportClosed(\n  const RTC::DtlsTransport* /*dtlsTransport*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton closed\");\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportSendData(\n  const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* /*data*/, size_t /*len*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton wants to send data\");\n}\n\nvoid FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportApplicationDataReceived(\n  const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* /*data*/, size_t /*len*/)\n{\n\tMS_DEBUG_DEV(\"DtlsTransport singleton received application data\");\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/FuzzerRateCalculator.cpp",
    "content": "#include \"RTC/FuzzerRateCalculator.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n\nnamespace\n{\n\t// NOLINTBEGIN(readability-identifier-naming)\n\tRTC::RateCalculator rateCalculator;\n\tuint64_t nowMs;\n\t// NOLINTEND(readability-identifier-naming)\n\n\tint init()\n\t{\n\t\tnowMs = DepLibUV::GetTimeMs();\n\n\t\treturn 0;\n\t}\n} // namespace\n\nvoid FuzzerRtcRateCalculator::Fuzz(const uint8_t* data, size_t len)\n{\n\t// Trick to initialize our stuff just once.\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tstatic thread_local const int unused = init();\n\n\t// Avoid [-Wunused-variable].\n\t(void)unused;\n\n\t// We need at least 2 bytes of |data|.\n\tif (len < 2)\n\t{\n\t\treturn;\n\t}\n\n\tauto size = Utils::Crypto::GetRandomUInt<size_t>(0u, static_cast<uint32_t>(RTC::Consts::MtuSize));\n\n\tnowMs += Utils::Crypto::GetRandomUInt<uint64_t>(0u, 1000u);\n\n\trateCalculator.Update(size, nowMs);\n\n\t// Only get rate from time to time.\n\tif (Utils::Byte::Get2Bytes(data, 0) % 100 == 0)\n\t{\n\t\trateCalculator.GetRate(nowMs);\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/FuzzerSeqManager.cpp",
    "content": "#include \"RTC/FuzzerSeqManager.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <iostream>\n\nvoid FuzzerRtcSeqManager::Fuzz(const uint8_t* data, size_t len)\n{\n\tif (len < 10)\n\t{\n\t\treturn;\n\t}\n\n\tRTC::SeqManager<uint16_t> seqManager;\n\n\tuint16_t output;\n\n\tfor (size_t count = 0; count < 7; count++)\n\t{\n\t\tseqManager.Input(Utils::Byte::Get2Bytes(data, count), output);\n\t\tseqManager.Drop(Utils::Byte::Get2Bytes(data, count + 2));\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/FuzzerTrendCalculator.cpp",
    "content": "#include \"RTC/FuzzerTrendCalculator.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/TrendCalculator.hpp\"\n\nvoid FuzzerRtcTrendCalculator::Fuzz(const uint8_t* data, size_t len)\n{\n\tRTC::TrendCalculator trend;\n\tauto nowMs = DepLibUV::GetTimeMs();\n\tsize_t offset{ 0u };\n\n\twhile (len >= 4u)\n\t{\n\t\tconst auto value = Utils::Byte::Get4Bytes(data, offset);\n\n\t\ttrend.Update(value, nowMs);\n\t\ttrend.GetValue();\n\t\ttrend.Update(value, nowMs - 1000u);\n\t\ttrend.GetValue();\n\n\t\tlen -= 4u;\n\t\toffset += 4;\n\t\tnowMs += 500u;\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/ICE/FuzzerStunPacket.cpp",
    "content": "#include \"RTC/ICE/FuzzerStunPacket.hpp\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include <string_view>\n\nnamespace\n{\n\tconstexpr size_t ResponseFactoryBufferLength{ 65536 };\n\talignas(4) thread_local uint8_t ResponseFactoryBuffer[ResponseFactoryBufferLength];\n\tconstexpr size_t SerializeBufferLength{ 65536 };\n\talignas(4) thread_local uint8_t SerializeBuffer[SerializeBufferLength];\n\tconstexpr size_t CloneBufferLength{ 65536 };\n\talignas(4) thread_local uint8_t CloneBuffer[CloneBufferLength];\n} // namespace\n\nvoid FuzzerRtcIceStunPacket::Fuzz(const uint8_t* data, size_t len)\n{\n\tif (!RTC::ICE::StunPacket::IsStun(data, len))\n\t{\n\t\treturn;\n\t}\n\n\tauto* packet = RTC::ICE::StunPacket::Parse(data, len);\n\n\tif (!packet)\n\t{\n\t\treturn;\n\t}\n\n\tstruct sockaddr_storage xorMappedAddressStorage{};\n\tstd::string_view errorReasonPhrase;\n\n\t// packet->Dump();\n\tpacket->GetClass();\n\tpacket->GetMethod();\n\tpacket->GetTransactionId();\n\tpacket->HasAttribute(RTC::ICE::StunPacket::AttributeType::USERNAME);\n\tpacket->GetUsername();\n\tpacket->GetPriority();\n\tpacket->GetIceControlling();\n\tpacket->GetIceControlled();\n\tpacket->GetNomination();\n\tpacket->GetSoftware();\n\tpacket->GetXorMappedAddress(std::addressof(xorMappedAddressStorage));\n\tpacket->GetErrorCode(errorReasonPhrase);\n\tpacket->CheckAuthentication(\"1234:1234\", \"aksjd\");\n\tpacket->CheckAuthentication(\"foo\");\n\n\ttry\n\t{\n\t\tpacket->AddUsername(\"foo\");\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddPriority(123456);\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddIceControlling(98989232u);\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddIceControlled(87823823u);\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddUseCandidate();\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddNomination(7623547u);\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddXorMappedAddress(\n\t\t  reinterpret_cast<struct sockaddr*>(std::addressof(xorMappedAddressStorage)));\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->AddErrorCode(555, \"ERROR å∫∂æ®€å∂ƒ∫\");\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->Protect(\"askjhdakjsd\");\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\ttry\n\t{\n\t\tpacket->Protect();\n\t}\n\tcatch (...) // NOLINT(bugprone-empty-catch)\n\t{\n\t}\n\n\tif (packet->GetClass() == RTC::ICE::StunPacket::Class::REQUEST)\n\t{\n\t\tauto* successResponse =\n\t\t  packet->CreateSuccessResponse(ResponseFactoryBuffer, sizeof(ResponseFactoryBuffer));\n\t\tauto* errorResponse = packet->CreateErrorResponse(\n\t\t  ResponseFactoryBuffer, sizeof(ResponseFactoryBuffer), 444, \"ERROR aljsh œœ∫∂å∫∂ zhx   å∫∂å∫∂ !!!\");\n\n\t\tdelete successResponse;\n\t\tdelete errorResponse;\n\t}\n\n\tpacket->Serialize(SerializeBuffer, sizeof(SerializeBuffer));\n\n\tconst auto* clonedPacket = packet->Clone(CloneBuffer, sizeof(CloneBuffer));\n\n\tdelete packet;\n\tdelete clonedPacket;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerBye.cpp",
    "content": "#include \"RTC/RTCP/FuzzerBye.hpp\"\n\nvoid FuzzerRtcRtcpBye::Fuzz(RTC::RTCP::ByePacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\tpacket->AddSsrc(1111);\n\tpacket->SetReason(\"because!\");\n\tpacket->GetReason();\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPs.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPs.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsAfb.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsFir.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsLei.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsPli.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsRemb.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsRpsi.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsSli.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsTst.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPsVbcm.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPs::Fuzz(RTC::RTCP::Packet* packet)\n{\n\tauto* fbps = dynamic_cast<RTC::RTCP::FeedbackPsPacket*>(packet);\n\n\tfbps->GetMessageType();\n\tfbps->GetSenderSsrc();\n\tfbps->SetSenderSsrc(1111);\n\tfbps->GetMediaSsrc();\n\tfbps->SetMediaSsrc(2222);\n\n\tswitch (fbps->GetMessageType())\n\t{\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t{\n\t\t\tauto* pli = dynamic_cast<RTC::RTCP::FeedbackPsPliPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsPli::Fuzz(pli);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::SLI:\n\t\t{\n\t\t\tauto* sli = dynamic_cast<RTC::RTCP::FeedbackPsSliPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsSli::Fuzz(sli);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::RPSI:\n\t\t{\n\t\t\tauto* rpsi = dynamic_cast<RTC::RTCP::FeedbackPsRpsiPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsRpsi::Fuzz(rpsi);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t{\n\t\t\tauto* fir = dynamic_cast<RTC::RTCP::FeedbackPsFirPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsFir::Fuzz(fir);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::TSTR:\n\t\t{\n\t\t\tauto* tstr = dynamic_cast<RTC::RTCP::FeedbackPsTstrPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsTstr::Fuzz(tstr);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::TSTN:\n\t\t{\n\t\t\tauto* tstn = dynamic_cast<RTC::RTCP::FeedbackPsTstnPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsTstn::Fuzz(tstn);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::VBCM:\n\t\t{\n\t\t\tauto* vbcm = dynamic_cast<RTC::RTCP::FeedbackPsVbcmPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsVbcm::Fuzz(vbcm);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::PSLEI:\n\t\t{\n\t\t\tauto* lei = dynamic_cast<RTC::RTCP::FeedbackPsLeiPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsLei::Fuzz(lei);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackPs::MessageType::AFB:\n\t\t{\n\t\t\tauto* afb = dynamic_cast<RTC::RTCP::FeedbackPsAfbPacket*>(fbps);\n\n\t\t\tFuzzerRtcRtcpFeedbackPsAfb::Fuzz(afb);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t{\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsAfb.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsAfb.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsAfb::Fuzz(RTC::RTCP::FeedbackPsAfbPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetSize();\n\tpacket->GetApplication();\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsFir.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsFir.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsFir::Fuzz(RTC::RTCP::FeedbackPsFirPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->GetSequenceNumber();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsLei.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsLei.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsLei::Fuzz(RTC::RTCP::FeedbackPsLeiPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsPli.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsPli.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsPli::Fuzz(RTC::RTCP::FeedbackPsPliPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRemb.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsRemb.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsRemb::Fuzz(RTC::RTCP::FeedbackPsRembPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\tpacket->IsCorrect();\n\tpacket->SetBitrate(1111);\n\tpacket->SetSsrcs({ 2222, 3333, 4444 });\n\tpacket->GetBitrate();\n\tpacket->GetSsrcs();\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRpsi.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsRpsi.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsRpsi::Fuzz(RTC::RTCP::FeedbackPsRpsiPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->IsCorrect();\n\t\titem->GetPayloadType();\n\t\titem->GetBitString();\n\t\titem->GetLength();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsSli.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsSli.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsSli::Fuzz(RTC::RTCP::FeedbackPsSliPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetFirst();\n\t\titem->SetFirst(1111);\n\t\titem->GetNumber();\n\t\titem->SetNumber(2222);\n\t\titem->GetPictureId();\n\t\titem->SetPictureId(255);\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsTst.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsTst.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsTstn::Fuzz(RTC::RTCP::FeedbackPsTstnPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->GetSequenceNumber();\n\t\titem->GetIndex();\n\t}\n}\n\nvoid FuzzerRtcRtcpFeedbackPsTstr::Fuzz(RTC::RTCP::FeedbackPsTstrPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->GetSequenceNumber();\n\t\titem->GetIndex();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsVbcm.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackPsVbcm.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackPsVbcm::Fuzz(RTC::RTCP::FeedbackPsVbcmPacket* packet)\n{\n\t// Triggers a crash in fuzzer.\n\t// TODO: Verify that there is buffer enough for the announce length.\n\t// packet->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\t// Triggers a crash in fuzzer.\n\t\t// item->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->GetSequenceNumber();\n\t\titem->GetPayloadType();\n\t\titem->GetLength();\n\t\titem->GetValue();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtp.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtp.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpEcn.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpTllei.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtpTransport.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtp::Fuzz(RTC::RTCP::Packet* packet)\n{\n\tauto* fbrtp = dynamic_cast<RTC::RTCP::FeedbackRtpPacket*>(packet);\n\n\tfbrtp->GetMessageType();\n\tfbrtp->GetSenderSsrc();\n\tfbrtp->SetSenderSsrc(1111);\n\tfbrtp->GetMediaSsrc();\n\tfbrtp->SetMediaSsrc(2222);\n\n\tswitch (fbrtp->GetMessageType())\n\t{\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::NACK:\n\t\t{\n\t\t\tauto* nack = dynamic_cast<RTC::RTCP::FeedbackRtpNackPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpNack::Fuzz(nack);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::TMMBR:\n\t\t{\n\t\t\tauto* tmmbr = dynamic_cast<RTC::RTCP::FeedbackRtpTmmbrPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpTmmbr::Fuzz(tmmbr);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::TMMBN:\n\t\t{\n\t\t\tauto* tmmbn = dynamic_cast<RTC::RTCP::FeedbackRtpTmmbnPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpTmmbn::Fuzz(tmmbn);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::SR_REQ:\n\t\t{\n\t\t\tauto* srReq = dynamic_cast<RTC::RTCP::FeedbackRtpSrReqPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpSrReq::Fuzz(srReq);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::TLLEI:\n\t\t{\n\t\t\tauto* tllei = dynamic_cast<RTC::RTCP::FeedbackRtpTlleiPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpTllei::Fuzz(tllei);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::ECN:\n\t\t{\n\t\t\tauto* ecn = dynamic_cast<RTC::RTCP::FeedbackRtpEcnPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpEcn::Fuzz(ecn);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase RTC::RTCP::FeedbackRtp::MessageType::TCC:\n\t\t{\n\t\t\tauto* feedback = dynamic_cast<RTC::RTCP::FeedbackRtpTransportPacket*>(fbrtp);\n\n\t\t\tFuzzerRtcRtcpFeedbackRtpTransport::Fuzz(feedback);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t{\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpEcn.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpEcn.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpEcn::Fuzz(RTC::RTCP::FeedbackRtpEcnPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSequenceNumber();\n\t\titem->GetEct0Counter();\n\t\titem->GetEct1Counter();\n\t\titem->GetEcnCeCounter();\n\t\titem->GetNotEctCounter();\n\t\titem->GetLostPackets();\n\t\titem->GetDuplicatedPackets();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpNack.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpNack.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpNack::Fuzz(RTC::RTCP::FeedbackRtpNackPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetPacketId();\n\t\titem->GetLostPacketBitmask();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpSrReq.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpSrReq::Fuzz(RTC::RTCP::FeedbackRtpSrReqPacket* packet)\n{\n\t// TODO\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTllei.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpTllei.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpTllei::Fuzz(RTC::RTCP::FeedbackRtpTlleiPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetPacketId();\n\t\titem->GetLostPacketBitmask();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTmmb.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpTmmbn::Fuzz(RTC::RTCP::FeedbackRtpTmmbnPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->SetSsrc(1111);\n\t\titem->GetBitrate();\n\t\titem->SetBitrate(2222);\n\t\titem->GetOverhead();\n\t\titem->SetOverhead(3333);\n\t}\n}\n\nvoid FuzzerRtcRtcpFeedbackRtpTmmbr::Fuzz(RTC::RTCP::FeedbackRtpTmmbrPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddItem(Item* item);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& item = (*it);\n\n\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\titem->GetSize();\n\t\titem->GetSsrc();\n\t\titem->SetSsrc(1111);\n\t\titem->GetBitrate();\n\t\titem->SetBitrate(2222);\n\t\titem->GetOverhead();\n\t\titem->SetOverhead(3333);\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTransport.cpp",
    "content": "#include \"RTC/RTCP/FuzzerFeedbackRtpTransport.hpp\"\n\nvoid FuzzerRtcRtcpFeedbackRtpTransport::Fuzz(RTC::RTCP::FeedbackRtpTransportPacket* packet)\n{\n\tpacket->GetCount();\n\tpacket->GetSize();\n\tpacket->IsFull();\n\tpacket->IsSerializable();\n\tpacket->IsCorrect();\n\tpacket->GetBaseSequenceNumber();\n\tpacket->GetPacketStatusCount();\n\tpacket->GetReferenceTime();\n\tpacket->GetReferenceTimestamp();\n\tpacket->GetFeedbackPacketCount();\n\tpacket->GetLatestSequenceNumber();\n\tpacket->GetLatestTimestamp();\n\tpacket->GetPacketResults();\n\tpacket->GetPacketFractionLost();\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerPacket.cpp",
    "content": "#include \"RTC/RTCP/FuzzerPacket.hpp\"\n#include \"RTC/RTCP/FuzzerBye.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackPs.hpp\"\n#include \"RTC/RTCP/FuzzerFeedbackRtp.hpp\"\n#include \"RTC/RTCP/FuzzerReceiverReport.hpp\"\n#include \"RTC/RTCP/FuzzerSdes.hpp\"\n#include \"RTC/RTCP/FuzzerSenderReport.hpp\"\n#include \"RTC/RTCP/FuzzerXr.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace\n{\n\talignas(4) thread_local uint8_t DataBuffer[65536];\n} // namespace\n\nvoid FuzzerRtcRtcpPacket::Fuzz(const uint8_t* data, size_t len)\n{\n\tif (!RTC::RTCP::Packet::IsRtcp(data, len))\n\t{\n\t\treturn;\n\t}\n\n\t// NOTE: We need to copy given data into another buffer because we are gonna\n\t// write into it.\n\tstd::memcpy(DataBuffer, data, len);\n\n\tRTC::RTCP::Packet* packet = RTC::RTCP::Packet::Parse(DataBuffer, len);\n\n\tif (!packet)\n\t{\n\t\treturn;\n\t}\n\n\twhile (packet != nullptr)\n\t{\n\t\tauto* previousPacket = packet;\n\n\t\tswitch (RTC::RTCP::Type(packet->GetType()))\n\t\t{\n\t\t\tcase RTC::RTCP::Type::SR:\n\t\t\t{\n\t\t\t\tauto* sr = dynamic_cast<RTC::RTCP::SenderReportPacket*>(packet);\n\n\t\t\t\tFuzzerRtcRtcpSenderReport::Fuzz(sr);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::RR:\n\t\t\t{\n\t\t\t\tauto* rr = dynamic_cast<RTC::RTCP::ReceiverReportPacket*>(packet);\n\n\t\t\t\tFuzzerRtcRtcpReceiverReport::Fuzz(rr);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::SDES:\n\t\t\t{\n\t\t\t\tauto* sdes = dynamic_cast<RTC::RTCP::SdesPacket*>(packet);\n\n\t\t\t\tFuzzerRtcRtcpSdes::Fuzz(sdes);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::BYE:\n\t\t\t{\n\t\t\t\tauto* bye = dynamic_cast<RTC::RTCP::ByePacket*>(packet);\n\n\t\t\t\tFuzzerRtcRtcpBye::Fuzz(bye);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::RTPFB:\n\t\t\t{\n\t\t\t\tFuzzerRtcRtcpFeedbackRtp::Fuzz(packet);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::PSFB:\n\t\t\t{\n\t\t\t\tFuzzerRtcRtcpFeedbackPs::Fuzz(packet);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::XR:\n\t\t\t{\n\t\t\t\tauto* xr = dynamic_cast<RTC::RTCP::ExtendedReportPacket*>(packet);\n\n\t\t\t\tFuzzerRtcRtcpExtendedReport::Fuzz(xr);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t}\n\t\t}\n\n\t\tpacket = packet->GetNext();\n\n\t\tdelete previousPacket;\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerReceiverReport.cpp",
    "content": "#include \"RTC/RTCP/FuzzerReceiverReport.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n\nvoid FuzzerRtcRtcpReceiverReport::Fuzz(RTC::RTCP::ReceiverReportPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\tpacket->GetSsrc();\n\tpacket->SetSsrc(1111);\n\n\t// TODO.\n\t// AddReport(ReceiverReport* report);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& report = (*it);\n\n\t\treport->Serialize(RTC::RTCP::SerializationBuffer);\n\t\treport->GetSize();\n\t\treport->GetSsrc();\n\t\treport->SetSsrc(1111);\n\t\treport->GetFractionLost();\n\t\treport->SetFractionLost(64);\n\t\treport->GetTotalLost();\n\t\treport->SetTotalLost(2222);\n\t\treport->GetLastSeq();\n\t\treport->SetLastSeq(3333);\n\t\treport->GetJitter();\n\t\treport->SetJitter(4444);\n\t\treport->GetLastSenderReport();\n\t\treport->SetLastSenderReport(5555);\n\t\treport->GetDelaySinceLastSenderReport();\n\t\treport->SetDelaySinceLastSenderReport(6666);\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerSdes.cpp",
    "content": "#include \"RTC/RTCP/FuzzerSdes.hpp\"\n\nvoid FuzzerRtcRtcpSdes::Fuzz(RTC::RTCP::SdesPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddChunk(SdesChunk* chunk);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& chunk = (*it);\n\n\t\tchunk->Serialize(RTC::RTCP::SerializationBuffer);\n\t\tchunk->GetSize();\n\t\tchunk->GetSsrc();\n\t\tchunk->SetSsrc(1111);\n\n\t\t// TODO\n\t\t// AddItem(SdesItem* item);\n\n\t\tfor (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2)\n\t\t{\n\t\t\tauto& item = (*it2);\n\n\t\t\titem->Serialize(RTC::RTCP::SerializationBuffer);\n\t\t\titem->GetSize();\n\t\t\titem->GetType();\n\t\t\titem->GetLength();\n\t\t\titem->GetValue();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerSenderReport.cpp",
    "content": "#include \"RTC/RTCP/FuzzerSenderReport.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n\nvoid FuzzerRtcRtcpSenderReport::Fuzz(RTC::RTCP::SenderReportPacket* packet)\n{\n\t// A well formed packet must have a single report.\n\tif (packet->GetCount() == 1)\n\t{\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\t}\n\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\t// TODO.\n\t// AddReport(SenderReport* report);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& report = (*it);\n\n\t\treport->Serialize(RTC::RTCP::SerializationBuffer);\n\t\treport->GetSize();\n\t\treport->GetSsrc();\n\t\treport->SetSsrc(1111);\n\t\treport->GetNtpSec();\n\t\treport->SetNtpSec(2222);\n\t\treport->GetNtpFrac();\n\t\treport->SetNtpFrac(3333);\n\t\treport->GetRtpTs();\n\t\treport->SetRtpTs(4444);\n\t\treport->GetPacketCount();\n\t\treport->SetPacketCount(1024);\n\t\treport->GetOctetCount();\n\t\treport->SetOctetCount(11223344);\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTCP/FuzzerXr.cpp",
    "content": "#include \"RTC/RTCP/FuzzerXr.hpp\"\n\nvoid FuzzerRtcRtcpExtendedReport::Fuzz(RTC::RTCP::ExtendedReportPacket* packet)\n{\n\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\tpacket->GetCount();\n\tpacket->GetSize();\n\n\tpacket->GetSsrc();\n\tpacket->SetSsrc(1111);\n\n\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t{\n\t\tauto& report = (*it);\n\n\t\treport->Serialize(RTC::RTCP::SerializationBuffer);\n\t\treport->GetSize();\n\t\treport->GetType();\n\t}\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerAV1.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerAV1.hpp\"\n#include \"RTC/RTP/Codecs/AV1.hpp\"\n\nclass Listener : public RTC::RTP::Codecs::DependencyDescriptor::Listener\n{\npublic:\n\tvoid OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override\n\t{\n\t}\n};\n\nvoid FuzzerRtcRtpCodecsAV1::Fuzz(const uint8_t* data, size_t len)\n{\n\tListener listener;\n\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t  templateDependencyStructure;\n\n\tauto dependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t    data, len, std::addressof(listener), templateDependencyStructure));\n\n\tauto* descriptor = RTC::RTP::Codecs::AV1::Parse(dependencyDescriptor);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerDependencyDescriptor.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp\"\n\nvoid FuzzerRtcRtpCodecsDependencyDescriptor::Fuzz(const uint8_t* data, size_t len)\n{\n\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t  templateDependencyStructure;\n\n\tDependencyDescriptorListener listener;\n\n\tauto* descriptor = RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t  data, len, std::addressof(listener), templateDependencyStructure);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerH264.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerH264.hpp\"\n#include \"RTC/RTP/Codecs/H264.hpp\"\n\nvoid FuzzerRtcRtpCodecsH264::Fuzz(const uint8_t* data, size_t len)\n{\n\tRTC::RTP::Codecs::DependencyDescriptor* dependencyDescriptor{ nullptr };\n\n\tconst RTC::RTP::Codecs::H264::PayloadDescriptor* descriptor =\n\t  RTC::RTP::Codecs::H264::Parse(data, len, dependencyDescriptor);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerOpus.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerOpus.hpp\"\n#include \"RTC/RTP/Codecs/Opus.hpp\"\n\nvoid FuzzerRtcRtpCodecsOpus::Fuzz(const uint8_t* data, size_t len)\n{\n\tconst RTC::RTP::Codecs::Opus::PayloadDescriptor* descriptor =\n\t  RTC::RTP::Codecs::Opus::Parse(data, len);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerVP8.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerVP8.hpp\"\n#include \"RTC/RTP/Codecs/VP8.hpp\"\n\nvoid FuzzerRtcRtpCodecsVP8::Fuzz(const uint8_t* data, size_t len)\n{\n\tconst RTC::RTP::Codecs::VP8::PayloadDescriptor* descriptor =\n\t  RTC::RTP::Codecs::VP8::Parse(data, len);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/Codecs/FuzzerVP9.cpp",
    "content": "#include \"RTC/RTP/Codecs/FuzzerVP9.hpp\"\n#include \"RTC/RTP/Codecs/VP9.hpp\"\n\nvoid FuzzerRtcRtpCodecsVP9::Fuzz(const uint8_t* data, size_t len)\n{\n\tconst RTC::RTP::Codecs::VP9::PayloadDescriptor* descriptor =\n\t  RTC::RTP::Codecs::VP9::Parse(data, len);\n\n\tif (!descriptor)\n\t{\n\t\treturn;\n\t}\n\n\tdelete descriptor;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/FuzzerPacket.cpp",
    "content": "#include \"RTC/RTP/FuzzerPacket.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <string>\n#include <vector>\n\nvoid FuzzerRtcRtcPacket::Fuzz(const uint8_t* data, size_t len)\n{\n\tif (!RTC::RTP::Packet::IsRtp(data, len))\n\t{\n\t\treturn;\n\t}\n\n\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(data, len, len) };\n\n\tif (!packet)\n\t{\n\t\treturn;\n\t}\n\n\t// We need to serialize the Packet into a separate buffer because setters\n\t// below will try to write into packet memory.\n\t//\n\t// NOTE: Let's make the buffer bigger to test API that increases packet size.\n\tconst std::unique_ptr<uint8_t[]> buffer(new uint8_t[len + 512]);\n\n\tpacket->Serialize(buffer.get(), len + 512);\n\n\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\tuint8_t extenLen;\n\tbool voice;\n\tuint8_t volume;\n\tbool camera;\n\tbool flip;\n\tuint16_t rotation;\n\tuint32_t absSendTime;\n\tuint16_t playoutDelayMinDelay;\n\tuint16_t playoutDelayMaxDelay;\n\tuint16_t wideSeqNumber;\n\tstd::string mid;\n\tstd::string rid;\n\n\tpacket->GetBuffer();\n\tpacket->GetBufferLength();\n\tpacket->GetLength();\n\t// packet->Dump();\n\tpacket->GetVersion();\n\tpacket->GetPayloadType();\n\tpacket->SetPayloadType(100);\n\tpacket->HasMarker();\n\tpacket->SetMarker(true);\n\tpacket->SetMarker(false);\n\tpacket->GetSequenceNumber();\n\tpacket->SetSequenceNumber(12345);\n\tpacket->GetTimestamp();\n\tpacket->SetTimestamp(8888);\n\tpacket->GetSsrc();\n\tpacket->SetSsrc(666);\n\tpacket->HasCsrcs();\n\n\tpacket->HasHeaderExtension();\n\tpacket->GetHeaderExtensionId();\n\tpacket->GetHeaderExtensionValue();\n\tpacket->GetHeaderExtensionValueLength();\n\tpacket->HasExtensions();\n\tpacket->HasOneByteExtensions();\n\tpacket->HasTwoBytesExtensions();\n\n\tRTC::RTP::HeaderExtensionIds headerExtensionIds{};\n\n\theaderExtensionIds.mid               = 5;\n\theaderExtensionIds.rid               = 6;\n\theaderExtensionIds.absSendTime       = 3;\n\theaderExtensionIds.transportWideCc01 = 4;\n\theaderExtensionIds.ssrcAudioLevel    = 1;\n\theaderExtensionIds.videoOrientation  = 2;\n\theaderExtensionIds.playoutDelay      = 8;\n\n\tpacket->AssignExtensionIds(headerExtensionIds);\n\n\tpacket->HasExtension(5);\n\tpacket->GetExtensionValue(5, extenLen);\n\tpacket->ReadMid(mid);\n\tpacket->UpdateMid(mid);\n\n\tpacket->HasExtension(6);\n\tpacket->GetExtensionValue(6, extenLen);\n\tpacket->ReadRid(rid);\n\n\tpacket->HasExtension(3);\n\tpacket->GetExtensionValue(3, extenLen);\n\tpacket->ReadAbsSendTime(absSendTime);\n\tpacket->UpdateAbsSendTime(12345678u);\n\n\tpacket->HasExtension(4);\n\tpacket->GetExtensionValue(4, extenLen);\n\tpacket->ReadTransportWideCc01(wideSeqNumber);\n\tpacket->UpdateTransportWideCc01(12345u);\n\n\tpacket->HasExtension(1);\n\tpacket->GetExtensionValue(1, extenLen);\n\tpacket->ReadSsrcAudioLevel(volume, voice);\n\n\tpacket->HasExtension(2);\n\tpacket->GetExtensionValue(2, extenLen);\n\tpacket->ReadVideoOrientation(camera, flip, rotation);\n\n\tpacket->HasExtension(8);\n\tpacket->GetExtensionValue(8, extenLen);\n\tpacket->ReadPlayoutDelay(playoutDelayMinDelay, playoutDelayMaxDelay);\n\n\tpacket->HasExtension(6);\n\tpacket->HasExtension(7);\n\tpacket->HasExtension(8);\n\tpacket->HasExtension(9);\n\tpacket->HasExtension(10);\n\tpacket->HasExtension(11);\n\tpacket->HasExtension(12);\n\tpacket->HasExtension(13);\n\tpacket->HasExtension(14);\n\tpacket->HasExtension(15);\n\n\tuint8_t value1[] = { 0x01, 0x02, 0x03, 0x04 };\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::MID, // type\n\t  1,                                     // id\n\t  4,                                     // len\n\t  value1                                 // value\n\t);\n\n\tuint8_t value2[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11 };\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, // type\n\t  2,                                               // id\n\t  11,                                              // len\n\t  value2                                           // value\n\t);\n\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\n\textensions.clear();\n\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\n\tuint8_t value3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,\n\t\t                   0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 };\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::MID, // type\n\t  14,                                    // id\n\t  24,                                    // len\n\t  value3                                 // value\n\t);\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, // type\n\t  15,                                              // id\n\t  24,                                              // len\n\t  value3                                           // value\n\t);\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, // type\n\t  22,                                                       // id\n\t  24,                                                       // len\n\t  value3                                                    // value\n\t);\n\n\textensions.emplace_back(\n\t  RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR, // type\n\t  100,                                                     // id\n\t  24,                                                      // len\n\t  value3                                                   // value\n\t);\n\n\t// NOTE: Cannot use One-Byte Extensions because we are using big ids and\n\t// lengths.\n\t// packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions);\n\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\n\tpacket->HasExtension(13);\n\tpacket->GetExtensionValue(13, extenLen);\n\tpacket->ReadAbsSendTime(absSendTime);\n\tpacket->UpdateAbsSendTime(12345678);\n\n\tpacket->HasExtension(14);\n\tpacket->GetExtensionValue(14, extenLen);\n\tpacket->ReadTransportWideCc01(wideSeqNumber);\n\tpacket->UpdateTransportWideCc01(12345);\n\n\tpacket->HasExtension(11);\n\tpacket->GetExtensionValue(11, extenLen);\n\tpacket->ReadSsrcAudioLevel(volume, voice);\n\n\tpacket->HasExtension(12);\n\tpacket->GetExtensionValue(12, extenLen);\n\tpacket->ReadVideoOrientation(camera, flip, rotation);\n\n\tpacket->HasExtension(15);\n\tpacket->GetExtensionValue(15, extenLen);\n\tpacket->ReadPlayoutDelay(playoutDelayMinDelay, playoutDelayMaxDelay);\n\n\tpacket->HasPayload();\n\tpacket->GetPayload();\n\tpacket->GetPayloadLength();\n\tpacket->HasPadding();\n\tpacket->IsPaddedTo4Bytes();\n\tpacket->GetPaddingLength();\n\tpacket->SetPaddingLength(1);\n\tpacket->SetPaddingLength(6);\n\tpacket->SetPaddingLength(0);\n\tpacket->IsPaddedTo4Bytes();\n\n\t// clang-format off\n\tuint8_t payload[] =\n\t{\n\t\t0x11, 0x22, 0x33, 0x44,\n\t\t0x55, 0x66, 0x77, 0x88,\n\t\t0x99, 0xAA\n\t};\n\t// clang-format on\n\n\tpacket->SetPayload(payload, sizeof(payload));\n\tpacket->RtxEncode(1, 2, 3);\n\tpacket->RtxDecode(4, 5);\n\tpacket->PadTo4Bytes();\n\tpacket->ShiftPayload(4, 2);\n\tpacket->ShiftPayload(4, -2);\n\tpacket->ShiftPayload(3, 4);\n\tpacket->ShiftPayload(3, -4);\n\n\t// These cannot be tested this way.\n\t// packet->SetPayloadDescriptorHandler();\n\t// packet->ProcessPayload();\n\t// packet->GetPayloadEncoder();\n\t// packet->EncodePayload();\n\t// packet->RestorePayload();\n\t// packet->IsKeyFrame();\n\t// packet->GetSpatialLayer();\n\t// packet->GetTemporalLayer();\n\n\tconst std::unique_ptr<uint8_t[]> buffer2(new uint8_t[len + 512]);\n\n\tpacket.reset(packet->Clone(buffer2.get(), len + 512));\n\n\tpacket->RemoveHeaderExtension();\n\tpacket->SetPayloadLength(sizeof(payload) - 2);\n\tpacket->RemovePayload();\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/FuzzerProbationGenerator.cpp",
    "content": "#include \"RTC/RTP/FuzzerProbationGenerator.hpp\"\n#include \"RTC/RTP/ProbationGenerator.hpp\"\n\nvoid FuzzerRtcRtpProbationGenerator::Fuzz(const uint8_t* /*data*/, size_t len)\n{\n\tstd::unique_ptr<RTC::RTP::ProbationGenerator> probationGenerator{\n\t\tnew RTC::RTP::ProbationGenerator()\n\t};\n\n\tprobationGenerator->GetNextPacket(len);\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/FuzzerRetransmissionBuffer.cpp",
    "content": "#include \"RTC/RTP/FuzzerRetransmissionBuffer.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RetransmissionBuffer.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n\nvoid FuzzerRtcRtpRetransmissionBuffer::Fuzz(const uint8_t* data, size_t len)\n{\n\tconst uint16_t maxItems{ 2500u };\n\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\tconst uint32_t clockRate{ 90000 };\n\n\t// Trick to initialize our stuff just once (use static).\n\tstatic RTC::RTP::RetransmissionBuffer retransmissionBuffer(\n\t  maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t// clang-format off\n\tuint8_t buffer[] =\n\t{\n\t\t0b10000000, 0b01111011, 0b01010010, 0b00001110,\n\t\t0b01011011, 0b01101011, 0b11001010, 0b10110101,\n\t\t0, 0, 0, 2\n\t};\n\t// clang-format on\n\n\t// Create base RtpPacket instance.\n\tauto* packet = RTC::RTP::Packet::Parse(buffer, 12);\n\tsize_t offset{ 0u };\n\n\twhile (len >= 4u)\n\t{\n\t\tconst RTC::RTP::SharedPacket sharedPacket;\n\n\t\t// Set 'random' sequence number and timestamp.\n\t\tpacket->SetSequenceNumber(Utils::Byte::Get2Bytes(data, offset));\n\t\tpacket->SetTimestamp(Utils::Byte::Get4Bytes(data, offset));\n\n\t\tretransmissionBuffer.Insert(packet, sharedPacket);\n\n\t\tlen -= 4u;\n\t\toffset += 4;\n\t}\n\n\tdelete packet;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/RTP/FuzzerRtpStreamSend.cpp",
    "content": "#include \"RTC/RTP/FuzzerRtpStreamSend.hpp\"\n#include \"Utils.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n\nnamespace\n{\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tthread_local mocks::MockShared shared(/*getTimeMs*/\n\t                                      []()\n\t                                      {\n\t\t                                      return 1000;\n\t                                      });\n} // namespace\n\nvoid FuzzerRtcRtpStreamSend::Fuzz(const uint8_t* data, size_t len)\n{\n\t// clang-format off\n\tuint8_t buffer[] =\n\t{\n\t\t0b10000000, 0b01111011, 0b01010010, 0b00001110,\n\t\t0b01011011, 0b01101011, 0b11001010, 0b10110101,\n\t\t0, 0, 0, 2\n\t};\n\t// clang-format on\n\n\t// Create base RtpPacket instance.\n\tauto* packet = RTC::RTP::Packet::Parse(buffer, 12);\n\n\t// Create a RtpStreamSend instance.\n\tTestRtpStreamListener testRtpStreamListener;\n\n\t// Create RtpStreamSend instance.\n\tRTC::RTP::RtpStream::Params params;\n\n\tparams.ssrc          = 1111;\n\tparams.clockRate     = 90000;\n\tparams.useNack       = true;\n\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\tpacket->SetSsrc(params.ssrc);\n\n\tstd::string mid;\n\tauto* stream = new RTC::RTP::RtpStreamSend(\n\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);\n\tsize_t offset{ 0u };\n\n\twhile (len >= 4u)\n\t{\n\t\tconst RTC::RTP::SharedPacket sharedPacket;\n\n\t\t// Set 'random' sequence number and timestamp.\n\t\tpacket->SetSequenceNumber(Utils::Byte::Get2Bytes(data, offset));\n\t\tpacket->SetTimestamp(Utils::Byte::Get4Bytes(data, offset));\n\n\t\tstream->ReceivePacket(packet, sharedPacket);\n\n\t\tlen -= 4u;\n\t\toffset += 4;\n\t}\n\n\tdelete stream;\n\tdelete packet;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/SCTP/association/FuzzerStateCookie.cpp",
    "content": "#include \"RTC/SCTP/FuzzerStateCookie.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace\n{\n\talignas(4) thread_local uint8_t DataBuffer[65536];\n\talignas(4) thread_local uint8_t StateCookieSerializeBuffer[65536];\n\talignas(4) thread_local uint8_t StateCookieCloneBuffer[65536];\n} // namespace\n\nvoid FuzzerRtcSctpStateCookie::Fuzz(const uint8_t* data, size_t len)\n{\n\t// NOTE: We need to copy given data into another buffer because we are gonna\n\t// write into it.\n\tstd::memcpy(DataBuffer, data, len);\n\n\t// We need to force `data` to be a StateCookie since it's too hard that\n\t// random data matches it.\n\tif (len > RTC::SCTP::StateCookie::StateCookieLength)\n\t{\n\t\tlen = Utils::Crypto::GetRandomUInt<size_t>(\n\t\t  RTC::SCTP::StateCookie::StateCookieLength, RTC::SCTP::StateCookie::StateCookieLength + 10);\n\n\t\tif (len < RTC::SCTP::StateCookie::StateCookieLength + 5)\n\t\t{\n\t\t\tUtils::Byte::Set8Bytes(DataBuffer, 0, RTC::SCTP::StateCookie::Magic1);\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  DataBuffer,\n\t\t\t  RTC::SCTP::StateCookie::NegotiatedCapabilitiesOffset,\n\t\t\t  RTC::SCTP::StateCookie::Magic2);\n\t\t}\n\t}\n\n\tRTC::SCTP::StateCookie* stateCookie = RTC::SCTP::StateCookie::Parse(DataBuffer, len);\n\n\tif (!stateCookie)\n\t{\n\t\treturn;\n\t}\n\n\tstateCookie->GetLocalVerificationTag();\n\tstateCookie->GetRemoteVerificationTag();\n\tstateCookie->GetLocalInitialTsn();\n\tstateCookie->GetRemoteInitialTsn();\n\tstateCookie->GetRemoteAdvertisedReceiverWindowCredit();\n\tstateCookie->GetTieTag();\n\tstateCookie->GetNegotiatedCapabilities();\n\n\tstateCookie->Serialize(StateCookieSerializeBuffer, len);\n\n\tstateCookie->GetLocalVerificationTag();\n\tstateCookie->GetRemoteVerificationTag();\n\tstateCookie->GetLocalInitialTsn();\n\tstateCookie->GetRemoteInitialTsn();\n\tstateCookie->GetRemoteAdvertisedReceiverWindowCredit();\n\tstateCookie->GetTieTag();\n\tstateCookie->GetNegotiatedCapabilities();\n\n\tauto* clonedStateCookie = stateCookie->Clone(StateCookieCloneBuffer, len);\n\n\tdelete stateCookie;\n\n\tclonedStateCookie->GetLocalVerificationTag();\n\tclonedStateCookie->GetRemoteVerificationTag();\n\tclonedStateCookie->GetLocalInitialTsn();\n\tclonedStateCookie->GetRemoteInitialTsn();\n\tclonedStateCookie->GetRemoteAdvertisedReceiverWindowCredit();\n\tclonedStateCookie->GetTieTag();\n\tclonedStateCookie->GetNegotiatedCapabilities();\n\n\tclonedStateCookie->Serialize(StateCookieSerializeBuffer, len);\n\n\tdelete clonedStateCookie;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/RTC/SCTP/packet/FuzzerPacket.cpp",
    "content": "#include \"RTC/SCTP/packet/FuzzerPacket.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n\nnamespace\n{\n\tthread_local uint8_t PacketSerializeBuffer[65536];\n\tthread_local uint8_t PacketCloneBuffer[65536];\n} // namespace\n\nvoid FuzzerRtcSctpPacket::Fuzz(const uint8_t* data, size_t len)\n{\n\tRTC::SCTP::Packet* packet = RTC::SCTP::Packet::Parse(data, len);\n\n\tif (!packet)\n\t{\n\t\treturn;\n\t}\n\n\tpacket->GetSourcePort();\n\tpacket->GetDestinationPort();\n\tpacket->GetVerificationTag();\n\tpacket->GetChecksum();\n\tpacket->ValidateCRC32cChecksum();\n\tpacket->HasChunks();\n\tpacket->GetChunksCount();\n\tpacket->GetChunkAt(0);\n\tpacket->GetChunkAt(1);\n\tpacket->GetChunkAt(2);\n\tpacket->GetChunkAt(666);\n\n\tpacket->Serialize(PacketSerializeBuffer, len);\n\n\tpacket->GetSourcePort();\n\tpacket->SetSourcePort(12345);\n\tpacket->GetDestinationPort();\n\tpacket->SetDestinationPort(54321);\n\tpacket->GetVerificationTag();\n\tpacket->SetVerificationTag(12345678);\n\tpacket->GetChecksum();\n\tpacket->ValidateCRC32cChecksum();\n\tpacket->SetChecksum(999999);\n\tpacket->WriteCRC32cChecksum();\n\tpacket->ValidateCRC32cChecksum();\n\tpacket->HasChunks();\n\tpacket->GetChunksCount();\n\tpacket->GetChunkAt(0);\n\tpacket->GetChunkAt(1);\n\tpacket->GetChunkAt(2);\n\tpacket->GetChunkAt(666);\n\n\tauto* clonedPacket = packet->Clone(PacketCloneBuffer, len);\n\n\tdelete packet;\n\n\tclonedPacket->GetSourcePort();\n\tclonedPacket->SetSourcePort(12345);\n\tclonedPacket->GetDestinationPort();\n\tclonedPacket->SetDestinationPort(54321);\n\tclonedPacket->GetVerificationTag();\n\tclonedPacket->SetVerificationTag(12345678);\n\tclonedPacket->GetChecksum();\n\tclonedPacket->ValidateCRC32cChecksum();\n\tclonedPacket->SetChecksum(999999);\n\tclonedPacket->WriteCRC32cChecksum();\n\tclonedPacket->ValidateCRC32cChecksum();\n\tclonedPacket->HasChunks();\n\tclonedPacket->GetChunksCount();\n\tclonedPacket->GetChunkAt(0);\n\tclonedPacket->GetChunkAt(1);\n\tclonedPacket->GetChunkAt(2);\n\tclonedPacket->GetChunkAt(666);\n\n\tclonedPacket->Serialize(PacketSerializeBuffer, len);\n\n\tdelete clonedPacket;\n}\n"
  },
  {
    "path": "worker/fuzzer/src/fuzzer.cpp",
    "content": "#define MS_CLASS \"fuzzer\"\n\n#include \"common.hpp\"\n#include \"DepLibSRTP.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"DepLibWebRTC.hpp\"\n#include \"DepOpenSSL.hpp\"\n// TODO: Remove once we only use built-in SCTP stack.\n#include \"DepUsrSCTP.hpp\"\n#include \"FuzzerUtils.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/DtlsTransport.hpp\"\n#include \"RTC/FuzzerDtlsTransport.hpp\"\n#include \"RTC/FuzzerRateCalculator.hpp\"\n#include \"RTC/FuzzerSeqManager.hpp\"\n#include \"RTC/FuzzerTrendCalculator.hpp\"\n#include \"RTC/ICE/FuzzerStunPacket.hpp\"\n#include \"RTC/RTCP/FuzzerPacket.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerAV1.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerH264.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerOpus.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerVP8.hpp\"\n#include \"RTC/RTP/Codecs/FuzzerVP9.hpp\"\n#include \"RTC/RTP/FuzzerPacket.hpp\"\n#include \"RTC/RTP/FuzzerProbationGenerator.hpp\"\n#include \"RTC/RTP/FuzzerRetransmissionBuffer.hpp\"\n#include \"RTC/RTP/FuzzerRtpStreamSend.hpp\"\n#include \"RTC/SCTP/FuzzerStateCookie.hpp\"\n#include \"RTC/SCTP/packet/FuzzerPacket.hpp\"\n#include <cstdlib> // std::getenv()\n#include <iostream>\n#include <sstream> // std::istringstream()\n#include <string>\n#include <vector>\n\nnamespace\n{\n\t// NOLINTBEGIN(readability-identifier-naming)\n\tbool fuzzStun   = false;\n\tbool fuzzDtls   = false;\n\tbool fuzzSctp   = false;\n\tbool fuzzRtp    = false;\n\tbool fuzzRtcp   = false;\n\tbool fuzzCodecs = false;\n\tbool fuzzUtils  = false;\n\t// NOLINTEND(readability-identifier-naming)\n\n\tint init()\n\t{\n\t\tstd::string logLevel{ \"none\" };\n\t\tstd::vector<std::string> logTags = { \"info\" };\n\n\t\tconst auto* logLevelPtr = std::getenv(\"MS_FUZZ_LOG_LEVEL\");\n\t\tconst auto* logTagsPtr  = std::getenv(\"MS_FUZZ_LOG_TAGS\");\n\n\t\t// Get logLevel from ENV variable.\n\t\tif (logLevelPtr)\n\t\t{\n\t\t\tlogLevel = std::string(logLevelPtr);\n\t\t}\n\n\t\t// Get logTags from ENV variable.\n\t\tif (logTagsPtr)\n\t\t{\n\t\t\tauto logTagsStr = std::string(logTagsPtr);\n\t\t\tstd::istringstream iss(logTagsStr);\n\t\t\tstd::string logTag;\n\n\t\t\twhile (iss >> logTag)\n\t\t\t{\n\t\t\t\tlogTags.push_back(logTag);\n\t\t\t}\n\t\t}\n\n\t\tSettings::SetLogLevel(logLevel);\n\t\tSettings::SetLogTags(logTags);\n\t\tSettings::PrintConfiguration();\n\n\t\t// Select what to fuzz.\n\n\t\tif (std::getenv(\"MS_FUZZ_STUN\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] STUN fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzStun = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_DTLS\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] DTLS fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzDtls = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_SCTP\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] SCTP fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzSctp = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_RTP\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] RTP fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzRtp = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_RTCP\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] RTCP fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzRtcp = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_CODECS\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] codecs fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzCodecs = true;\n\t\t}\n\n\t\tif (std::getenv(\"MS_FUZZ_UTILS\"))\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] Utils fuzzer enabled\" << std::endl;\n\n\t\t\tfuzzUtils = true;\n\t\t}\n\n\t\tif (!fuzzStun && !fuzzDtls && !fuzzSctp && !fuzzRtp && !fuzzRtcp && !fuzzCodecs && !fuzzUtils)\n\t\t{\n\t\t\tstd::cout << \"[fuzzer] all fuzzers enabled\" << std::endl;\n\n\t\t\tfuzzStun   = true;\n\t\t\tfuzzDtls   = true;\n\t\t\tfuzzSctp   = true;\n\t\t\tfuzzRtp    = true;\n\t\t\tfuzzRtcp   = true;\n\t\t\tfuzzCodecs = true;\n\t\t\tfuzzUtils  = true;\n\t\t}\n\n\t\t// Initialize static stuff.\n\t\tDepLibUV::ClassInit();\n\t\tDepOpenSSL::ClassInit();\n\t\tDepLibSRTP::ClassInit();\n\t\t// TODO: Remove once we only use built-in SCTP stack.\n\t\tif (!Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tDepUsrSCTP::ClassInit();\n\t\t}\n\t\tDepLibWebRTC::ClassInit();\n\t\tUtils::Crypto::ClassInit();\n\t\tRTC::DtlsTransport::ClassInit();\n\n\t\treturn 0;\n\t}\n} // namespace\n\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len)\n{\n\t// Trick to initialize our stuff just once.\n\t// NOLINTNEXTLINE(readability-identifier-naming)\n\tstatic thread_local const int unused = init();\n\n\t// Avoid [-Wunused-variable].\n\t(void)unused;\n\n\tif (fuzzStun)\n\t{\n\t\tFuzzerRtcIceStunPacket::Fuzz(data, len);\n\t}\n\n\tif (fuzzDtls)\n\t{\n\t\tFuzzerRtcDtlsTransport::Fuzz(data, len);\n\t}\n\n\tif (fuzzSctp)\n\t{\n\t\tFuzzerRtcSctpPacket::Fuzz(data, len);\n\t\tFuzzerRtcSctpStateCookie::Fuzz(data, len);\n\t}\n\n\tif (fuzzRtp)\n\t{\n\t\tFuzzerRtcRtcPacket::Fuzz(data, len);\n\t\tFuzzerRtcRtpStreamSend::Fuzz(data, len);\n\t\tFuzzerRtcRtpRetransmissionBuffer::Fuzz(data, len);\n\t\tFuzzerRtcRtpProbationGenerator::Fuzz(data, len);\n\t\tFuzzerRtcSeqManager::Fuzz(data, len);\n\t\tFuzzerRtcRateCalculator::Fuzz(data, len);\n\t}\n\n\tif (fuzzRtcp)\n\t{\n\t\tFuzzerRtcRtcpPacket::Fuzz(data, len);\n\t}\n\n\tif (fuzzCodecs)\n\t{\n\t\tFuzzerRtcRtpCodecsOpus::Fuzz(data, len);\n\t\tFuzzerRtcRtpCodecsVP8::Fuzz(data, len);\n\t\tFuzzerRtcRtpCodecsVP9::Fuzz(data, len);\n\t\tFuzzerRtcRtpCodecsH264::Fuzz(data, len);\n\t\tFuzzerRtcRtpCodecsAV1::Fuzz(data, len);\n\t\tFuzzerRtcRtpCodecsDependencyDescriptor::Fuzz(data, len);\n\t}\n\n\tif (fuzzUtils)\n\t{\n\t\tFuzzerUtils::Fuzz(data, len);\n\t\tFuzzerRtcTrendCalculator::Fuzz(data, len);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "worker/include/Channel/ChannelMessageRegistrator.hpp",
    "content": "#ifndef MS_CHANNEL_MESSAGE_REGISTRATOR_HPP\n#define MS_CHANNEL_MESSAGE_REGISTRATOR_HPP\n\n#include \"Channel/ChannelMessageRegistratorInterface.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace Channel\n{\n\tclass ChannelMessageRegistrator : public Channel::ChannelMessageRegistratorInterface\n\t{\n\tpublic:\n\t\texplicit ChannelMessageRegistrator();\n\n\t\t~ChannelMessageRegistrator() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Worker::ChannelMessageHandlers> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\n\t\tvoid RegisterHandler(\n\t\t  const std::string& id,\n\t\t  ChannelSocket::RequestHandler* channelRequestHandler,\n\t\t  ChannelSocket::NotificationHandler* channelNotificationHandler) override;\n\n\t\tvoid UnregisterHandler(const std::string& id) override;\n\n\t\tChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) override;\n\n\t\tChannelSocket::NotificationHandler* GetChannelNotificationHandler(const std::string& id) override;\n\n\tprivate:\n\t\tstd::unordered_map<std::string, ChannelSocket::RequestHandler*> mapChannelRequestHandlers;\n\t\tstd::unordered_map<std::string, ChannelSocket::NotificationHandler*> mapChannelNotificationHandlers;\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/Channel/ChannelMessageRegistratorInterface.hpp",
    "content": "#ifndef MS_CHANNEL_MESSAGE_REGISTRATOR_INTERFACE_HPP\n#define MS_CHANNEL_MESSAGE_REGISTRATOR_INTERFACE_HPP\n\n// TODO: We should have a ChannelSocketInterface class instead.\n#include \"Channel/ChannelSocket.hpp\"\n#include <string>\n\nnamespace Channel\n{\n\tclass ChannelMessageRegistratorInterface\n\t{\n\tpublic:\n\t\tvirtual ~ChannelMessageRegistratorInterface() = default;\n\n\tpublic:\n\t\tvirtual flatbuffers::Offset<FBS::Worker::ChannelMessageHandlers> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) = 0;\n\n\t\tvirtual void RegisterHandler(\n\t\t  const std::string& id,\n\t\t  ChannelSocket::RequestHandler* channelRequestHandler,\n\t\t  ChannelSocket::NotificationHandler* channelNotificationHandler) = 0;\n\n\t\tvirtual void UnregisterHandler(const std::string& id) = 0;\n\n\t\tvirtual ChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) = 0;\n\n\t\tvirtual ChannelSocket::NotificationHandler* GetChannelNotificationHandler(const std::string& id) = 0;\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/Channel/ChannelNotification.hpp",
    "content": "#ifndef MS_CHANNEL_NOTIFICATION_HPP\n#define MS_CHANNEL_NOTIFICATION_HPP\n\n#include \"FBS/notification.h\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n\nnamespace Channel\n{\n\tclass ChannelNotification\n\t{\n\tpublic:\n\t\tusing Event = FBS::Notification::Event;\n\n\tprivate:\n\t\tstatic const absl::flat_hash_map<FBS::Notification::Event, const char*> Event2String;\n\n\tpublic:\n\t\texplicit ChannelNotification(const FBS::Notification::Notification* notification);\n\t\t~ChannelNotification() = default;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tEvent event;\n\t\t// Others.\n\t\tconst char* eventCStr;\n\t\tstd::string handlerId;\n\t\tconst FBS::Notification::Notification* data{ nullptr };\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/Channel/ChannelNotifier.hpp",
    "content": "#ifndef MS_CHANNEL_NOTIFIER_HPP\n#define MS_CHANNEL_NOTIFIER_HPP\n\n#include \"Channel/ChannelSocket.hpp\"\n#include <string>\n\nnamespace Channel\n{\n\tclass ChannelNotifier\n\t{\n\tpublic:\n\t\texplicit ChannelNotifier(Channel::ChannelSocket* channel);\n\n\tpublic:\n\t\tflatbuffers::FlatBufferBuilder& GetBufferBuilder()\n\t\t{\n\t\t\treturn this->bufferBuilder;\n\t\t}\n\n\t\ttemplate<class Body>\n\t\tvoid Emit(\n\t\t  const std::string& targetId,\n\t\t  FBS::Notification::Event event,\n\t\t  FBS::Notification::Body type,\n\t\t  flatbuffers::Offset<Body>& body)\n\t\t{\n\t\t\tauto& builder     = this->bufferBuilder;\n\t\t\tauto notification = FBS::Notification::CreateNotificationDirect(\n\t\t\t  builder, targetId.c_str(), event, type, body.Union());\n\t\t\tauto message =\n\t\t\t  FBS::Message::CreateMessage(builder, FBS::Message::Body::Notification, notification.Union());\n\n\t\t\tbuilder.FinishSizePrefixed(message);\n\t\t\tthis->channel->Send(builder.GetBufferPointer(), builder.GetSize());\n\t\t\tbuilder.Clear();\n\t\t}\n\n\t\tvoid Emit(const std::string& targetId, FBS::Notification::Event event);\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tChannel::ChannelSocket* channel{ nullptr };\n\t\t// Others.\n\t\tflatbuffers::FlatBufferBuilder bufferBuilder;\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/Channel/ChannelRequest.hpp",
    "content": "#ifndef MS_CHANNEL_REQUEST_HPP\n#define MS_CHANNEL_REQUEST_HPP\n\n#include \"common.hpp\"\n#include \"FBS/message.h\"\n#include \"FBS/request.h\"\n#include \"FBS/response.h\"\n#include <flatbuffers/minireflect.h>\n#include <absl/container/flat_hash_map.h>\n#include <string>\n\nnamespace Channel\n{\n\t// Avoid cyclic #include problem by declaring classes instead of including\n\t// the corresponding header files.\n\tclass ChannelSocket;\n\n\tclass ChannelRequest\n\t{\n\tpublic:\n\t\tusing Method = FBS::Request::Method;\n\n\tpublic:\n\t\tstatic thread_local flatbuffers::FlatBufferBuilder bufferBuilder;\n\t\tstatic const absl::flat_hash_map<FBS::Request::Method, const char*> Method2String;\n\n\tpublic:\n\t\tChannelRequest(Channel::ChannelSocket* channel, const FBS::Request::Request* request);\n\n\t\t~ChannelRequest() = default;\n\n\t\tflatbuffers::FlatBufferBuilder& GetBufferBuilder() const\n\t\t{\n\t\t\treturn ChannelRequest::bufferBuilder;\n\t\t}\n\n\t\tvoid Accept();\n\n\t\ttemplate<class Body>\n\t\tvoid Accept(FBS::Response::Body type, flatbuffers::Offset<Body>& body)\n\t\t{\n\t\t\tassert(!this->replied);\n\n\t\t\tthis->replied = true;\n\n\t\t\tauto& builder = ChannelRequest::bufferBuilder;\n\t\t\tauto response = FBS::Response::CreateResponse(builder, this->id, true, type, body.Union());\n\t\t\tauto message =\n\t\t\t  FBS::Message::CreateMessage(builder, FBS::Message::Body::Response, response.Union());\n\n\t\t\tbuilder.FinishSizePrefixed(message);\n\t\t\tthis->Send(builder.GetBufferPointer(), builder.GetSize());\n\t\t\tbuilder.Clear();\n\t\t}\n\n\t\tvoid Error(const char* reason = nullptr);\n\n\t\tvoid TypeError(const char* reason = nullptr);\n\n\tprivate:\n\t\tvoid Send(const uint8_t* buffer, size_t size) const;\n\n\t\tvoid SendResponse(const flatbuffers::Offset<FBS::Response::Response>& response) const;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tChannel::ChannelSocket* channel{ nullptr };\n\t\tconst FBS::Request::Request* data{ nullptr };\n\t\t// Others.\n\t\tuint32_t id{ 0u };\n\t\tMethod method;\n\t\tconst char* methodCStr;\n\t\tstd::string handlerId;\n\t\tbool replied{ false };\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/Channel/ChannelSocket.hpp",
    "content": "#ifndef MS_CHANNEL_SOCKET_HPP\n#define MS_CHANNEL_SOCKET_HPP\n\n#include \"common.hpp\"\n#include \"Channel/ChannelNotification.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"handles/UnixStreamSocketHandle.hpp\"\n\nnamespace Channel\n{\n\tclass ConsumerSocket : public UnixStreamSocketHandle\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnConsumerSocketMessage(\n\t\t\t  const ConsumerSocket* consumerSocket, char* msg, size_t msgLen)         = 0;\n\t\t\tvirtual void OnConsumerSocketClosed(const ConsumerSocket* consumerSocket) = 0;\n\t\t};\n\n\tpublic:\n\t\tConsumerSocket(int fd, size_t bufferSize, Listener* listener);\n\t\t~ConsumerSocket() override;\n\n\t\t/* Pure virtual methods inherited from UnixStreamSocketHandle. */\n\tpublic:\n\t\tvoid UserOnUnixStreamRead() override;\n\t\tvoid UserOnUnixStreamSocketClosed() override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t};\n\n\tclass ProducerSocket : public UnixStreamSocketHandle\n\t{\n\tpublic:\n\t\tProducerSocket(int fd, size_t bufferSize);\n\n\t\t/* Pure virtual methods inherited from UnixStreamSocketHandle. */\n\tpublic:\n\t\tvoid UserOnUnixStreamRead() override\n\t\t{\n\t\t}\n\t\tvoid UserOnUnixStreamSocketClosed() override\n\t\t{\n\t\t}\n\t};\n\n\tclass ChannelSocket : public ConsumerSocket::Listener\n\t{\n\tpublic:\n\t\tclass RequestHandler\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~RequestHandler() = default;\n\n\t\tpublic:\n\t\t\tvirtual void HandleRequest(Channel::ChannelRequest* request) = 0;\n\t\t};\n\n\t\tclass NotificationHandler\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~NotificationHandler() = default;\n\n\t\tpublic:\n\t\t\tvirtual void HandleNotification(Channel::ChannelNotification* notification) = 0;\n\t\t};\n\n\t\tclass Listener : public RequestHandler, public NotificationHandler\n\t\t{\n\t\tpublic:\n\t\t\t~Listener() override = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnChannelClosed(Channel::ChannelSocket* channel) = 0;\n\t\t};\n\n\tpublic:\n#if defined(MS_TEST) || defined(MS_FUZZER)\n\t\texplicit ChannelSocket();\n#endif\n\t\texplicit ChannelSocket(int consumerFd, int producerFd);\n\t\texplicit ChannelSocket(\n\t\t  ChannelReadFn channelReadFn,\n\t\t  ChannelReadCtx channelReadCtx,\n\t\t  ChannelWriteFn channelWriteFn,\n\t\t  ChannelWriteCtx channelWriteCtx);\n\t\t~ChannelSocket() override;\n\n\tpublic:\n\t\tvoid Close();\n\t\tvoid SetListener(Listener* listener);\n\t\tvoid Send(const uint8_t* data, uint32_t dataLen);\n\t\tvoid SendLog(const char* data, uint32_t dataLen);\n\t\tbool CallbackRead();\n\n\tprivate:\n\t\tvoid SendImpl(const uint8_t* payload, uint32_t payloadLen);\n\n\t\t/* Pure virtual methods inherited from ConsumerSocket::Listener. */\n\tpublic:\n\t\tvoid OnConsumerSocketMessage(const ConsumerSocket* consumerSocket, char* msg, size_t msgLen) override;\n\t\tvoid OnConsumerSocketClosed(const ConsumerSocket* consumerSocket) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\t// Others.\n\t\tbool closed{ false };\n\t\tConsumerSocket* consumerSocket{ nullptr };\n\t\tProducerSocket* producerSocket{ nullptr };\n\t\tChannelReadFn channelReadFn{ nullptr };\n\t\tChannelReadCtx channelReadCtx{ nullptr };\n\t\tChannelWriteFn channelWriteFn{ nullptr };\n\t\tChannelWriteCtx channelWriteCtx{ nullptr };\n\t\tuv_async_t* uvReadHandle{ nullptr };\n\t\tflatbuffers::FlatBufferBuilder bufferBuilder;\n\t};\n} // namespace Channel\n\n#endif\n"
  },
  {
    "path": "worker/include/DepLibSRTP.hpp",
    "content": "#ifndef MS_DEP_LIBSRTP_HPP\n#define MS_DEP_LIBSRTP_HPP\n\n#include <srtp.h>\n#include <string>\n#include <unordered_map>\n\nclass DepLibSRTP\n{\npublic:\n\tstatic void ClassInit();\n\n\tstatic void ClassDestroy();\n\n\tstatic bool IsError(srtp_err_status_t code)\n\t{\n\t\treturn (code != srtp_err_status_ok);\n\t}\n\n\tstatic const std::string& GetErrorString(srtp_err_status_t code);\n\nprivate:\n\tstatic const std::unordered_map<srtp_err_status_t, std::string> ErrorCode2String;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/DepLibUV.hpp",
    "content": "#ifndef MS_DEP_LIBUV_HPP\n#define MS_DEP_LIBUV_HPP\n\n#include \"common.hpp\"\n#include <uv.h>\n\nclass DepLibUV\n{\npublic:\n\tstatic void ClassInit();\n\tstatic void ClassDestroy();\n\tstatic void PrintVersion();\n\tstatic void RunLoop();\n\tstatic uv_loop_t* GetLoop()\n\t{\n\t\treturn DepLibUV::loop;\n\t}\n\tstatic uint64_t GetTimeMs()\n\t{\n\t\treturn static_cast<uint64_t>(uv_hrtime() / 1000000u);\n\t}\n\tstatic uint64_t GetTimeUs()\n\t{\n\t\treturn static_cast<uint64_t>(uv_hrtime() / 1000u);\n\t}\n\tstatic uint64_t GetTimeNs()\n\t{\n\t\treturn uv_hrtime();\n\t}\n\t// Used within libwebrtc dependency which uses int64_t values for time\n\t// representation.\n\tstatic int64_t GetTimeMsInt64()\n\t{\n\t\treturn static_cast<int64_t>(DepLibUV::GetTimeMs());\n\t}\n\t// Used within libwebrtc dependency which uses int64_t values for time\n\t// representation.\n\tstatic int64_t GetTimeUsInt64()\n\t{\n\t\treturn static_cast<int64_t>(DepLibUV::GetTimeUs());\n\t}\n\nprivate:\n\tstatic thread_local uv_loop_t* loop;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/DepLibUring.hpp",
    "content": "#ifndef MS_DEP_LIBURING_HPP\n#define MS_DEP_LIBURING_HPP\n\n#include \"FBS/liburing.h\"\n#include <uv.h>\n#include <functional>\n#include <liburing.h>\n#include <queue>\n\nclass DepLibUring\n{\npublic:\n\tusing onSendCallback = const std::function<void(bool sent)>;\n\n\t/* Struct for the user data field of SQE and CQE. */\n\tstruct UserData\n\t{\n\t\t// Pointer to send buffer.\n\t\tuint8_t* store{ nullptr };\n\t\t// Frame len buffer for TCP.\n\t\tuint8_t frameLen[2] = { 0 };\n\t\t// iovec for TCP, first item for framing, second item for payload.\n\t\tstruct iovec iov[2];\n\t\t// Send callback.\n\t\tonSendCallback* cb{ nullptr };\n\t\t// Index in userDatas array.\n\t\tsize_t idx{ 0 };\n\t};\n\n\t/* Number of submission queue entries (SQE). */\n\tstatic constexpr size_t QueueDepth{ 1024 * 4 };\n\tstatic constexpr size_t SendBufferSize{ 1500 };\n\n\tusing SendBuffer = uint8_t[SendBufferSize];\n\n\tstatic void ClassInit();\n\tstatic void ClassDestroy();\n\tstatic bool CheckRuntimeSupport();\n\tstatic bool IsEnabled();\n\tstatic flatbuffers::Offset<FBS::LibUring::Dump> FillBuffer(flatbuffers::FlatBufferBuilder& builder);\n\tstatic void StartPollingCQEs();\n\tstatic void StopPollingCQEs();\n\tstatic uint8_t* GetSendBuffer();\n\tstatic bool PrepareSend(\n\t  int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb);\n\tstatic bool PrepareWrite(\n\t  int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb);\n\tstatic void Submit();\n\tstatic void SetActive();\n\tstatic bool IsActive();\n\n\tclass LibUring;\n\n\t// Whether liburing is enabled or not after runtime checks.\n\tstatic thread_local bool enabled;\n\tstatic thread_local LibUring* liburing;\n\npublic:\n\t// Singleton.\n\tclass LibUring\n\t{\n\tpublic:\n\t\tLibUring();\n\t\t~LibUring();\n\t\tflatbuffers::Offset<FBS::LibUring::Dump> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid StartPollingCQEs();\n\t\tvoid StopPollingCQEs();\n\t\tuint8_t* GetSendBuffer();\n\t\tbool PrepareSend(\n\t\t  int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb);\n\t\tbool PrepareWrite(\n\t\t  int sockfd,\n\t\t  const uint8_t* data1,\n\t\t  size_t len1,\n\t\t  const uint8_t* data2,\n\t\t  size_t len2,\n\t\t  onSendCallback* cb);\n\t\tvoid Submit();\n\t\tvoid SetActive()\n\t\t{\n\t\t\tthis->active = true;\n\t\t}\n\t\tbool IsActive() const\n\t\t{\n\t\t\treturn this->active;\n\t\t}\n\t\tbool IsZeroCopyEnabled() const\n\t\t{\n\t\t\treturn this->zeroCopyEnabled;\n\t\t}\n\t\tio_uring* GetRing()\n\t\t{\n\t\t\treturn std::addressof(this->ring);\n\t\t}\n\t\tint GetEventFd() const\n\t\t{\n\t\t\treturn this->efd;\n\t\t}\n\t\tvoid ReleaseUserDataEntry(size_t idx)\n\t\t{\n\t\t\tthis->availableUserDataEntries.push(idx);\n\t\t}\n\n\tprivate:\n\t\tvoid SetInactive()\n\t\t{\n\t\t\tthis->active = false;\n\t\t}\n\t\tUserData* GetUserData();\n\t\tbool IsDataInSendBuffers(const uint8_t* data) const\n\t\t{\n\t\t\treturn data >= this->sendBuffers[0] && data <= this->sendBuffers[DepLibUring::QueueDepth - 1];\n\t\t}\n\n\tprivate:\n\t\t// io_uring instance.\n\t\tio_uring ring;\n\t\t// Event file descriptor to watch for io_uring completions.\n\t\tint efd;\n\t\t// libuv handle used to poll io_uring completions.\n\t\tuv_poll_t* uvHandle{ nullptr };\n\t\t// Whether we are currently sending RTP over io_uring.\n\t\tbool active{ false };\n\t\t// Whether Zero Copy feature is enabled.\n\t\tbool zeroCopyEnabled{ true };\n\t\t// Pre-allocated UserData's.\n\t\tUserData userDatas[QueueDepth]{};\n\t\t// Indexes of available UserData entries.\n\t\tstd::queue<size_t> availableUserDataEntries;\n\t\t// Pre-allocated SendBuffer's.\n\t\tSendBuffer sendBuffers[QueueDepth];\n\t\t// iovec structs to be registered for Zero Copy.\n\t\tstruct iovec iovecs[QueueDepth];\n\t\t// Submission queue entry process count.\n\t\tuint64_t sqeProcessCount{ 0u };\n\t\t// Submission queue entry miss count.\n\t\tuint64_t sqeMissCount{ 0u };\n\t\t// User data miss count.\n\t\tuint64_t userDataMissCount{ 0u };\n\t};\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/DepLibWebRTC.hpp",
    "content": "#ifndef MS_DEP_LIBWEBRTC_HPP\n#define MS_DEP_LIBWEBRTC_HPP\n\nclass DepLibWebRTC\n{\npublic:\n\tstatic void ClassInit();\n\tstatic void ClassDestroy();\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/DepOpenSSL.hpp",
    "content": "#ifndef MS_DEP_OPENSSL_HPP\n#define MS_DEP_OPENSSL_HPP\n\nclass DepOpenSSL\n{\npublic:\n\tstatic void ClassInit();\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/DepUsrSCTP.hpp",
    "content": "#ifndef MS_DEP_USRSCTP_HPP\n#define MS_DEP_USRSCTP_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/SctpAssociation.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nclass DepUsrSCTP\n{\nprivate:\n\tclass Checker : public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\texplicit Checker(SharedInterface* shared);\n\t\t~Checker() override;\n\n\tpublic:\n\t\tvoid Start();\n\t\tvoid Stop();\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\tTimerHandleInterface* timer{ nullptr };\n\t\tuint64_t lastCalledAtMs{ 0u };\n\t};\n\npublic:\n\tstatic void ClassInit();\n\tstatic void ClassDestroy();\n\tstatic void CreateChecker(SharedInterface* shared);\n\tstatic void CloseChecker();\n\tstatic uintptr_t GetNextSctpAssociationId();\n\tstatic void RegisterSctpAssociation(RTC::SctpAssociation* sctpAssociation);\n\tstatic void DeregisterSctpAssociation(RTC::SctpAssociation* sctpAssociation);\n\tstatic RTC::SctpAssociation* RetrieveSctpAssociation(uintptr_t id);\n\nprivate:\n\tstatic thread_local Checker* checker;\n\tstatic uint64_t numSctpAssociations;\n\tstatic uintptr_t nextSctpAssociationId;\n\tstatic absl::flat_hash_map<uintptr_t, RTC::SctpAssociation*> mapIdSctpAssociation;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/LogLevel.hpp",
    "content": "#ifndef MS_LOG_LEVEL_HPP\n#define MS_LOG_LEVEL_HPP\n\n#include \"common.hpp\"\n\nenum class LogLevel : uint8_t\n{\n\tLOG_DEBUG = 3,\n\tLOG_WARN  = 2,\n\tLOG_ERROR = 1,\n\tLOG_NONE  = 0\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/Logger.hpp",
    "content": "/**\n * Logger facility.\n *\n * This include file defines logging macros for source files (.cpp). Each\n * source file including Logger.hpp MUST define its own MS_CLASS macro. Include\n * files (.hpp) MUST NOT include Logger.hpp.\n *\n * All the logging macros use the same format as printf(). The XXX_STD() version\n * of a macro logs to stdoud/stderr instead of using the ChannelSocket instance.\n * However some macros such as MS_ABORT() and MS_ASSERT() always log to stderr.\n *\n * If the macro MS_LOG_STD is defined, all the macros log to stdout/stderr.\n *\n * If the macro MS_LOG_FILE_LINE is defined, all the logging macros print more\n * verbose information, including current file and line.\n *\n * MS_TRACE()\n *\n *   Logs the current method/function if MS_LOG_TRACE macro is defined and the\n *   current log level is \"debug\".\n *\n * MS_HAS_DEBUG_TAG(tag)\n * MS_HAS_WARN_TAG(tag)\n *\n *   True if the current log level is satisfied and the given tag is enabled.\n *\n * MS_DEBUG_TAG(tag, ...)\n * MS_WARN_TAG(tag, ...)\n *\n *   Logs if the current log level is satisfied and the given tag is enabled.\n *\n *   Example:\n *     MS_WARN_TAG(ice, \"ICE failed\");\n *\n * MS_DEBUG_2TAGS(tag1, tag2, ...)\n * MS_WARN_2TAGS(tag1, tag2, ...)\n *\n *   Logs if the current log level is satisfied and any of the given two tags\n *   is enabled.\n *\n *   Example:\n *     MS_DEBUG_2TAGS(ice, dtls, \"media connection established\");\n *\n * MS_DEBUG_DEV(...)\n *\n * \t Logs if the current source file defines the MS_LOG_DEV_LEVEL macro with\n * \t value 3.\n *\n * \t Example:\n * \t   MS_DEBUG_DEV(\"foo:%\" PRIu32, foo);\n *\n * MS_WARN_DEV(...)\n *\n * \t Logs if the current source file defines the MS_LOG_DEV_LEVEL macro with\n * \t value >= 2.\n *\n * \t Example:\n * \t   MS_WARN_DEV(\"foo:%\" PRIu32, foo);\n *\n * MS_DUMP(...)\n *\n * \t Logs always. Useful for temporal debugging. Do not use it for Dump()\n * \t methods, use MS_DUMP_CLEAN() instead.\n *\n * \t Example:\n * \t   MS_DUMP(\"foo\");\n *\n * MS_DUMP_CLEAN(indentation, ...)\n *\n *   Log always. Useful for Dump() methods in packets. It doesn't print the\n *   class and method names.\n *   `indentation` mandatory argument must be 0, 1, 2 or 3, and it affects the\n *   output by adding indentation at the start of the string.\n *\n * MS_DUMP_DATA(const uint8_t* data, size_t len)\n *\n *   Logs always. Prints the given data in hexadecimal format (Wireshark\n *   friendly).\n *\n * MS_ERROR(...)\n *\n *   Logs an error if the current log level is satisfied (or if the current\n *   source file defines the MS_LOG_DEV_LEVEL macro with value >= 1). Must just\n *   be used for internal errors that should not happen.\n *\n * MS_ABORT(...)\n *\n *   Logs the given error to stderr and aborts the process.\n *\n * MS_ASSERT(condition, ...)\n *\n *   If the condition is not satisfied, it calls MS_ABORT().\n */\n\n#ifndef MS_LOGGER_HPP\n#define MS_LOGGER_HPP\n\n#include \"common.hpp\"\n#include \"LogLevel.hpp\"\n#include \"Settings.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include <cstdio>  // std::snprintf(), std::fprintf(), stdout, stderr\n#include <cstdlib> // std::abort()\n#include <cstring>\n\n// clang-format off\n\n// NOLINTBEGIN\n#define _MS_TAG_ENABLED(tag) Settings::configuration.logTags.tag\n#define _MS_TAG_ENABLED_2(tag1, tag2) (Settings::configuration.logTags.tag1 || Settings::configuration.logTags.tag2)\n// NOLINTEND\n\n#if !defined(MS_LOG_DEV_LEVEL)\n\t#define MS_LOG_DEV_LEVEL 0\n#elif MS_LOG_DEV_LEVEL < 0 || MS_LOG_DEV_LEVEL > 3\n\t#error \"invalid MS_LOG_DEV_LEVEL macro value\"\n#endif\n\n// Usage:\n//   MS_DEBUG_DEV(\"Leading text \"MS_UINT16_TO_BINARY_PATTERN, MS_UINT16_TO_BINARY(value));\n#define MS_UINT16_TO_BINARY_PATTERN \"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\"\n#define MS_UINT16_TO_BINARY(value) \\\n\t((value & 0x8000) ? '1' : '0'), \\\n\t((value & 0x4000) ? '1' : '0'), \\\n\t((value & 0x2000) ? '1' : '0'), \\\n\t((value & 0x1000) ? '1' : '0'), \\\n\t((value & 0x800) ? '1' : '0'), \\\n\t((value & 0x400) ? '1' : '0'), \\\n\t((value & 0x200) ? '1' : '0'), \\\n\t((value & 0x100) ? '1' : '0'), \\\n\t((value & 0x80) ? '1' : '0'), \\\n\t((value & 0x40) ? '1' : '0'), \\\n\t((value & 0x20) ? '1' : '0'), \\\n\t((value & 0x10) ? '1' : '0'), \\\n\t((value & 0x08) ? '1' : '0'), \\\n\t((value & 0x04) ? '1' : '0'), \\\n\t((value & 0x02) ? '1' : '0'), \\\n\t((value & 0x01) ? '1' : '0')\n\n// Usage:\n//   MS_DEBUG_DEV(\"Leading text \"MS_UINT8_TO_BINARY_PATTERN, MS_UINT8_TO_BINARY(value));\n#define MS_UINT8_TO_BINARY_PATTERN \"%c%c%c%c%c%c%c%c\"\n#define MS_UINT8_TO_BINARY(value) \\\n\t((value & 0x80) ? '1' : '0'), \\\n\t((value & 0x40) ? '1' : '0'), \\\n\t((value & 0x20) ? '1' : '0'), \\\n\t((value & 0x10) ? '1' : '0'), \\\n\t((value & 0x08) ? '1' : '0'), \\\n\t((value & 0x04) ? '1' : '0'), \\\n\t((value & 0x02) ? '1' : '0'), \\\n\t((value & 0x01) ? '1' : '0')\n\nclass Logger\n{\npublic:\n\tstatic void ClassInit(Channel::ChannelSocket* channel);\n\npublic:\n\tstatic const uint64_t Pid;\n\tstatic thread_local Channel::ChannelSocket* channel;\n\tstatic const size_t BufferSize {50000};\n\tstatic thread_local char buffer[];\n};\n\n/* Logging macros. */\n\n// NOLINTBEGIN\n#define _MS_LOG_SEPARATOR_CHAR_STD \"\\n\"\n\n#ifdef MS_LOG_FILE_LINE\n\t#define _MS_LOG_STR \"%s:%d | %s::%s()\"\n\t#define _MS_LOG_STR_DESC _MS_LOG_STR \" | \"\n\t#define _MS_FILE (std::strchr(__FILE__, '/') ? std::strchr(__FILE__, '/') + 1 : __FILE__)\n\t#define _MS_LOG_ARG _MS_FILE, __LINE__, MS_CLASS, __FUNCTION__\n#else\n\t#define _MS_LOG_STR \"%s::%s()\"\n\t#define _MS_LOG_STR_DESC _MS_LOG_STR \" | \"\n\t#define _MS_LOG_ARG MS_CLASS, __FUNCTION__\n#endif\n// NOLINTEND\n\n#ifdef MS_LOG_TRACE\n\t#define MS_TRACE() \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \\\n\t\t\t{ \\\n\t\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"D(trace) \" _MS_LOG_STR, _MS_LOG_ARG); \\\n\t\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t\t} \\\n\t\t} \\\n\t\twhile (false)\n\n\t#define MS_TRACE_STD() \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \\\n\t\t\t{ \\\n\t\t\t\tstd::fprintf(stdout, \"(trace) \" _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \\\n\t\t\t\tstd::fflush(stdout); \\\n\t\t\t} \\\n\t\t} \\\n\t\twhile (false)\n#else\n\t#define MS_TRACE() {}\n\t#define MS_TRACE_STD() {}\n#endif\n\n#define MS_HAS_DEBUG_TAG(tag) \\\n\t(Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag))\n\n#define MS_HAS_WARN_TAG(tag) \\\n\t(Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag))\n\n#define MS_DEBUG_TAG(tag, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"D\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_DEBUG_TAG_STD(tag, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \\\n\t\t{ \\\n\t\t\tstd::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stdout); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_WARN_TAG(tag, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"W\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_WARN_TAG_STD(tag, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \\\n\t\t{ \\\n\t\t\tstd::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stderr); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_DEBUG_2TAGS(tag1, tag2, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"D\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_DEBUG_2TAGS_STD(tag1, tag2, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \\\n\t\t{ \\\n\t\t\tstd::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stdout); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_WARN_2TAGS(tag1, tag2, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"W\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_WARN_2TAGS_STD(tag1, tag2, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \\\n\t\t{ \\\n\t\t\tstd::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stderr); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#if MS_LOG_DEV_LEVEL == 3\n\t#define MS_DEBUG_DEV(desc, ...) \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"D\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t\twhile (false)\n\n\t#define MS_DEBUG_DEV_STD(desc, ...) \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tstd::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stdout); \\\n\t\t} \\\n\t\twhile (false)\n#else\n\t#define MS_DEBUG_DEV(desc, ...) {}\n\t#define MS_DEBUG_DEV_STD(desc, ...) {}\n#endif\n\n\n#if MS_LOG_DEV_LEVEL >= 2\n\t#define MS_WARN_DEV(desc, ...) \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"W\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t\twhile (false)\n\n\t#define MS_WARN_DEV_STD(desc, ...) \\\n\t\tdo \\\n\t\t{ \\\n\t\t\tstd::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stderr); \\\n\t\t} \\\n\t\twhile (false)\n#else\n\t#define MS_WARN_DEV(desc, ...) {}\n\t#define MS_WARN_DEV_STD(desc, ...) {}\n#endif\n\n#define MS_DUMP(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"X\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t} \\\n\twhile (false)\n\n#define MS_DUMP_STD(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tstd::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\tstd::fflush(stdout); \\\n\t} \\\n\twhile (false)\n\n#define MS_DUMP_CLEAN(indentation, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tconst int spaceCount = (indentation) * 2; \\\n\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"X%*s\" desc, spaceCount, \"\", ##__VA_ARGS__); \\\n\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t} \\\n\twhile (false)\n\n#define MS_DUMP_CLEAN_STD(indentation, desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tconst int spaceCount = (indentation) * 2; \\\n\t\tstd::fprintf(stdout, \"%*s\" desc _MS_LOG_SEPARATOR_CHAR_STD, spaceCount, \"\", ##__VA_ARGS__); \\\n\t\tstd::fflush(stdout); \\\n\t} \\\n\twhile (false)\n\n#define MS_DUMP_DATA(data, len) \\\n\tdo \\\n\t{ \\\n\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"X\" _MS_LOG_STR, _MS_LOG_ARG); \\\n\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\tsize_t bufferDataLen{ 0 }; \\\n\t\tfor (size_t i{0}; i < len; ++i) \\\n\t\t{ \\\n\t\t  if (i % 4 == 0) \\\n\t\t  { \\\n\t\t  \tif (bufferDataLen != 0) \\\n\t\t  \t{ \\\n\t\t  \t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(bufferDataLen)); \\\n\t\t  \t\tbufferDataLen = 0; \\\n\t\t  \t} \\\n\t\t    const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, \"X%06X \", static_cast<unsigned int>(i)); \\\n\t\t    bufferDataLen += loggerWritten; \\\n\t\t  } \\\n\t\t  const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, \"%02X \", static_cast<unsigned char>(data[i])); \\\n\t\t  bufferDataLen += loggerWritten; \\\n\t\t} \\\n\t\tif (bufferDataLen != 0) \\\n\t\t{ \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(bufferDataLen)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_DUMP_DATA_STD(data, len) \\\n\tdo \\\n\t{ \\\n\t\tstd::fprintf(stdout, _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \\\n\t\tsize_t bufferDataLen{ 0 }; \\\n\t\tfor (size_t i{0}; i < len; ++i) \\\n\t\t{ \\\n\t\t  if (i % 4 == 0) \\\n\t\t  { \\\n\t\t  \tif (bufferDataLen != 0) \\\n\t\t  \t{ \\\n\t\t  \t\tLogger::buffer[bufferDataLen] = '\\0'; \\\n\t\t  \t\tstd::fprintf(stdout, \"%s\", Logger::buffer); \\\n\t\t  \t\tbufferDataLen = 0; \\\n\t\t  \t} \\\n\t\t    const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, \"\\n%06X \", static_cast<unsigned int>(i)); \\\n\t\t    bufferDataLen += loggerWritten; \\\n\t\t  } \\\n\t\t  const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, \"%02X \", static_cast<unsigned char>(data[i])); \\\n\t\t  bufferDataLen += loggerWritten; \\\n\t\t} \\\n\t\tif (bufferDataLen != 0) \\\n\t\t{ \\\n\t\t\tLogger::buffer[bufferDataLen] = '\\0'; \\\n\t\t\tstd::fprintf(stdout, \"%s\", Logger::buffer); \\\n\t\t} \\\n\t\tstd::fflush(stdout); \\\n\t} \\\n\twhile (false)\n\n#define MS_ERROR(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \\\n\t\t{ \\\n\t\t\tconst int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, \"E\" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tLogger::channel->SendLog(Logger::buffer, static_cast<uint32_t>(loggerWritten)); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#define MS_ERROR_STD(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tif (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \\\n\t\t{ \\\n\t\t\tstd::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\t\tstd::fflush(stderr); \\\n\t\t} \\\n\t} \\\n\twhile (false)\n\n#ifdef MS_EXECUTABLE\n#define MS_ABORT(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tstd::fprintf(stderr, \"(ABORT) \" _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\tstd::fflush(stderr); \\\n\t\tstd::abort(); \\\n\t} \\\n\twhile (false)\n#else\n#define MS_ABORT(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tstd::fprintf(stderr, \"(ABORT) \" _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\tstd::fflush(stderr); \\\n\t\tchar abortMessage[Logger::BufferSize]; \\\n\t\tstd::snprintf(abortMessage, Logger::BufferSize, \"(ABORT) \" _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \\\n\t\tthrow std::runtime_error(abortMessage); \\\n\t} \\\n\twhile (false)\n#endif\n\n#define MS_ASSERT(condition, desc, ...) \\\n\tif (!(condition)) \\\n\t{ \\\n\t\tMS_ABORT(\"failed assertion `%s`: \" desc, #condition, ##__VA_ARGS__); \\\n\t}\n\n#ifdef MS_LOG_STD\n\t#undef MS_TRACE\n\t#define MS_TRACE MS_TRACE_STD\n\t#undef MS_DEBUG_TAG\n\t#define MS_DEBUG_TAG MS_DEBUG_TAG_STD\n\t#undef MS_WARN_TAG\n\t#define MS_WARN_TAG MS_WARN_TAG_STD\n\t#undef MS_DEBUG_2TAGS\n\t#define MS_DEBUG_2TAGS MS_DEBUG_2TAGS_STD\n\t#undef MS_WARN_2TAGS\n\t#define MS_WARN_2TAGS MS_WARN_2TAGS_STD\n\t#undef MS_DEBUG_DEV\n\t#define MS_DEBUG_DEV MS_DEBUG_DEV_STD\n\t#undef MS_WARN_DEV\n\t#define MS_WARN_DEV MS_WARN_DEV_STD\n\t#undef MS_DUMP\n\t#define MS_DUMP MS_DUMP_STD\n\t#undef MS_DUMP_CLEAN\n\t#define MS_DUMP_CLEAN MS_DUMP_CLEAN_STD\n\t#undef MS_DUMP_DATA\n\t#define MS_DUMP_DATA MS_DUMP_DATA_STD\n\t#undef MS_ERROR\n\t#define MS_ERROR MS_ERROR_STD\n#endif\n\n// clang-format on\n\n#endif\n"
  },
  {
    "path": "worker/include/MediaSoupErrors.hpp",
    "content": "#ifndef MS_MEDIASOUP_ERRORS_HPP\n#define MS_MEDIASOUP_ERRORS_HPP\n\n#include \"Logger.hpp\"\n#include <cstdio> // std::snprintf()\n#include <stdexcept>\n\nclass MediaSoupError : public std::runtime_error\n{\npublic:\n\texplicit MediaSoupError(const char* description) : std::runtime_error(description)\n\t{\n\t}\n\npublic:\n\tstatic const size_t BufferSize{ 2000 };\n\tstatic thread_local char buffer[];\n};\n\nclass MediaSoupTypeError : public MediaSoupError\n{\npublic:\n\texplicit MediaSoupTypeError(const char* description) : MediaSoupError(description)\n\t{\n\t}\n};\n\n// clang-format off\n#define MS_THROW_ERROR(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tMS_ERROR(\"throwing MediaSoupError: \" desc, ##__VA_ARGS__); \\\n\t\tstd::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \\\n\t\tthrow MediaSoupError(MediaSoupError::buffer); \\\n\t} while (false)\n\n#define MS_THROW_ERROR_STD(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tMS_ERROR_STD(\"throwing MediaSoupError: \" desc, ##__VA_ARGS__); \\\n\t\tstd::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \\\n\t\tthrow MediaSoupError(MediaSoupError::buffer); \\\n\t} while (false)\n\n#define MS_THROW_TYPE_ERROR(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tMS_ERROR(\"throwing MediaSoupTypeError: \" desc, ##__VA_ARGS__); \\\n\t\tstd::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \\\n\t\tthrow MediaSoupTypeError(MediaSoupError::buffer); \\\n\t} while (false)\n\n#define MS_THROW_TYPE_ERROR_STD(desc, ...) \\\n\tdo \\\n\t{ \\\n\t\tMS_ERROR_STD(\"throwing MediaSoupTypeError: \" desc, ##__VA_ARGS__); \\\n\t\tstd::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \\\n\t\tthrow MediaSoupTypeError(MediaSoupError::buffer); \\\n\t} while (false)\n// clang-format on\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/ActiveSpeakerObserver.hpp",
    "content": "#ifndef MS_RTC_ACTIVE_SPEAKER_OBSERVER_HPP\n#define MS_RTC_ACTIVE_SPEAKER_OBSERVER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"RTC/RtpObserver.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <vector>\n\n// Implementation of Dominant Speaker Identification for Multipoint\n// Videoconferencing by Ilana Volfin and Israel Cohen. This implementation uses\n// the RTP Audio Level extension from RFC-6464 for the input signal. This has\n// been ported from DominantSpeakerIdentification.java in Jitsi:\n// https://github.com/jitsi/jitsi-utils/blob/master/src/main/java/org/jitsi/utils/dsi/DominantSpeakerIdentification.java\nnamespace RTC\n{\n\tclass ActiveSpeakerObserver : public RTC::RtpObserver, public TimerHandleInterface::Listener\n\t{\n\tprivate:\n\t\tclass Speaker\n\t\t{\n\t\tpublic:\n\t\t\texplicit Speaker(SharedInterface* shared);\n\n\t\tpublic:\n\t\t\tvoid EvalActivityScores();\n\t\t\tdouble GetActivityScore(uint8_t interval) const;\n\t\t\tvoid LevelChanged(uint32_t level, uint64_t now);\n\t\t\tvoid LevelTimedOut(uint64_t now);\n\n\t\tprivate:\n\t\t\tbool ComputeImmediates();\n\t\t\tbool ComputeLongs();\n\t\t\tbool ComputeMediums();\n\t\t\tvoid EvalImmediateActivityScore();\n\t\t\tvoid EvalMediumActivityScore();\n\t\t\tvoid EvalLongActivityScore();\n\t\t\tvoid UpdateMinLevel(int8_t level);\n\n\t\tpublic:\n\t\t\tbool paused{ false };\n\t\t\tdouble immediateActivityScore{ 0 };\n\t\t\tdouble mediumActivityScore{ 0 };\n\t\t\tdouble longActivityScore{ 0 };\n\t\t\tuint64_t lastLevelChangeTime{ 0 };\n\n\t\tprivate:\n\t\t\tuint8_t minLevel{ 0u };\n\t\t\tuint8_t nextMinLevel{ 0u };\n\t\t\tuint32_t nextMinLevelWindowLen{ 0u };\n\t\t\tstd::vector<uint8_t> immediates;\n\t\t\tstd::vector<uint8_t> mediums;\n\t\t\tstd::vector<uint8_t> longs;\n\t\t\tstd::vector<uint8_t> levels;\n\t\t\tsize_t nextLevelIndex{ 0u };\n\t\t};\n\n\t\tclass ProducerSpeaker\n\t\t{\n\t\tpublic:\n\t\t\tProducerSpeaker(SharedInterface* shared, RTC::Producer* producer);\n\t\t\t~ProducerSpeaker();\n\n\t\tpublic:\n\t\t\tRTC::Producer* producer;\n\t\t\tSpeaker* speaker;\n\t\t};\n\n\tprivate:\n\t\tstatic const uint8_t RelativeSpeachActivitiesLen{ 3u };\n\n\tpublic:\n\t\tActiveSpeakerObserver(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::RtpObserver::Listener* listener,\n\t\t  const FBS::ActiveSpeakerObserver::ActiveSpeakerObserverOptions* options);\n\t\t~ActiveSpeakerObserver() override;\n\n\tpublic:\n\t\tvoid AddProducer(RTC::Producer* producer) override;\n\t\tvoid RemoveProducer(RTC::Producer* producer) override;\n\t\tvoid ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) override;\n\t\tvoid ProducerPaused(RTC::Producer* producer) override;\n\t\tvoid ProducerResumed(RTC::Producer* producer) override;\n\n\tprivate:\n\t\tvoid Paused() override;\n\t\tvoid Resumed() override;\n\t\tvoid Update();\n\t\tbool CalculateActiveSpeaker();\n\t\tvoid TimeoutIdleLevels(uint64_t now);\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface. */\n\tprotected:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\tdouble relativeSpeachActivities[RelativeSpeachActivitiesLen]{};\n\t\tstd::string dominantId;\n\t\tTimerHandleInterface* periodicTimer{ nullptr };\n\t\tuint16_t interval{ 300u };\n\t\t// Map of ProducerSpeakers indexed by Producer id.\n\t\tabsl::flat_hash_map<std::string, ProducerSpeaker*> mapProducerSpeakers;\n\t\tuint64_t lastLevelIdleTime{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/AudioLevelObserver.hpp",
    "content": "#ifndef MS_RTC_AUDIO_LEVEL_OBSERVER_HPP\n#define MS_RTC_AUDIO_LEVEL_OBSERVER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"RTC/RtpObserver.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nnamespace RTC\n{\n\tclass AudioLevelObserver : public RTC::RtpObserver, public TimerHandleInterface::Listener\n\t{\n\tprivate:\n\t\tstruct DBovs\n\t\t{\n\t\t\tuint16_t totalSum{ 0u }; // Sum of dBvos (positive integer).\n\t\t\tsize_t count{ 0u };      // Number of dBvos entries in totalSum.\n\t\t};\n\n\tpublic:\n\t\tAudioLevelObserver(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::RtpObserver::Listener* listener,\n\t\t  const FBS::AudioLevelObserver::AudioLevelObserverOptions* options);\n\t\t~AudioLevelObserver() override;\n\n\tpublic:\n\t\tvoid AddProducer(RTC::Producer* producer) override;\n\t\tvoid RemoveProducer(RTC::Producer* producer) override;\n\t\tvoid ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) override;\n\t\tvoid ProducerPaused(RTC::Producer* producer) override;\n\t\tvoid ProducerResumed(RTC::Producer* producer) override;\n\n\tprivate:\n\t\tvoid Paused() override;\n\t\tvoid Resumed() override;\n\t\tvoid Update();\n\t\tvoid ResetMapProducerDBovs();\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface. */\n\tprotected:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tuint16_t maxEntries{ 1u };\n\t\tint8_t threshold{ -80 };\n\t\tuint16_t interval{ 1000u };\n\t\t// Allocated by this.\n\t\tTimerHandleInterface* periodicTimer{ nullptr };\n\t\t// Others.\n\t\tabsl::flat_hash_map<RTC::Producer*, DBovs> mapProducerDBovs;\n\t\tbool silence{ true };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/BweType.hpp",
    "content": "#ifndef MS_RTC_BWE_TYPE_HPP\n#define MS_RTC_BWE_TYPE_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tenum class BweType : uint8_t\n\t{\n\t\tTRANSPORT_CC = 1,\n\t\tREMB\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Consts.hpp",
    "content": "#ifndef MS_RTC_CONSTS_HPP\n#define MS_RTC_CONSTS_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tnamespace Consts\n\t{\n\t\t/**\n\t\t * Max MTU size.\n\t\t */\n\t\tconstexpr size_t MtuSize{ 1500u };\n\n\t\t/**\n\t\t * Maximum size for a RTCP compound packet.\n\t\t * IPv4|Ipv6 header size (20|40 bytes). IPv6 considered.\n\t\t * UDP|TCP header size (8|20  bytes). TCP considered.\n\t\t * SRTP Encryption (148 bytes):\n\t\t *   - SRTP_MAX_TRAILER_LEN + 4 is the maximum number of octects that will\n\t\t *     be added to an RTCP packet by srtp_protect_rtcp().\n\t\t *   - srtp.h: SRTP_MAX_TRAILER_LEN (SRTP_MAX_TAG_LEN + SRTP_MAX_MKI_LEN).\n\t\t */\n\t\tconstexpr size_t RtcpPacketMaxSize{ RTC::Consts::MtuSize - 40 - 20 - 148u };\n\n\t\t/**\n\t\t * Max length for a 1 byte RTP header extension.\n\t\t */\n\t\tconstexpr uint8_t OneByteRtpExtensionMaxLength{ 16u };\n\n\t\t/**\n\t\t * Max length for a 2 bytes RTP header extension.\n\t\t */\n\t\tconstexpr uint8_t TwoBytesRtpExtensionMaxLength{ 255u };\n\n\t\t/**\n\t\t * MID RTP header extension max length (just used when setting/updating MID\n\t\t * extension).\n\t\t */\n\t\tconstexpr uint8_t MidRtpExtensionMaxLength{ 8u };\n\n\t\t/**\n\t\t * Largest safe SCTP packet. Starting from the minimum guaranteed MTU value\n\t\t * of 1280 for IPv6 (which may not support fragmentation), take off 85\n\t\t * bytes for DTLS/TURN/TCP/IP and ciphertext overhead.\n\t\t *\n\t\t * Additionally, it's possible that TURN adds an additional 4 bytes of\n\t\t * overhead after a channel has been established, so an additional 4 bytes\n\t\t * is subtracted.\n\t\t *\n\t\t * 1280 IPV6 MTU\n\t\t *  -40 IPV6 header\n\t\t *   -8 UDP\n\t\t *  -24 GCM Cipher\n\t\t *  -13 DTLS record header\n\t\t *   -4 TURN ChannelData\n\t\t * = 1191 bytes.\n\t\t *\n\t\t * @remarks\n\t\t * Value copied from dcSCTP library.\n\t\t */\n\t\tconstexpr size_t MaxSafeMtuSizeForSctp{ 1191u };\n\t} // namespace Consts\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Consumer.hpp",
    "content": "#ifndef MS_RTC_CONSUMER_HPP\n#define MS_RTC_CONSUMER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"FBS/consumer.h\"\n#include \"FBS/transport.h\"\n#include \"RTC/ConsumerTypes.hpp\"\n#include \"RTC/RTCP/CompoundPacket.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include \"RTC/RTP/RtpStreamSend.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <absl/container/flat_hash_set.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Consumer : public Channel::ChannelSocket::RequestHandler\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) = 0;\n\t\t\tvirtual void OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) = 0;\n\t\t\tvirtual void OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) = 0;\n\t\t\tvirtual void OnConsumerNeedBitrateChange(RTC::Consumer* consumer)                      = 0;\n\t\t\tvirtual void OnConsumerNeedZeroBitrate(RTC::Consumer* consumer)                        = 0;\n\t\t\tvirtual void OnConsumerProducerClosed(RTC::Consumer* consumer)                         = 0;\n\t\t};\n\n\tprivate:\n\t\tstruct TraceEventTypes\n\t\t{\n\t\t\tbool rtp{ false };\n\t\t\tbool keyframe{ false };\n\t\t\tbool nack{ false };\n\t\t\tbool pli{ false };\n\t\t\tbool fir{ false };\n\t\t};\n\n\tpublic:\n\t\tConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& producerId,\n\t\t  RTC::Consumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeRequest* data,\n\t\t  RTC::RtpParameters::Type type);\n\t\t~Consumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Consumer::BaseConsumerDump> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvirtual flatbuffers::Offset<FBS::Consumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) = 0;\n\t\tvirtual flatbuffers::Offset<FBS::Consumer::ConsumerScore> FillBufferScore(\n\t\t  flatbuffers::FlatBufferBuilder& /*builder*/) const\n\t\t{\n\t\t\treturn 0;\n\t\t};\n\t\tRTC::Media::Kind GetKind() const\n\t\t{\n\t\t\treturn this->kind;\n\t\t}\n\t\tconst RTC::RtpParameters& GetRtpParameters() const\n\t\t{\n\t\t\treturn this->rtpParameters;\n\t\t}\n\t\tconst struct RTC::RTP::HeaderExtensionIds& GetRtpHeaderExtensionIds() const\n\t\t{\n\t\t\treturn this->rtpHeaderExtensionIds;\n\t\t}\n\t\tRTC::RtpParameters::Type GetType() const\n\t\t{\n\t\t\treturn this->type;\n\t\t}\n\t\tvirtual RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const\n\t\t{\n\t\t\t// By default return 1:1.\n\t\t\tRTC::ConsumerTypes::VideoLayers layers;\n\n\t\t\treturn layers;\n\t\t}\n\t\tconst std::vector<uint32_t>& GetMediaSsrcs() const\n\t\t{\n\t\t\treturn this->mediaSsrcs;\n\t\t}\n\t\tconst std::vector<uint32_t>& GetRtxSsrcs() const\n\t\t{\n\t\t\treturn this->rtxSsrcs;\n\t\t}\n\t\tvirtual bool IsActive() const\n\t\t{\n\t\t\t// The parent Consumer just checks whether Consumer and Producer are\n\t\t\t// not paused and the transport connected.\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\tthis->transportConnected &&\n\t\t\t\t!this->paused &&\n\t\t\t\t!this->producerPaused &&\n\t\t\t\t!this->producerClosed\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tbool IsPaused() const\n\t\t{\n\t\t\treturn this->paused;\n\t\t}\n\t\tbool IsProducerPaused() const\n\t\t{\n\t\t\treturn this->producerPaused;\n\t\t}\n\t\tvoid ProducerPaused();\n\t\tvoid ProducerResumed();\n\t\tvirtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc)    = 0;\n\t\tvirtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0;\n\t\tvoid ProducerRtpStreamScores(const std::vector<uint8_t>* scores);\n\t\tvirtual void ProducerRtpStreamScore(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore)           = 0;\n\t\tvirtual void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0;\n\t\tvoid ProducerClosed();\n\t\tvoid SetExternallyManagedBitrate()\n\t\t{\n\t\t\tthis->externallyManagedBitrate = true;\n\t\t}\n\t\tvirtual uint8_t GetBitratePriority() const                                                 = 0;\n\t\tvirtual uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss)                        = 0;\n\t\tvirtual void ApplyLayers()                                                                 = 0;\n\t\tvirtual uint32_t GetDesiredBitrate() const                                                 = 0;\n\t\tvirtual void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) = 0;\n\t\tvirtual bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)                    = 0;\n\t\tvirtual const std::vector<RTC::RTP::RtpStreamSend*>& GetRtpStreams() const                 = 0;\n\t\tvirtual void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0;\n\t\tvirtual void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) = 0;\n\t\tvirtual void ReceiveKeyFrameRequest(\n\t\t  RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)                          = 0;\n\t\tvirtual void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)                 = 0;\n\t\tvirtual void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) = 0;\n\t\tvirtual uint32_t GetTransmissionRate(uint64_t nowMs)                                      = 0;\n\t\tvirtual float GetRtt() const                                                              = 0;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprotected:\n\t\tvoid EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx = false) const;\n\t\tvoid EmitTraceEventPliType(uint32_t ssrc) const;\n\t\tvoid EmitTraceEventFirType(uint32_t ssrc) const;\n\t\tvoid EmitTraceEventNackType() const;\n\t\tvoid EmitTraceEvent(flatbuffers::Offset<FBS::Consumer::TraceNotification>& notification) const;\n\n\tprivate:\n\t\tvirtual void UserOnTransportConnected()    = 0;\n\t\tvirtual void UserOnTransportDisconnected() = 0;\n\t\tvirtual void UserOnPaused()                = 0;\n\t\tvirtual void UserOnResumed()               = 0;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\t\tstd::string producerId;\n\n\tprotected:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\tRTC::Consumer::Listener* listener{ nullptr };\n\t\tRTC::Media::Kind kind;\n\t\tRTC::RtpParameters rtpParameters;\n\t\tRTC::RtpParameters::Type type;\n\t\tstd::vector<RTC::RtpEncodingParameters> consumableRtpEncodings;\n\t\tstruct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds;\n\t\tconst std::vector<uint8_t>* producerRtpStreamScores{ nullptr };\n\t\t// Others.\n\t\t// Whether a payload type is supported or not is represented in the\n\t\t// corresponding position of the bitset.\n\t\tstd::bitset<128u> supportedCodecPayloadTypes;\n\t\tuint64_t lastRtcpSentTime{ 0u };\n\t\tuint16_t maxRtcpInterval{ 0u };\n\t\tbool externallyManagedBitrate{ false };\n\t\tuint8_t priority{ 1u };\n\t\tstruct TraceEventTypes traceEventTypes;\n\n\tprivate:\n\t\t// Others.\n\t\tstd::vector<uint32_t> mediaSsrcs;\n\t\tstd::vector<uint32_t> rtxSsrcs;\n\t\tbool transportConnected{ false };\n\t\tbool paused{ false };\n\t\tbool producerPaused{ false };\n\t\tbool producerClosed{ false };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/ConsumerTypes.hpp",
    "content": "#ifndef MS_RTC_CONSUMER_TYPES_HPP\n#define MS_RTC_CONSUMER_TYPES_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tnamespace ConsumerTypes\n\t{\n\t\tstruct VideoLayers\n\t\t{\n\t\t\tint16_t spatial{ -1 };\n\t\t\tint16_t temporal{ -1 };\n\n\t\t\tVideoLayers() = default;\n\n\t\t\tVideoLayers(int16_t spatial, int16_t temporal) : spatial(spatial), temporal(temporal)\n\t\t\t{\n\t\t\t}\n\n\t\t\tVideoLayers(const VideoLayers& other) = default;\n\t\t\tbool operator==(const VideoLayers& other) const\n\t\t\t{\n\t\t\t\treturn spatial == other.spatial && temporal == other.temporal;\n\t\t\t}\n\n\t\t\tbool operator!=(const VideoLayers& other) const\n\t\t\t{\n\t\t\t\treturn !(*this == other);\n\t\t\t}\n\n\t\t\tvoid Reset()\n\t\t\t{\n\t\t\t\tspatial  = -1;\n\t\t\t\ttemporal = -1;\n\t\t\t}\n\t\t};\n\t} // namespace ConsumerTypes\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/DataConsumer.hpp",
    "content": "#ifndef MS_RTC_DATA_CONSUMER_HPP\n#define MS_RTC_DATA_CONSUMER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SctpDictionaries.hpp\"\n#include <absl/container/flat_hash_set.h>\n#include <string>\n\nnamespace RTC\n{\n\tclass DataConsumer : public Channel::ChannelSocket::RequestHandler\n\t{\n\tprotected:\n\t\tusing onQueuedCallback = const std::function<void(bool queued, bool sctpSendBufferFull)>;\n\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\t\tvirtual void OnDataConsumerSendMessage(\n\t\t\t  RTC::DataConsumer* dataConsumer,\n\t\t\t  const uint8_t* msg,\n\t\t\t  size_t len,\n\t\t\t  uint32_t ppid,\n\t\t\t  onQueuedCallback* cb) = 0;\n\t\t\tvirtual void OnDataConsumerSendMessage(\n\t\t\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) = 0;\n\t\t\tvirtual void OnDataConsumerNeedBufferedAmount(\n\t\t\t  RTC::DataConsumer* dataConsumer, uint32_t& bufferedAmount)                   = 0;\n\t\t\tvirtual void OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer) = 0;\n\t\t};\n\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tSCTP = 0,\n\t\t\tDIRECT\n\t\t};\n\n\tpublic:\n\t\tDataConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& dataProducerId,\n\t\t  RTC::DataConsumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeDataRequest* data,\n\t\t  size_t maxMessageSize);\n\t\t~DataConsumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::DataConsumer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::DataConsumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tType GetType() const\n\t\t{\n\t\t\treturn this->type;\n\t\t}\n\t\tconst RTC::SctpStreamParameters& GetSctpStreamParameters() const\n\t\t{\n\t\t\treturn this->sctpStreamParameters;\n\t\t}\n\t\tbool IsActive() const\n\t\t{\n\t\t\t// It's active it DataConsumer and DataProducer are not paused and the transport\n\t\t\t// is connected.\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\tthis->transportConnected &&\n\t\t\t\t(this->type == DataConsumer::Type::DIRECT || this->sctpAssociationConnected) &&\n\t\t\t\t!this->paused &&\n\t\t\t\t!this->dataProducerPaused &&\n\t\t\t\t!this->dataProducerClosed\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tbool IsPaused() const\n\t\t{\n\t\t\treturn this->paused;\n\t\t}\n\t\tbool IsDataProducerPaused() const\n\t\t{\n\t\t\treturn this->dataProducerPaused;\n\t\t}\n\t\tvoid DataProducerPaused();\n\t\tvoid DataProducerResumed();\n\t\tvoid SctpAssociationConnected();\n\t\tvoid SctpAssociationClosed();\n\t\tvoid SetSctpAssociationBufferedAmount(uint32_t bufferedAmount);\n\t\tvoid SctpAssociationSendBufferFull();\n\t\tvoid DataProducerClosed();\n\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\tbool SendMessage(\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel,\n\t\t  const onQueuedCallback* cb = nullptr);\n\t\tbool SendMessage(\n\t\t  RTC::SCTP::Message message,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel,\n\t\t  const onQueuedCallback* cb = nullptr);\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\t\tstd::string dataProducerId;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\tRTC::DataConsumer::Listener* listener{ nullptr };\n\t\tsize_t maxMessageSize{ 0u };\n\t\t// Others.\n\t\tType type;\n\t\tRTC::SctpStreamParameters sctpStreamParameters;\n\t\tstd::string label;\n\t\tstd::string protocol;\n\t\tabsl::flat_hash_set<uint16_t> subchannels;\n\t\tbool transportConnected{ false };\n\t\tbool sctpAssociationConnected{ false };\n\t\tbool paused{ false };\n\t\tbool dataProducerPaused{ false };\n\t\tbool dataProducerClosed{ false };\n\t\tsize_t messagesSent{ 0u };\n\t\tsize_t bytesSent{ 0u };\n\t\tuint32_t bufferedAmount{ 0u };\n\t\tuint32_t bufferedAmountLowThreshold{ 0u };\n\t\tbool forceTriggerBufferedAmountLow{ false };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/DataProducer.hpp",
    "content": "#ifndef MS_RTC_DATA_PRODUCER_HPP\n#define MS_RTC_DATA_PRODUCER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SctpDictionaries.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass DataProducer : public Channel::ChannelSocket::RequestHandler,\n\t                     public Channel::ChannelSocket::NotificationHandler\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnDataProducerReceiveData(RTC::DataProducer* producer, size_t len) = 0;\n\t\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\t\tvirtual void OnDataProducerMessageReceived(\n\t\t\t  RTC::DataProducer* dataProducer,\n\t\t\t  const uint8_t* msg,\n\t\t\t  size_t len,\n\t\t\t  uint32_t ppid,\n\t\t\t  std::vector<uint16_t>& subchannels,\n\t\t\t  std::optional<uint16_t> requiredSubchannel) = 0;\n\t\t\tvirtual void OnDataProducerMessageReceived(\n\t\t\t  RTC::DataProducer* dataProducer,\n\t\t\t  RTC::SCTP::Message message,\n\t\t\t  std::vector<uint16_t>& subchannels,\n\t\t\t  std::optional<uint16_t> requiredSubchannel)                       = 0;\n\t\t\tvirtual void OnDataProducerPaused(RTC::DataProducer* dataProducer)  = 0;\n\t\t\tvirtual void OnDataProducerResumed(RTC::DataProducer* dataProducer) = 0;\n\t\t};\n\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tSCTP = 0,\n\t\t\tDIRECT\n\t\t};\n\n\tpublic:\n\t\tDataProducer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  size_t maxMessageSize,\n\t\t  RTC::DataProducer::Listener* listener,\n\t\t  const FBS::Transport::ProduceDataRequest* data);\n\t\t~DataProducer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::DataProducer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::DataProducer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tType GetType() const\n\t\t{\n\t\t\treturn this->type;\n\t\t}\n\t\tconst RTC::SctpStreamParameters& GetSctpStreamParameters() const\n\t\t{\n\t\t\treturn this->sctpStreamParameters;\n\t\t}\n\t\tbool IsPaused() const\n\t\t{\n\t\t\treturn this->paused;\n\t\t}\n\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\tvoid ReceiveMessage(\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel);\n\t\tvoid ReceiveMessage(\n\t\t  RTC::SCTP::Message message,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel);\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\tsize_t maxMessageSize{ 0u };\n\t\tRTC::DataProducer::Listener* listener{ nullptr };\n\t\t// Others.\n\t\tType type;\n\t\tRTC::SctpStreamParameters sctpStreamParameters;\n\t\tstd::string label;\n\t\tstd::string protocol;\n\t\tbool paused{ false };\n\t\tsize_t messagesReceived{ 0u };\n\t\tsize_t bytesReceived{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/DirectTransport.hpp",
    "content": "#ifndef MS_RTC_DIRECT_TRANSPORT_HPP\n#define MS_RTC_DIRECT_TRANSPORT_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"RTC/Transport.hpp\"\n\nnamespace RTC\n{\n\tclass DirectTransport : public RTC::Transport\n\t{\n\tpublic:\n\t\tDirectTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  const FBS::DirectTransport::DirectTransportOptions* options);\n\t\t~DirectTransport() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::DirectTransport::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\tflatbuffers::Offset<FBS::DirectTransport::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tprivate:\n\t\tbool IsConnected() const override;\n\t\tvoid SendRtpPacket(\n\t\t  RTC::Consumer* consumer,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  RTC::Transport::onSendCallback* cb = nullptr) override;\n\t\tvoid SendRtcpPacket(RTC::RTCP::Packet* packet) override;\n\t\tvoid SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  RTC::SCTP::Message message,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tbool SendData(const uint8_t* data, size_t len) override;\n\t\tvoid RecvStreamClosed(uint32_t ssrc) override;\n\t\tvoid SendStreamClosed(uint32_t ssrc) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/DtlsTransport.hpp",
    "content": "#ifndef MS_RTC_DTLS_TRANSPORT_HPP\n#define MS_RTC_DTLS_TRANSPORT_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"FBS/webRtcTransport.h\"\n#include \"RTC/SrtpSession.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <openssl/bio.h>\n#include <openssl/ssl.h>\n#include <openssl/x509.h>\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass DtlsTransport : public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tenum class DtlsState : uint8_t\n\t\t{\n\t\t\tNEW = 1,\n\t\t\tCONNECTING,\n\t\t\tCONNECTED,\n\t\t\tFAILED,\n\t\t\tCLOSED\n\t\t};\n\n\tpublic:\n\t\tenum class Role : uint8_t\n\t\t{\n\t\t\tAUTO = 1,\n\t\t\tCLIENT,\n\t\t\tSERVER\n\t\t};\n\n\tpublic:\n\t\tenum class FingerprintAlgorithm : uint8_t\n\t\t{\n\t\t\tSHA1 = 1,\n\t\t\tSHA224,\n\t\t\tSHA256,\n\t\t\tSHA384,\n\t\t\tSHA512\n\t\t};\n\n\tpublic:\n\t\tstruct Fingerprint\n\t\t{\n\t\t\tFingerprintAlgorithm algorithm;\n\t\t\tstd::string value;\n\t\t};\n\n\tprivate:\n\t\tstruct SrtpCryptoSuiteMapEntry\n\t\t{\n\t\t\tRTC::SrtpSession::CryptoSuite cryptoSuite;\n\t\t\tconst char* name;\n\t\t};\n\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\t// DTLS is in the process of negotiating a secure connection. Incoming\n\t\t\t// media can flow through.\n\t\t\t// NOTE: The caller MUST NOT call any method during this callback.\n\t\t\tvirtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0;\n\t\t\t// DTLS has completed negotiation of a secure connection (including DTLS-SRTP\n\t\t\t// and remote fingerprint verification). Outgoing media can now flow through.\n\t\t\t// NOTE: The caller MUST NOT call any method during this callback.\n\t\t\tvirtual void OnDtlsTransportConnected(\n\t\t\t  const RTC::DtlsTransport* dtlsTransport,\n\t\t\t  RTC::SrtpSession::CryptoSuite srtpCryptoSuite,\n\t\t\t  uint8_t* srtpLocalKey,\n\t\t\t  size_t srtpLocalKeyLen,\n\t\t\t  uint8_t* srtpRemoteKey,\n\t\t\t  size_t srtpRemoteKeyLen,\n\t\t\t  std::string& remoteCert) = 0;\n\t\t\t// The DTLS connection has been closed as the result of an error (such as a\n\t\t\t// DTLS alert or a failure to validate the remote fingerprint).\n\t\t\tvirtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0;\n\t\t\t// The DTLS connection has been closed due to receipt of a close_notify alert.\n\t\t\tvirtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0;\n\t\t\t// Need to send DTLS data to the peer.\n\t\t\tvirtual void OnDtlsTransportSendData(\n\t\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;\n\t\t\t// DTLS application data received.\n\t\t\tvirtual void OnDtlsTransportApplicationDataReceived(\n\t\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0;\n\t\t};\n\n\tpublic:\n\t\tstatic void ClassInit();\n\t\tstatic void ClassDestroy();\n\t\tstatic Role RoleFromFbs(FBS::WebRtcTransport::DtlsRole role);\n\t\tstatic FBS::WebRtcTransport::DtlsRole RoleToFbs(Role role);\n\t\tstatic FBS::WebRtcTransport::DtlsState StateToFbs(DtlsState state);\n\t\tstatic FingerprintAlgorithm AlgorithmFromFbs(FBS::WebRtcTransport::FingerprintAlgorithm algorithm);\n\t\tstatic FBS::WebRtcTransport::FingerprintAlgorithm AlgorithmToFbs(FingerprintAlgorithm algorithm);\n\t\tstatic bool IsDtls(const uint8_t* data, size_t len)\n\t\t{\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\t// Minimum DTLS record length is 13 bytes.\n\t\t\t\t(len >= 13) &&\n\t\t\t\t// @see RFC 7983.\n\t\t\t\t(data[0] > 19 && data[0] < 64)\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tstatic const std::vector<Fingerprint>& GetLocalFingerprints()\n\t\t{\n\t\t\treturn DtlsTransport::localFingerprints;\n\t\t}\n\n\tprivate:\n\t\tstatic void GenerateCertificateAndPrivateKey();\n\t\tstatic void ReadCertificateAndPrivateKeyFromFiles();\n\t\tstatic void CreateSslCtx();\n\t\tstatic void GenerateFingerprints();\n\n\tprivate:\n\t\tstatic thread_local X509* certificate;\n\t\tstatic thread_local EVP_PKEY* privateKey;\n\t\tstatic thread_local SSL_CTX* sslCtx;\n\t\tstatic thread_local uint8_t sslReadBuffer[];\n\t\tstatic const absl::flat_hash_map<std::string, Role> String2Role;\n\t\tstatic const absl::flat_hash_map<std::string, FingerprintAlgorithm> String2FingerprintAlgorithm;\n\t\tstatic const absl::flat_hash_map<FingerprintAlgorithm, std::string> FingerprintAlgorithm2String;\n\t\tstatic thread_local std::vector<Fingerprint> localFingerprints;\n\t\tstatic const std::vector<SrtpCryptoSuiteMapEntry> SrtpCryptoSuites;\n\n\tpublic:\n\t\texplicit DtlsTransport(Listener* listener, SharedInterface* shared);\n\t\t~DtlsTransport() override;\n\n\tpublic:\n\t\tvoid Dump(int indentation = 0) const;\n\t\tvoid Run(Role localRole);\n\t\tbool SetRemoteFingerprint(const Fingerprint& fingerprint);\n\t\tvoid ProcessDtlsData(const uint8_t* data, size_t len);\n\t\tDtlsState GetState() const\n\t\t{\n\t\t\treturn this->state;\n\t\t}\n\t\tstd::optional<Role> GetLocalRole() const\n\t\t{\n\t\t\treturn this->localRole;\n\t\t}\n\t\t// Returns a boolean indicating whether the data could be sent.\n\t\tbool SendApplicationData(const uint8_t* data, size_t len);\n\t\t// This method must be public since it's called within an OpenSSL callback.\n\t\tvoid SendDtlsData(const uint8_t* data, size_t len);\n\n\tprivate:\n\t\tbool IsRunning() const\n\t\t{\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase DtlsState::NEW:\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tcase DtlsState::CONNECTING:\n\t\t\t\tcase DtlsState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tcase DtlsState::FAILED:\n\t\t\t\tcase DtlsState::CLOSED:\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Make GCC 4.9 happy.\n\t\t\treturn false;\n\t\t}\n\n\t\tvoid Reset();\n\t\tbool CheckStatus(int returnCode);\n\t\tbool SetTimeout();\n\t\tbool ProcessHandshake();\n\t\tbool CheckRemoteFingerprint();\n\t\tvoid ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite);\n\t\tstd::optional<RTC::SrtpSession::CryptoSuite> GetNegotiatedSrtpCryptoSuite();\n\n\t\t/* Callbacks fired by OpenSSL events. */\n\tpublic:\n\t\tvoid OnSslInfo(int where, int ret);\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Allocated by this.\n\t\tSSL* ssl{ nullptr };\n\t\tBIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads.\n\t\tBIO* sslBioToNetwork{ nullptr };   // The BIO in which ssl writes.\n\t\tTimerHandleInterface* timer{ nullptr };\n\t\t// Others.\n\t\tDtlsState state{ DtlsState::NEW };\n\t\tstd::optional<Role> localRole;\n\t\tstd::optional<Fingerprint> remoteFingerprint;\n\t\tbool handshakeDone{ false };\n\t\tbool handshakeDoneNow{ false };\n\t\tstd::string remoteCert;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/ICE/IceCandidate.hpp",
    "content": "#ifndef MS_RTC_ICE_ICE_CANDIDATE_HPP\n#define MS_RTC_ICE_ICE_CANDIDATE_HPP\n\n#include \"common.hpp\"\n#include \"FBS/webRtcTransport.h\"\n#include \"RTC/TcpServer.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include <flatbuffers/flatbuffers.h>\n#include <string>\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\tclass IceCandidate\n\t\t{\n\t\t\tusing Protocol = TransportTuple::Protocol;\n\n\t\tpublic:\n\t\t\tenum class CandidateType : uint8_t\n\t\t\t{\n\t\t\t\tHOST = 1\n\t\t\t};\n\n\t\tpublic:\n\t\t\tenum class TcpCandidateType : uint8_t\n\t\t\t{\n\t\t\t\tPASSIVE = 1\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic CandidateType CandidateTypeFromFbs(FBS::WebRtcTransport::IceCandidateType type);\n\t\t\tstatic FBS::WebRtcTransport::IceCandidateType CandidateTypeToFbs(CandidateType type);\n\t\t\tstatic TcpCandidateType TcpCandidateTypeFromFbs(FBS::WebRtcTransport::IceCandidateTcpType type);\n\t\t\tstatic FBS::WebRtcTransport::IceCandidateTcpType TcpCandidateTypeToFbs(TcpCandidateType type);\n\n\t\tpublic:\n\t\t\tIceCandidate(RTC::UdpSocket* udpSocket, uint32_t priority)\n\t\t\t  : foundation(\"udpcandidate\"),\n\t\t\t    priority(priority),\n\t\t\t    address(udpSocket->GetLocalIp()),\n\t\t\t    protocol(Protocol::UDP),\n\t\t\t    port(udpSocket->GetLocalPort())\n\t\t\t{\n\t\t\t}\n\n\t\t\tIceCandidate(RTC::UdpSocket* udpSocket, uint32_t priority, std::string& announcedAddress)\n\t\t\t  : foundation(\"udpcandidate\"),\n\t\t\t    priority(priority),\n\t\t\t    address(announcedAddress),\n\t\t\t    protocol(Protocol::UDP),\n\t\t\t    port(udpSocket->GetLocalPort())\n\t\t\t{\n\t\t\t}\n\n\t\t\tIceCandidate(RTC::TcpServer* tcpServer, uint32_t priority)\n\t\t\t  : foundation(\"tcpcandidate\"),\n\t\t\t    priority(priority),\n\t\t\t    address(tcpServer->GetLocalIp()),\n\t\t\t    protocol(Protocol::TCP),\n\t\t\t    port(tcpServer->GetLocalPort())\n\n\t\t\t{\n\t\t\t}\n\n\t\t\tIceCandidate(RTC::TcpServer* tcpServer, uint32_t priority, std::string& announcedAddress)\n\t\t\t  : foundation(\"tcpcandidate\"),\n\t\t\t    priority(priority),\n\t\t\t    address(announcedAddress),\n\t\t\t    protocol(Protocol::TCP),\n\t\t\t    port(tcpServer->GetLocalPort())\n\n\t\t\t{\n\t\t\t}\n\n\t\t\tflatbuffers::Offset<FBS::WebRtcTransport::IceCandidate> FillBuffer(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\tprivate:\n\t\t\t// Others.\n\t\t\tstd::string foundation;\n\t\t\tuint32_t priority{ 0u };\n\t\t\tstd::string address;\n\t\t\tProtocol protocol;\n\t\t\tuint16_t port{ 0u };\n\t\t\tCandidateType type{ CandidateType::HOST };\n\t\t\tTcpCandidateType tcpType{ TcpCandidateType::PASSIVE };\n\t\t};\n\t} // namespace ICE\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/ICE/IceServer.hpp",
    "content": "#ifndef MS_RTC_ICE_ICE_SERVER_HPP\n#define MS_RTC_ICE_ICE_SERVER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"FBS/webRtcTransport.h\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <list>\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\tclass IceServer : public TimerHandleInterface::Listener\n\t\t{\n\t\tpublic:\n\t\t\tenum class IceState : uint8_t\n\t\t\t{\n\t\t\t\tNEW = 1,\n\t\t\t\tCONNECTED,\n\t\t\t\tCOMPLETED,\n\t\t\t\tDISCONNECTED,\n\t\t\t};\n\n\t\tpublic:\n\t\t\tclass Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~Listener() = default;\n\n\t\t\tpublic:\n\t\t\t\t/**\n\t\t\t\t * These callbacks are guaranteed to be called before ProcessStunPacket()\n\t\t\t\t * returns, so the given pointers are still usable.\n\t\t\t\t */\n\t\t\t\tvirtual void OnIceServerSendStunPacket(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer,\n\t\t\t\t  const RTC::ICE::StunPacket* packet,\n\t\t\t\t  RTC::TransportTuple* tuple) = 0;\n\t\t\t\tvirtual void OnIceServerLocalUsernameFragmentAdded(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) = 0;\n\t\t\t\tvirtual void OnIceServerLocalUsernameFragmentRemoved(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) = 0;\n\t\t\t\tvirtual void OnIceServerTupleAdded(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;\n\t\t\t\tvirtual void OnIceServerTupleRemoved(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) = 0;\n\t\t\t\tvirtual void OnIceServerSelectedTuple(\n\t\t\t\t  const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple)        = 0;\n\t\t\t\tvirtual void OnIceServerConnected(const RTC::ICE::IceServer* iceServer)    = 0;\n\t\t\t\tvirtual void OnIceServerCompleted(const RTC::ICE::IceServer* iceServer)    = 0;\n\t\t\t\tvirtual void OnIceServerDisconnected(const RTC::ICE::IceServer* iceServer) = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const std::string& IceStateToString(IceState iceState);\n\t\t\tstatic FBS::WebRtcTransport::IceState IceStateToFbs(IceState state);\n\n\t\tprivate:\n\t\t\tstatic std::unordered_map<IceState, std::string> iceStateToString;\n\n\t\tpublic:\n\t\t\tIceServer(\n\t\t\t  Listener* listener,\n\t\t\t  SharedInterface* shared,\n\t\t\t  const std::string& usernameFragment,\n\t\t\t  const std::string& password,\n\t\t\t  uint8_t consentTimeoutSec);\n\t\t\t~IceServer() override;\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tvoid ProcessStunPacket(const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple);\n\t\t\tconst std::string& GetUsernameFragment() const\n\t\t\t{\n\t\t\t\treturn this->usernameFragment;\n\t\t\t}\n\t\t\tconst std::string& GetPassword() const\n\t\t\t{\n\t\t\t\treturn this->password;\n\t\t\t}\n\t\t\tIceState GetState() const\n\t\t\t{\n\t\t\t\treturn this->state;\n\t\t\t}\n\t\t\tRTC::TransportTuple* GetSelectedTuple() const\n\t\t\t{\n\t\t\t\treturn this->selectedTuple;\n\t\t\t}\n\t\t\tvoid RestartIce(const std::string& usernameFragment, const std::string& password);\n\t\t\tbool IsValidTuple(const RTC::TransportTuple* tuple) const;\n\t\t\tvoid RemoveTuple(RTC::TransportTuple* tuple);\n\t\t\t/**\n\t\t\t * This should be just called in 'connected' or 'completed' state and the\n\t\t\t * given tuple must be an already valid tuple.\n\t\t\t */\n\t\t\tvoid MayForceSelectedTuple(const RTC::TransportTuple* tuple);\n\n\t\tprivate:\n\t\t\tvoid ProcessStunRequest(const RTC::ICE::StunPacket* request, RTC::TransportTuple* tuple);\n\t\t\tvoid ProcessStunIndication(const RTC::ICE::StunPacket* indication);\n\t\t\tvoid ProcessStunResponse(const RTC::ICE::StunPacket* response);\n\t\t\tvoid HandleTuple(\n\t\t\t  RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination);\n\t\t\t/**\n\t\t\t * Store the given tuple and return its stored address.\n\t\t\t */\n\t\t\tRTC::TransportTuple* AddTuple(RTC::TransportTuple* tuple);\n\t\t\t/**\n\t\t\t * If the given tuple exists return its stored address, nullptr otherwise.\n\t\t\t */\n\t\t\tRTC::TransportTuple* HasTuple(const RTC::TransportTuple* tuple) const;\n\t\t\t/**\n\t\t\t * Set the given tuple as the selected tuple.\n\t\t\t * NOTE: The given tuple MUST be already stored within the list.\n\t\t\t */\n\t\t\tvoid SetSelectedTuple(RTC::TransportTuple* storedTuple);\n\t\t\tbool IsConsentCheckSupported() const\n\t\t\t{\n\t\t\t\treturn this->consentTimeoutMs != 0u;\n\t\t\t}\n\t\t\tbool IsConsentCheckRunning() const\n\t\t\t{\n\t\t\t\treturn (this->consentCheckTimer && this->consentCheckTimer->IsActive());\n\t\t\t}\n\t\t\tvoid StartConsentCheck();\n\t\t\tvoid RestartConsentCheck();\n\t\t\tvoid StopConsentCheck();\n\n\t\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\t\tpublic:\n\t\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\t\tprivate:\n\t\t\t// Passed by argument.\n\t\t\tListener* listener{ nullptr };\n\t\t\tSharedInterface* shared{ nullptr };\n\t\t\tstd::string usernameFragment;\n\t\t\tstd::string password;\n\t\t\tuint16_t consentTimeoutMs{ 30000u };\n\t\t\t// Others.\n\t\t\tstd::string oldUsernameFragment;\n\t\t\tstd::string oldPassword;\n\t\t\tIceState state{ IceState::NEW };\n\t\t\tuint32_t remoteNomination{ 0u };\n\t\t\tstd::list<RTC::TransportTuple> tuples;\n\t\t\tRTC::TransportTuple* selectedTuple{ nullptr };\n\t\t\tTimerHandleInterface* consentCheckTimer{ nullptr };\n\t\t\tbool isRemovingTuples{ false };\n\t\t};\n\t} // namespace ICE\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/ICE/StunPacket.hpp",
    "content": "#ifndef MS_RTC_ICE_STUN_PACKET_HPP\n#define MS_RTC_ICE_STUN_PACKET_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/Serializable.hpp\"\n#include <string_view>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\t/**\n\t\t * STUN Packet.\n\t\t *\n\t\t * @see RFC 5389.\n\t\t * @see RFC 8445.\n\t\t */\n\t\tclass StunPacket : public Serializable\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * STUN Message Header.\n\t\t\t *\n\t\t\t * @see RFC 5389 section 6.\n\t\t\t *\n\t\t\t *  0                   1                   2                   3\n\t\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |0 0|     STUN Message Type     |         Message Length        |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |                         Magic Cookie                          |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |                                                               |\n\t\t\t * |                     Transaction ID (96 bits)                  |\n\t\t\t * |                                                               |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t */\n\n\t\t\t/**\n\t\t\t * The STUN Message Type field is decomposed further into the following\n\t\t\t * structure:\n\t\t\t *\n\t\t\t *  0                 1\n\t\t\t *  2  3  4 5 6 7 8 9 0 1 2 3 4 5\n\t\t\t * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |M |M |M|M|M|C|M|M|M|C|M|M|M|M|\n\t\t\t * |11|10|9|8|7|1|6|5|4|0|3|2|1|0|\n\t\t\t * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t *\n\t\t\t * Here the bits in the message type field are shown as most significant\n\t\t\t * (M11) through least significant (M0).  M11 through M0 represent a\n\t\t\t * 12-bit encoding of the method.  C1 and C0 represent a 2-bit encoding\n\t\t\t * of the class.\n\t\t\t */\n\n\t\t\t/**\n\t\t\t * STUN Attributes.\n\t\t\t *\n\t\t\t * After the STUN Message Header are zero or more Attributes. Each\n\t\t\t * Attribute MUST be TLV encoded, with a 16-bit type, 16-bit length, and\n\t\t\t * value. Each STUN Attribute MUST end on a 32-bit boundary.  All fields\n\t\t\t * in an Attribute are transmitted most significant bit first.\n\t\t\t *\n\t\t\t *  0                   1                   2                   3\n\t\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |         Type                  |            Length             |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |                         Value (variable)                ....\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t */\n\n\t\t\t/**\n\t\t\t * STUN message class.\n\t\t\t */\n\t\t\tenum class Class : uint8_t\n\t\t\t{\n\t\t\t\tREQUEST          = 0,\n\t\t\t\tINDICATION       = 1,\n\t\t\t\tSUCCESS_RESPONSE = 2,\n\t\t\t\tERROR_RESPONSE   = 3,\n\t\t\t\tUNSET            = 255\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * STUN message method.\n\t\t\t */\n\t\t\tenum class Method : uint16_t\n\t\t\t{\n\t\t\t\tBINDING = 1,\n\t\t\t\tUNSET   = 255\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * STUN Attribute type.\n\t\t\t */\n\t\t\tenum class AttributeType : uint16_t\n\t\t\t{\n\t\t\t\tMAPPED_ADDRESS     = 0x0001,\n\t\t\t\tUSERNAME           = 0x0006,\n\t\t\t\tMESSAGE_INTEGRITY  = 0x0008,\n\t\t\t\tERROR_CODE         = 0x0009,\n\t\t\t\tUNKNOWN_ATTRIBUTES = 0x000A,\n\t\t\t\tREALM              = 0x0014,\n\t\t\t\tNONCE              = 0x0015,\n\t\t\t\tXOR_MAPPED_ADDRESS = 0x0020,\n\t\t\t\tPRIORITY           = 0x0024,\n\t\t\t\tUSE_CANDIDATE      = 0x0025,\n\t\t\t\tSOFTWARE           = 0x8022,\n\t\t\t\tALTERNATE_SERVER   = 0x8023,\n\t\t\t\tFINGERPRINT        = 0x8028,\n\t\t\t\tICE_CONTROLLED     = 0x8029,\n\t\t\t\tICE_CONTROLLING    = 0x802A,\n\t\t\t\tNOMINATION         = 0xC001\n\t\t\t};\n\n\t\t\t// Authentication result.\n\t\t\tenum class AuthenticationResult : uint8_t\n\t\t\t{\n\t\t\t\tOK           = 0,\n\t\t\t\tUNAUTHORIZED = 1,\n\t\t\t\tBAD_MESSAGE  = 2\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is NOT guaranteed to be aligned to any fixed number of\n\t\t\t *   bytes because it contains a `size_t`, which is 4 or 8 bytes depending\n\t\t\t *   on the architecture. Anyway we never cast any buffer to this struct.\n\t\t\t */\n\t\t\tstruct Attribute\n\t\t\t{\n\t\t\t\tAttribute(AttributeType type, uint16_t len, size_t offset)\n\t\t\t\t  : type(type), len(len), offset(offset) {};\n\n\t\t\t\t/**\n\t\t\t\t * Attribute type.\n\t\t\t\t */\n\t\t\t\tAttributeType type;\n\t\t\t\t/**\n\t\t\t\t * Length of the value (not padded).\n\t\t\t\t */\n\t\t\t\tuint16_t len;\n\t\t\t\t/**\n\t\t\t\t * Offset of the Attribute from the start of the Attributes.\n\t\t\t\t */\n\t\t\t\tsize_t offset;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t FixedHeaderLength{ 20 };\n\t\t\tstatic const size_t TransactionIdLength{ 12 };\n\t\t\tstatic const size_t UsernameAttributeMaxLength{ 513 };\n\t\t\tstatic const size_t XorMappedAddressIPv4Length{ 8 };\n\t\t\tstatic const size_t XorMappedAddressIPv6Length{ 20 };\n\t\t\tstatic const size_t SoftwareAttributeMaxLength{ 763 };\n\t\t\tstatic const size_t MessageIntegrityAttributeLength{ 20 };\n\t\t\tstatic const size_t FingerprintAttributeLength{ 4 };\n\n\t\t\t/**\n\t\t\t * Whether given buffer could be a valid STUN Packet.\n\t\t\t */\n\t\t\tstatic bool IsStun(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Parse a STUN Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the STUN Packet.\n\t\t\t */\n\t\t\tstatic StunPacket* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a STUN Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the STUN Packet.\n\t\t\t * - If `transactionId` is not given then a random Transaction ID is\n\t\t\t *   generated.\n\t\t\t */\n\t\t\tstatic StunPacket* Factory(\n\t\t\t  uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  StunPacket::Class klass,\n\t\t\t  StunPacket::Method method,\n\t\t\t  const uint8_t* transactionId);\n\n\t\t\tstatic StunPacket* Factory(\n\t\t\t  uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method);\n\n\t\tprivate:\n\t\t\tstatic const uint8_t MagicCookie[];\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Constructor is private because we only want to create STUN Packet\n\t\t\t * instances via Parse() and Factory().\n\t\t\t */\n\t\t\tStunPacket(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~StunPacket() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tStunPacket* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tStunPacket::Class GetClass() const\n\t\t\t{\n\t\t\t\treturn this->klass;\n\t\t\t}\n\n\t\t\tStunPacket::Method GetMethod() const\n\t\t\t{\n\t\t\t\treturn this->method;\n\t\t\t}\n\n\t\t\tconst uint8_t* GetTransactionId() const\n\t\t\t{\n\t\t\t\treturn GetTransactionIdPointer();\n\t\t\t}\n\n\t\t\tvoid SetTransactionId(const uint8_t* transactionId);\n\n\t\t\tbool HasAttribute(StunPacket::AttributeType type) const\n\t\t\t{\n\t\t\t\treturn this->attributes.find(type) != this->attributes.end();\n\t\t\t}\n\n\t\t\tconst std::string_view GetUsername() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::USERNAME);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\n\t\t\t\treturn std::string_view(\n\t\t\t\t  reinterpret_cast<const char*>(GetAttributeValue(attribute)), attribute->len);\n\t\t\t}\n\n\t\t\tvoid AddUsername(const std::string_view username);\n\n\t\t\tuint32_t GetPriority() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::PRIORITY);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0);\n\t\t\t}\n\n\t\t\tvoid AddPriority(uint32_t priority);\n\n\t\t\tuint64_t GetIceControlling() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::ICE_CONTROLLING);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get8Bytes(GetAttributeValue(attribute), 0);\n\t\t\t}\n\n\t\t\tvoid AddIceControlling(uint64_t iceControlling);\n\n\t\t\tuint64_t GetIceControlled() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::ICE_CONTROLLED);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get8Bytes(GetAttributeValue(attribute), 0);\n\t\t\t}\n\n\t\t\tvoid AddIceControlled(uint64_t iceControlled);\n\n\t\t\tvoid AddUseCandidate();\n\n\t\t\tuint32_t GetNomination() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::NOMINATION);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0);\n\t\t\t}\n\n\t\t\tvoid AddNomination(uint32_t nomination);\n\n\t\t\tconst std::string_view GetSoftware() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::SOFTWARE);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\n\t\t\t\treturn std::string_view(\n\t\t\t\t  reinterpret_cast<const char*>(GetAttributeValue(attribute)), attribute->len);\n\t\t\t}\n\n\t\t\tvoid AddSoftware(const std::string_view software);\n\n\t\t\tbool GetXorMappedAddress(struct sockaddr_storage* xorMappedAddressStorage) const;\n\n\t\t\tvoid AddXorMappedAddress(const struct sockaddr* xorMappedAddress);\n\n\t\t\tuint16_t GetErrorCode(std::string_view& reasonPhrase) const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::ERROR_CODE);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tconst auto* attributeValue = GetAttributeValue(attribute);\n\n\t\t\t\tconst uint8_t errorClass  = Utils::Byte::Get1Byte(attributeValue, 2) & 0b00000111;\n\t\t\t\tconst uint8_t errorNumber = Utils::Byte::Get1Byte(attributeValue, 3);\n\t\t\t\tconst auto errorCode      = static_cast<uint16_t>((errorClass * 100) + errorNumber);\n\n\t\t\t\t// Reason Phrase comes after the first 4 bytes (it could be zero\n\t\t\t\t// length).\n\t\t\t\tif (attribute->len > 4)\n\t\t\t\t{\n\t\t\t\t\treasonPhrase = std::string_view(\n\t\t\t\t\t  reinterpret_cast<const char*>(GetAttributeValue(attribute) + 4), attribute->len - 4);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treasonPhrase = {};\n\t\t\t\t}\n\n\t\t\t\treturn errorCode;\n\t\t\t}\n\n\t\t\tvoid AddErrorCode(uint16_t errorCode, const std::string_view reasonPhrase);\n\n\t\t\t/**\n\t\t\t * Check authentication of the STUN request or notification.\n\t\t\t */\n\t\t\tStunPacket::AuthenticationResult CheckAuthentication(\n\t\t\t  const std::string_view usernameFragment1, const std::string_view& password) const;\n\n\t\t\t/**\n\t\t\t * Check authentication of the STUN success response or error response.\n\t\t\t */\n\t\t\tStunPacket::AuthenticationResult CheckAuthentication(std::string_view password) const;\n\n\t\t\t/**\n\t\t\t * Whether the STUN Packet is protected, meaning that it has\n\t\t\t * MESSAGE-INTEGRITY and/or FINGERPRINT Attributes.\n\t\t\t */\n\t\t\tbool IsProtected() const\n\t\t\t{\n\t\t\t\treturn (\n\t\t\t\t  HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY) ||\n\t\t\t\t  HasAttribute(StunPacket::AttributeType::FINGERPRINT));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Adds MESSAGE-INTEGRITY and FINGERPRINT to the STUN Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - MESSAGE-INTEGRITY is only added if given `password` is not empty.\n\t\t\t * - The application MUST NOT add more Attributes into the STUN Packet\n\t\t\t *   and MUST NOT modify any field of the STUN Packet after calling\n\t\t\t *   this method.\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If there is no enough space in the buffer.\n\t\t\t * @throw MediaSoupError - If the STUN Packet already has MESSAGE-INTEGRITY\n\t\t\t *   or FINGERPRINT Attributes.\n\t\t\t */\n\t\t\tvoid Protect(const std::string_view password);\n\n\t\t\tvoid Protect();\n\n\t\t\t/**\n\t\t\t * Creates a STUN success response for the current STUN request.\n\t\t\t *\n\t\t\t * @throw MediaSoupError - If the STUN Packet is not a STUN request.\n\t\t\t */\n\t\t\tStunPacket* CreateSuccessResponse(uint8_t* buffer, size_t bufferLength) const;\n\n\t\t\t/**\n\t\t\t * Creates a STUN error response for the current STUN request. It uses\n\t\t\t * given `errorCode` and `reasonPhrase` to add a ERROR-CODE Attribute.\n\t\t\t *\n\t\t\t * @throw MediaSoupError - If the STUN Packet is not a STUN request.\n\t\t\t */\n\t\t\tStunPacket* CreateErrorResponse(\n\t\t\t  uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  uint16_t errorCode,\n\t\t\t  const std::string_view& reasonPhrase) const;\n\n\t\tprivate:\n\t\t\tuint8_t* GetFixedHeaderPointer() const\n\t\t\t{\n\t\t\t\treturn const_cast<uint8_t*>(GetBuffer());\n\t\t\t}\n\n\t\t\tuint16_t GetMessageLength() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetFixedHeaderPointer(), 2);\n\t\t\t}\n\n\t\t\tvoid SetMessageLength(uint16_t msgLength)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set2Bytes(GetFixedHeaderPointer(), 2, msgLength);\n\t\t\t}\n\n\t\t\tuint8_t* GetTransactionIdPointer() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer() + 8;\n\t\t\t}\n\n\t\t\tuint8_t* GetAttributesPointer() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer() + StunPacket::FixedHeaderLength;\n\t\t\t}\n\n\t\t\tsize_t GetAttributesLength() const\n\t\t\t{\n\t\t\t\treturn GetLength() - StunPacket::FixedHeaderLength;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Validates whether the STUN Packet is valid. It also stores internal\n\t\t\t * offsets pointing to relevant STUN Attributes if `storeAttributes` is\n\t\t\t * `true`.\n\t\t\t */\n#ifdef MS_TEST\n\t\tpublic:\n#endif\n\t\t\tbool Validate(bool storeAttributes);\n#ifdef MS_TEST\n\t\tprivate:\n#endif\n\n\t\t\t/**\n\t\t\t * Parses Attributes. Returns `true` if they are valid. It also stores\n\t\t\t * internal containers holding Attributes if `storeAttributes` is `true`.\n\t\t\t */\n\t\t\tbool ParseAttributes(bool storeAttributes);\n\n\t\t\t/**\n\t\t\t * Stores the parsed Attribute data into the map of Attributes.\n\t\t\t *\n\t\t\t * @return `true` if the Attribute was stored and `false` if it couldn't\n\t\t\t *   be stored because there was already an Attribute with same type in\n\t\t\t *   the map.\n\t\t\t */\n\t\t\tbool StoreParsedAttribute(StunPacket::AttributeType type, uint16_t len, size_t offset);\n\n\t\t\t/**\n\t\t\t * Stores a new Attribute data into the map of Attributes.\n\t\t\t *\n\t\t\t * @throw MediaSoupError - If there is not enough space in the buffer for\n\t\t\t *   the new Attribute or if the Attribute couldn't be stored because\n\t\t\t *   there was already an Attribute with same type in the map.\n\t\t\t */\n\t\t\tvoid StoreNewAttribute(StunPacket::AttributeType type, const void* data, uint16_t len);\n\n\t\t\tconst StunPacket::Attribute* GetAttribute(StunPacket::AttributeType type) const\n\t\t\t{\n\t\t\t\tconst auto it = this->attributes.find(type);\n\n\t\t\t\tif (it != this->attributes.end())\n\t\t\t\t{\n\t\t\t\t\treturn std::addressof(it->second);\n\t\t\t\t}\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tconst uint8_t* GetAttributeValue(const StunPacket::Attribute* attribute) const\n\t\t\t{\n\t\t\t\treturn GetAttributesPointer() + attribute->offset + 4;\n\t\t\t}\n\n#ifdef MS_TEST\n\t\tpublic:\n#endif\n\t\t\tconst uint8_t* GetMessageIntegrity() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn GetAttributeValue(attribute);\n\t\t\t}\n\n\t\t\tuint32_t GetFingerprint() const\n\t\t\t{\n\t\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::FINGERPRINT);\n\n\t\t\t\tif (!attribute)\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0);\n\t\t\t}\n#ifdef MS_TEST\n\t\tprivate:\n#endif\n\n\t\t\tvoid AssertNotProtected() const;\n\n\t\tprivate:\n\t\t\tStunPacket::Class klass{ StunPacket::Class::UNSET };\n\t\t\tStunPacket::Method method{ StunPacket::Method::UNSET };\n\t\t\t// Map of STUN Attributes indexed by Attribute type.\n\t\t\tstd::unordered_map<StunPacket::AttributeType, StunPacket::Attribute> attributes;\n\t\t};\n\t} // namespace ICE\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/KeyFrameRequestManager.hpp",
    "content": "#ifndef MS_KEY_FRAME_REQUEST_MANAGER_HPP\n#define MS_KEY_FRAME_REQUEST_MANAGER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nnamespace RTC\n{\n\tclass PendingKeyFrameInfo : public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnKeyFrameRequestTimeout(PendingKeyFrameInfo* keyFrameRequestInfo) = 0;\n\t\t};\n\n\tpublic:\n\t\tPendingKeyFrameInfo(Listener* listener, SharedInterface* shared, uint32_t ssrc);\n\t\t~PendingKeyFrameInfo() override;\n\n\t\tuint32_t GetSsrc() const\n\t\t{\n\t\t\treturn this->ssrc;\n\t\t}\n\t\tvoid SetRetryOnTimeout(bool notify)\n\t\t{\n\t\t\tthis->retryOnTimeout = notify;\n\t\t}\n\t\tbool GetRetryOnTimeout() const\n\t\t{\n\t\t\treturn this->retryOnTimeout;\n\t\t}\n\t\tvoid Restart()\n\t\t{\n\t\t\tthis->timer->Restart();\n\t\t}\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\tListener* listener{ nullptr };\n\t\tuint32_t ssrc;\n\t\tTimerHandleInterface* timer{ nullptr };\n\t\tbool retryOnTimeout{ true };\n\t};\n\n\tclass KeyFrameRequestDelayer : public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer) = 0;\n\t\t};\n\n\tpublic:\n\t\tKeyFrameRequestDelayer(Listener* listener, SharedInterface* shared, uint32_t ssrc, uint32_t delay);\n\t\t~KeyFrameRequestDelayer() override;\n\n\t\tuint32_t GetSsrc() const\n\t\t{\n\t\t\treturn this->ssrc;\n\t\t}\n\t\tbool GetKeyFrameRequested() const\n\t\t{\n\t\t\treturn this->keyFrameRequested;\n\t\t}\n\t\tvoid SetKeyFrameRequested(bool flag)\n\t\t{\n\t\t\tthis->keyFrameRequested = flag;\n\t\t}\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\tListener* listener{ nullptr };\n\t\tuint32_t ssrc;\n\t\tTimerHandleInterface* timer{ nullptr };\n\t\tbool keyFrameRequested{ false };\n\t};\n\n\tclass KeyFrameRequestManager : public PendingKeyFrameInfo::Listener,\n\t                               public KeyFrameRequestDelayer::Listener\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnKeyFrameNeeded(KeyFrameRequestManager* keyFrameRequestManager, uint32_t ssrc) = 0;\n\t\t};\n\n\tpublic:\n\t\texplicit KeyFrameRequestManager(\n\t\t  Listener* listener, SharedInterface* shared, uint32_t keyFrameRequestDelay);\n\t\t~KeyFrameRequestManager() override;\n\n\t\tvoid KeyFrameNeeded(uint32_t ssrc);\n\t\tvoid ForceKeyFrameNeeded(uint32_t ssrc);\n\t\tvoid KeyFrameReceived(uint32_t ssrc);\n\n\t\t/* Pure virtual methods inherited from PendingKeyFrameInfo::Listener. */\n\tpublic:\n\t\tvoid OnKeyFrameRequestTimeout(PendingKeyFrameInfo* pendingKeyFrameInfo) override;\n\n\t\t/* Pure virtual methods inherited from PendingKeyFrameInfo::Listener. */\n\tpublic:\n\t\tvoid OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer) override;\n\n\tprivate:\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\tuint32_t keyFrameRequestDelay{ 0u }; // 0 means disabled.\n\t\tabsl::flat_hash_map<uint32_t, PendingKeyFrameInfo*> mapSsrcPendingKeyFrameInfo;\n\t\tabsl::flat_hash_map<uint32_t, KeyFrameRequestDelayer*> mapSsrcKeyFrameRequestDelayer;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/NackGenerator.hpp",
    "content": "#ifndef MS_RTC_NACK_GENERATOR_HPP\n#define MS_RTC_NACK_GENERATOR_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <map>\n#include <set>\n#include <vector>\n\nnamespace RTC\n{\n\tclass NackGenerator : public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnNackGeneratorNackRequired(const std::vector<uint16_t>& seqNumbers) = 0;\n\t\t\tvirtual void OnNackGeneratorKeyFrameRequired()                                    = 0;\n\t\t};\n\n\tprivate:\n\t\tstruct NackInfo\n\t\t{\n\t\t\tNackInfo() = default;\n\t\t\texplicit NackInfo(uint64_t createdAtMs, uint16_t seq, uint16_t sendAtSeq)\n\t\t\t  : createdAtMs(createdAtMs), seq(seq), sendAtSeq(sendAtSeq)\n\t\t\t{\n\t\t\t}\n\n\t\t\tuint64_t createdAtMs{ 0u };\n\t\t\tuint16_t seq{ 0u };\n\t\t\tuint16_t sendAtSeq{ 0u };\n\t\t\tuint64_t sentAtMs{ 0u };\n\t\t\tuint8_t retries{ 0u };\n\t\t};\n\n\t\tenum class NackFilter : uint8_t\n\t\t{\n\t\t\tSEQ,\n\t\t\tTIME\n\t\t};\n\n\tpublic:\n\t\texplicit NackGenerator(Listener* listener, SharedInterface* shared, unsigned int sendNackDelayMs);\n\t\t~NackGenerator() override;\n\n\t\tbool ReceivePacket(const RTC::RTP::Packet* packet, bool isRecovered);\n\t\tsize_t GetNackListLength() const\n\t\t{\n\t\t\treturn this->nackList.size();\n\t\t}\n\t\tvoid UpdateRtt(uint32_t rtt)\n\t\t{\n\t\t\tthis->rtt = rtt;\n\t\t}\n\t\tvoid Reset();\n\n\tprivate:\n\t\tvoid AddPacketsToNackList(uint16_t seqStart, uint16_t seqEnd);\n\t\tbool RemoveNackItemsUntilKeyFrame();\n\t\tstd::vector<uint16_t> GetNackBatch(NackFilter filter);\n\t\tvoid MayRunTimer() const;\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\tunsigned int sendNackDelayMs{ 0u };\n\t\t// Allocated by this.\n\t\tTimerHandleInterface* timer{ nullptr };\n\t\t// Others.\n\t\tstd::map<uint16_t, NackInfo, RTC::SeqManager<uint16_t>::SeqLowerThan> nackList;\n\t\tstd::set<uint16_t, RTC::SeqManager<uint16_t>::SeqLowerThan> keyFrameList;\n\t\tstd::set<uint16_t, RTC::SeqManager<uint16_t>::SeqLowerThan> recoveredList;\n\t\tbool started{ false };\n\t\tuint16_t lastSeq{ 0u }; // Seq number of last valid packet.\n\t\tuint32_t rtt{ 0u };     // Round trip time (ms).\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Parameters.hpp",
    "content": "#ifndef MS_RTC_PARAMETERS_HPP\n#define MS_RTC_PARAMETERS_HPP\n\n#include \"common.hpp\"\n#include \"FBS/rtpParameters.h\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Parameters\n\t{\n\tpublic:\n\t\tclass Value\n\t\t{\n\t\tpublic:\n\t\t\tenum class Type : uint8_t\n\t\t\t{\n\t\t\t\tBOOLEAN = 1,\n\t\t\t\tINTEGER,\n\t\t\t\tDOUBLE,\n\t\t\t\tSTRING,\n\t\t\t\tARRAY_OF_INTEGERS\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit Value(bool booleanValue) : type(Type::BOOLEAN), booleanValue(booleanValue)\n\t\t\t{\n\t\t\t}\n\n\t\t\texplicit Value(int32_t integerValue) : type(Type::INTEGER), integerValue(integerValue)\n\t\t\t{\n\t\t\t}\n\n\t\t\texplicit Value(double doubleValue) : type(Type::DOUBLE), doubleValue(doubleValue)\n\t\t\t{\n\t\t\t}\n\n\t\t\texplicit Value(std::string& stringValue) : type(Type::STRING), stringValue(stringValue)\n\t\t\t{\n\t\t\t}\n\n\t\t\texplicit Value(std::string&& stringValue)\n\t\t\t  : type(Type::STRING), stringValue(std::move(stringValue))\n\t\t\t{\n\t\t\t}\n\n\t\t\texplicit Value(std::vector<int32_t>& arrayOfIntegers)\n\t\t\t  : type(Type::ARRAY_OF_INTEGERS), arrayOfIntegers(arrayOfIntegers)\n\t\t\t{\n\t\t\t}\n\n\t\tpublic:\n\t\t\tType type;\n\t\t\tbool booleanValue{ false };\n\t\t\tint32_t integerValue{ 0 };\n\t\t\tdouble doubleValue{ 0.0 };\n\t\t\tstd::string stringValue;\n\t\t\tstd::vector<int32_t> arrayOfIntegers;\n\t\t};\n\n\tpublic:\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::Parameter>> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid Set(const flatbuffers::Vector<flatbuffers::Offset<FBS::RtpParameters::Parameter>>* data);\n\t\tbool HasBoolean(const std::string& key) const;\n\t\tbool HasInteger(const std::string& key) const;\n\t\tbool HasPositiveInteger(const std::string& key) const;\n\t\tbool HasDouble(const std::string& key) const;\n\t\tbool HasString(const std::string& key) const;\n\t\tbool HasArrayOfIntegers(const std::string& key) const;\n\t\tbool IncludesInteger(const std::string& key, int32_t integer) const;\n\t\tbool GetBoolean(const std::string& key) const;\n\t\tint32_t GetInteger(const std::string& key) const;\n\t\tdouble GetDouble(const std::string& key) const;\n\t\tconst std::string& GetString(const std::string& key) const;\n\t\tconst std::vector<int32_t>& GetArrayOfIntegers(const std::string& key) const;\n\n\tprivate:\n\t\tabsl::flat_hash_map<std::string, Value> mapKeyValues;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/PipeConsumer.hpp",
    "content": "#ifndef MS_RTC_PIPECONSUMER_HPP\n#define MS_RTC_PIPECONSUMER_HPP\n\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tclass PipeConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tprivate:\n\t\tstatic void StorePacketInTargetLayerRetransmissionBuffer(\n\t\t  std::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>&\n\t\t    targetLayerRetransmissionBuffer,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  RTC::RTP::SharedPacket& sharedPacket);\n\n\tpublic:\n\t\tPipeConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& producerId,\n\t\t  RTC::Consumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeRequest* data);\n\t\t~PipeConsumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Consumer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> FillBufferScore(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const override;\n\t\tvoid ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerRtpStreamScore(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override;\n\t\tuint8_t GetBitratePriority() const override;\n\t\tuint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override;\n\t\tvoid ApplyLayers() override;\n\t\tuint32_t GetDesiredBitrate() const override;\n\t\tvoid SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override;\n\t\tbool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override;\n\t\tconst std::vector<RTC::RTP::RtpStreamSend*>& GetRtpStreams() const override\n\t\t{\n\t\t\treturn this->rtpStreams;\n\t\t}\n\t\tvoid NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;\n\t\tvoid ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override;\n\t\tvoid ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override;\n\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override;\n\t\tvoid ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override;\n\t\tuint32_t GetTransmissionRate(uint64_t nowMs) override;\n\t\tfloat GetRtt() const override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tvoid UserOnTransportConnected() override;\n\t\tvoid UserOnTransportDisconnected() override;\n\t\tvoid UserOnPaused() override;\n\t\tvoid UserOnResumed() override;\n\t\tvoid CreateRtpStreams();\n\t\tvoid RequestKeyFrame();\n\n\t\t/* Pure virtual methods inherited from RtpStreamSend::Listener. */\n\tpublic:\n\t\tvoid OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tstd::vector<RTC::RTP::RtpStreamSend*> rtpStreams;\n\t\t// Others.\n\t\tabsl::flat_hash_map<uint32_t, uint32_t> mapMappedSsrcSsrc;\n\t\tabsl::flat_hash_map<uint32_t, RTC::RTP::RtpStreamSend*> mapSsrcRtpStream;\n\t\tbool keyFrameSupported{ false };\n\t\tabsl::flat_hash_map<RTC::RTP::RtpStreamSend*, bool> mapRtpStreamSyncRequired;\n\t\tabsl::flat_hash_map<RTC::RTP::RtpStreamSend*, RTC::SeqManager<uint16_t>> mapRtpStreamRtpSeqManager;\n\t\t// Buffers to store packets that arrive earlier than the first packet of the\n\t\t// video key frame.\n\t\tabsl::flat_hash_map<\n\t\t  RTC::RTP::RtpStreamSend*,\n\t\t  std::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>>\n\t\t  mapRtpStreamTargetLayerRetransmissionBuffer;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/PipeTransport.hpp",
    "content": "#ifndef MS_RTC_PIPE_TRANSPORT_HPP\n#define MS_RTC_PIPE_TRANSPORT_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"FBS/pipeTransport.h\"\n#include \"RTC/SrtpSession.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n\nnamespace RTC\n{\n\tclass PipeTransport : public RTC::Transport, public RTC::UdpSocket::Listener\n\t{\n\tprivate:\n\t\tstatic RTC::SrtpSession::CryptoSuite srtpCryptoSuite;\n\t\tstatic std::string srtpCryptoSuiteString;\n\t\tstatic size_t srtpMasterLength;\n\n\tpublic:\n\t\tPipeTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  const FBS::PipeTransport::PipeTransportOptions* options);\n\t\t~PipeTransport() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::PipeTransport::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\tflatbuffers::Offset<FBS::PipeTransport::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tprivate:\n\t\tbool IsConnected() const override;\n\t\tbool HasSrtp() const;\n\t\tvoid SendRtpPacket(\n\t\t  RTC::Consumer* consumer,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  RTC::Transport::onSendCallback* cb = nullptr) override;\n\t\tvoid SendRtcpPacket(RTC::RTCP::Packet* packet) override;\n\t\tvoid SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  RTC::SCTP::Message message,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tbool SendData(const uint8_t* data, size_t len) override;\n\t\tvoid RecvStreamClosed(uint32_t ssrc) override;\n\t\tvoid SendStreamClosed(uint32_t ssrc) override;\n\t\tvoid OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid OnSctpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\n\t\t/* Pure virtual methods inherited from RTC::UdpSocket::Listener. */\n\tpublic:\n\t\tvoid OnUdpSocketPacketReceived(\n\t\t  RTC::UdpSocket* socket,\n\t\t  const uint8_t* data,\n\t\t  size_t len,\n\t\t  size_t bufferLen,\n\t\t  const struct sockaddr* remoteAddr) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tRTC::UdpSocket* udpSocket{ nullptr };\n\t\tRTC::TransportTuple* tuple{ nullptr };\n\t\tRTC::SrtpSession* srtpRecvSession{ nullptr };\n\t\tRTC::SrtpSession* srtpSendSession{ nullptr };\n\t\t// Others.\n\t\tListenInfo listenInfo;\n\t\tstruct sockaddr_storage remoteAddrStorage{};\n\t\tbool rtx{ false };\n\t\tstd::string srtpKey;\n\t\tstd::string srtpKeyBase64;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/PlainTransport.hpp",
    "content": "#ifndef MS_RTC_PLAIN_TRANSPORT_HPP\n#define MS_RTC_PLAIN_TRANSPORT_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"FBS/plainTransport.h\"\n#include \"RTC/SrtpSession.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nnamespace RTC\n{\n\tclass PlainTransport : public RTC::Transport, public RTC::UdpSocket::Listener\n\t{\n\tpublic:\n\t\tPlainTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  const FBS::PlainTransport::PlainTransportOptions* options);\n\t\t~PlainTransport() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::PlainTransport::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\tflatbuffers::Offset<FBS::PlainTransport::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tprivate:\n\t\tbool IsConnected() const override;\n\t\tbool HasSrtp() const;\n\t\tbool IsSrtpReady() const;\n\t\tvoid SendRtpPacket(\n\t\t  RTC::Consumer* consumer,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  RTC::Transport::onSendCallback* cb = nullptr) override;\n\t\tvoid SendRtcpPacket(RTC::RTCP::Packet* packet) override;\n\t\tvoid SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  RTC::SCTP::Message message,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tbool SendData(const uint8_t* data, size_t len) override;\n\t\tvoid RecvStreamClosed(uint32_t ssrc) override;\n\t\tvoid SendStreamClosed(uint32_t ssrc) override;\n\t\tvoid OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid OnSctpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid EmitTuple() const;\n\t\tvoid EmitRtcpTuple() const;\n\n\t\t/* Pure virtual methods inherited from RTC::UdpSocket::Listener. */\n\tpublic:\n\t\tvoid OnUdpSocketPacketReceived(\n\t\t  RTC::UdpSocket* socket,\n\t\t  const uint8_t* data,\n\t\t  size_t len,\n\t\t  size_t bufferLen,\n\t\t  const struct sockaddr* remoteAddr) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tRTC::UdpSocket* udpSocket{ nullptr };\n\t\tRTC::UdpSocket* rtcpUdpSocket{ nullptr };\n\t\tRTC::TransportTuple* tuple{ nullptr };\n\t\tRTC::TransportTuple* rtcpTuple{ nullptr };\n\t\tRTC::SrtpSession* srtpRecvSession{ nullptr };\n\t\tRTC::SrtpSession* srtpSendSession{ nullptr };\n\t\t// Others.\n\t\tListenInfo listenInfo;\n\t\tListenInfo rtcpListenInfo;\n\t\tbool rtcpMux{ true };\n\t\tbool comedia{ false };\n\t\tstruct sockaddr_storage remoteAddrStorage{};\n\t\tstruct sockaddr_storage rtcpRemoteAddrStorage{};\n\t\tRTC::SrtpSession::CryptoSuite srtpCryptoSuite{\n\t\t\tRTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80\n\t\t};\n\t\tstd::string srtpKey;\n\t\tsize_t srtpMasterLength{ 0 };\n\t\tstd::string srtpKeyBase64;\n\t\tbool connectCalled{ false }; // Whether connect() was succesfully called.\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/PortManager.hpp",
    "content": "#ifndef MS_RTC_PORT_MANAGER_HPP\n#define MS_RTC_PORT_MANAGER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/Transport.hpp\"\n#include <uv.h>\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass PortManager\n\t{\n\tprivate:\n\t\tenum class Protocol : uint8_t\n\t\t{\n\t\t\tUDP = 1,\n\t\t\tTCP\n\t\t};\n\n\tprivate:\n\t\tstruct PortRange\n\t\t{\n\t\t\texplicit PortRange(uint16_t numPorts, uint16_t minPort)\n\t\t\t  : ports(numPorts, false), minPort(minPort)\n\t\t\t{\n\t\t\t}\n\n\t\t\tstd::vector<bool> ports;\n\t\t\tuint16_t minPort{ 0u };\n\t\t\tuint16_t numUsedPorts{ 0u };\n\t\t};\n\n\tpublic:\n\t\tstatic uv_udp_t* BindUdp(std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags)\n\t\t{\n\t\t\treturn reinterpret_cast<uv_udp_t*>(Bind(Protocol::UDP, ip, port, flags));\n\t\t}\n\t\tstatic uv_udp_t* BindUdp(\n\t\t  std::string& ip,\n\t\t  uint16_t minPort,\n\t\t  uint16_t maxPort,\n\t\t  RTC::Transport::SocketFlags& flags,\n\t\t  uint64_t& hash)\n\t\t{\n\t\t\treturn reinterpret_cast<uv_udp_t*>(Bind(Protocol::UDP, ip, minPort, maxPort, flags, hash));\n\t\t}\n\t\tstatic uv_tcp_t* BindTcp(std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags)\n\t\t{\n\t\t\treturn reinterpret_cast<uv_tcp_t*>(Bind(Protocol::TCP, ip, port, flags));\n\t\t}\n\t\tstatic uv_tcp_t* BindTcp(\n\t\t  std::string& ip,\n\t\t  uint16_t minPort,\n\t\t  uint16_t maxPort,\n\t\t  RTC::Transport::SocketFlags& flags,\n\t\t  uint64_t& hash)\n\t\t{\n\t\t\treturn reinterpret_cast<uv_tcp_t*>(Bind(Protocol::TCP, ip, minPort, maxPort, flags, hash));\n\t\t}\n\t\tstatic void Unbind(uint64_t hash, uint16_t port);\n\t\tvoid Dump(int indentation = 0) const;\n\n\tprivate:\n\t\tstatic uv_handle_t* Bind(\n\t\t  Protocol protocol, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags);\n\t\tstatic uv_handle_t* Bind(\n\t\t  Protocol protocol,\n\t\t  std::string& ip,\n\t\t  uint16_t minPort,\n\t\t  uint16_t maxPort,\n\t\t  RTC::Transport::SocketFlags& flags,\n\t\t  uint64_t& hash);\n\t\tstatic uint64_t GeneratePortRangeHash(\n\t\t  Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort);\n\t\tstatic PortRange& GetOrCreatePortRange(uint64_t hash, uint16_t minPort, uint16_t maxPort);\n\t\tstatic uint8_t ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family);\n\n\tprivate:\n\t\tstatic thread_local absl::flat_hash_map<uint64_t, PortRange> mapPortRanges;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Producer.hpp",
    "content": "#ifndef MS_RTC_PRODUCER_HPP\n#define MS_RTC_PRODUCER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"RTC/KeyFrameRequestManager.hpp\"\n#include \"RTC/RTCP/CompoundPacket.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Producer : public RTC::RTP::RtpStreamRecv::Listener,\n\t                 public RTC::KeyFrameRequestManager::Listener,\n\t                 public Channel::ChannelSocket::RequestHandler,\n\t                 public Channel::ChannelSocket::NotificationHandler\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnProducerReceiveData(RTC::Producer* producer, size_t len) = 0;\n\t\t\tvirtual void OnProducerReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0;\n\t\t\tvirtual void OnProducerPaused(RTC::Producer* producer)  = 0;\n\t\t\tvirtual void OnProducerResumed(RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnProducerNewRtpStream(\n\t\t\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0;\n\t\t\tvirtual void OnProducerRtpStreamScore(\n\t\t\t  RTC::Producer* producer,\n\t\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t\t  uint8_t score,\n\t\t\t  uint8_t previousScore) = 0;\n\t\t\tvirtual void OnProducerRtcpSenderReport(\n\t\t\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0;\n\t\t\tvirtual void OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0;\n\t\t\tvirtual void OnProducerSendRtcpPacket(RTC::Producer* producer, RTC::RTCP::Packet* packet) = 0;\n\t\t\tvirtual void OnProducerNeedWorstRemoteFractionLost(\n\t\t\t  RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0;\n\t\t};\n\n\tprivate:\n\t\tstruct RtpEncodingMapping\n\t\t{\n\t\t\tstd::string rid;\n\t\t\tuint32_t ssrc{ 0 };\n\t\t\tuint32_t mappedSsrc{ 0 };\n\t\t};\n\n\tprivate:\n\t\tstruct RtpMapping\n\t\t{\n\t\t\tabsl::flat_hash_map<uint8_t, uint8_t> codecs;\n\t\t\tstd::vector<RtpEncodingMapping> encodings;\n\t\t};\n\n\tprivate:\n\t\tstruct VideoOrientation\n\t\t{\n\t\t\tbool camera{ false };\n\t\t\tbool flip{ false };\n\t\t\tuint16_t rotation{ 0 };\n\t\t};\n\n\tpublic:\n\t\tenum class ReceiveRtpPacketResult : uint8_t\n\t\t{\n\t\t\tDISCARDED,\n\t\t\tMEDIA,\n\t\t\tRETRANSMISSION\n\t\t};\n\n\tprivate:\n\t\tstruct TraceEventTypes\n\t\t{\n\t\t\tbool rtp{ false };\n\t\t\tbool keyframe{ false };\n\t\t\tbool nack{ false };\n\t\t\tbool pli{ false };\n\t\t\tbool fir{ false };\n\t\t\tbool sr{ false };\n\t\t};\n\n\tpublic:\n\t\tProducer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Producer::Listener* listener,\n\t\t  const FBS::Transport::ProduceRequest* data);\n\t\t~Producer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Producer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::Producer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\tRTC::Media::Kind GetKind() const\n\t\t{\n\t\t\treturn this->kind;\n\t\t}\n\t\tconst RTC::RtpParameters& GetRtpParameters() const\n\t\t{\n\t\t\treturn this->rtpParameters;\n\t\t}\n\t\tconst struct RTC::RTP::HeaderExtensionIds& GetRtpHeaderExtensionIds() const\n\t\t{\n\t\t\treturn this->rtpHeaderExtensionIds;\n\t\t}\n\t\tRTC::RtpParameters::Type GetType() const\n\t\t{\n\t\t\treturn this->type;\n\t\t}\n\t\tbool IsPaused() const\n\t\t{\n\t\t\treturn this->paused;\n\t\t}\n\t\tconst absl::flat_hash_map<RTC::RTP::RtpStreamRecv*, uint32_t>& GetRtpStreams()\n\t\t{\n\t\t\treturn this->mapRtpStreamMappedSsrc;\n\t\t}\n\t\tconst std::vector<uint8_t>* GetRtpStreamScores() const\n\t\t{\n\t\t\treturn std::addressof(this->rtpStreamScores);\n\t\t}\n\t\tReceiveRtpPacketResult ReceiveRtpPacket(RTC::RTP::Packet* packet);\n\t\tvoid ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report);\n\t\tvoid ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo);\n\t\tbool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs);\n\t\tvoid RequestKeyFrame(uint32_t mappedSsrc);\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tprivate:\n\t\tRTC::RTP::RtpStreamRecv* GetRtpStream(const RTC::RTP::Packet* packet);\n\t\tRTC::RTP::RtpStreamRecv* CreateRtpStream(\n\t\t  const RTC::RTP::Packet* packet, const RTC::RtpCodecParameters& mediaCodec, size_t encodingIdx);\n\t\tvoid NotifyNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream);\n\t\tbool MangleRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::RtpStreamRecv* rtpStream) const;\n\t\tvoid PostProcessRtpPacket(RTC::RTP::Packet* packet);\n\t\tvoid EmitScore() const;\n\t\tvoid EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx = false) const;\n\t\tvoid EmitTraceEventPliType(uint32_t ssrc) const;\n\t\tvoid EmitTraceEventFirType(uint32_t ssrc) const;\n\t\tvoid EmitTraceEventNackType() const;\n\t\tvoid EmitTraceEventSrType(RTC::RTCP::SenderReport* report) const;\n\t\tvoid EmitTraceEvent(flatbuffers::Offset<FBS::Producer::TraceNotification>& notification) const;\n\n\t\t/* Pure virtual methods inherited from RTC::RtpStreamRecv::Listener. */\n\tpublic:\n\t\tvoid OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) override;\n\t\tvoid OnRtpStreamNeedWorstRemoteFractionLost(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost) override;\n\n\t\t/* Pure virtual methods inherited from RTC::KeyFrameRequestManager::Listener. */\n\tpublic:\n\t\tvoid OnKeyFrameNeeded(RTC::KeyFrameRequestManager* keyFrameRequestManager, uint32_t ssrc) override;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\tRTC::Producer::Listener* listener{ nullptr };\n\t\t// Allocated by this.\n\t\tabsl::flat_hash_map<uint32_t, RTC::RTP::RtpStreamRecv*> mapSsrcRtpStream;\n\t\tRTC::KeyFrameRequestManager* keyFrameRequestManager{ nullptr };\n\t\t// Others.\n\t\tRTC::Media::Kind kind;\n\t\tRTC::RtpParameters rtpParameters;\n\t\tRTC::RtpParameters::Type type;\n\t\tstruct RtpMapping rtpMapping;\n\t\tstd::vector<RTC::RTP::RtpStreamRecv*> rtpStreamByEncodingIdx;\n\t\tstd::vector<uint8_t> rtpStreamScores;\n\t\tabsl::flat_hash_map<uint32_t, RTC::RTP::RtpStreamRecv*> mapRtxSsrcRtpStream;\n\t\tabsl::flat_hash_map<RTC::RTP::RtpStreamRecv*, uint32_t> mapRtpStreamMappedSsrc;\n\t\tabsl::flat_hash_map<uint32_t, uint32_t> mapMappedSsrcSsrc;\n\t\tstruct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds;\n\t\tbool paused{ false };\n\t\tbool enableMediasoupPacketIdHeaderExtension{ false };\n\t\tRTC::RTP::Packet* currentRtpPacket{ nullptr };\n\t\t// Timestamp when last RTCP was sent.\n\t\tuint64_t lastRtcpSentTime{ 0u };\n\t\tuint16_t maxRtcpInterval{ 0u };\n\t\t// Video orientation.\n\t\tbool videoOrientationDetected{ false };\n\t\tstruct VideoOrientation videoOrientation;\n\t\tstruct TraceEventTypes traceEventTypes;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/Bye.hpp",
    "content": "#ifndef MS_RTC_RTCP_BYE_HPP\n#define MS_RTC_RTCP_BYE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass ByePacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = std::vector<uint32_t>::iterator;\n\n\t\tpublic:\n\t\t\tstatic ByePacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\tByePacket() : Packet(Type::BYE)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit ByePacket(CommonHeader* commonHeader) : Packet(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\t~ByePacket() override = default;\n\n\t\t\tvoid AddSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrcs.push_back(ssrc);\n\t\t\t}\n\t\t\tvoid SetReason(const std::string& reason)\n\t\t\t{\n\t\t\t\tthis->reason = reason;\n\t\t\t}\n\t\t\tconst std::string& GetReason() const\n\t\t\t{\n\t\t\t\treturn this->reason;\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->ssrcs.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->ssrcs.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn this->ssrcs.size();\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size = Packet::CommonHeaderSize;\n\n\t\t\t\tsize += ssrcs.size() * 4u;\n\n\t\t\t\tif (!this->reason.empty())\n\t\t\t\t{\n\t\t\t\t\tsize += 1u; // Length field.\n\t\t\t\t\tsize += this->reason.length();\n\t\t\t\t}\n\n\t\t\t\t// http://stackoverflow.com/questions/11642210/computing-padding-required-for-n-byte-alignment\n\t\t\t\t// Consider pading to 32 bits (4 bytes) boundary.\n\t\t\t\treturn (size + 3) & ~3;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<uint32_t> ssrcs;\n\t\t\tstd::string reason;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/CompoundPacket.hpp",
    "content": "#ifndef MS_RTC_RTCP_COMPOUND_PACKET_HPP\n#define MS_RTC_RTCP_COMPOUND_PACKET_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTCP/Sdes.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass CompoundPacket\n\t\t{\n\t\tpublic:\n\t\t\tCompoundPacket() = default;\n\n\t\tpublic:\n\t\t\tconst uint8_t* GetData() const\n\t\t\t{\n\t\t\t\treturn this->header;\n\t\t\t}\n\t\t\tsize_t GetSize();\n\t\t\tsize_t GetSenderReportCount() const\n\t\t\t{\n\t\t\t\treturn this->senderReportPacket.GetCount();\n\t\t\t}\n\t\t\tsize_t GetReceiverReportCount() const\n\t\t\t{\n\t\t\t\treturn this->receiverReportPacket.GetCount();\n\t\t\t}\n\t\t\tvoid Dump(int indentation = 0);\n\t\t\t// RTCP additions per Consumer (non pipe).\n\t\t\t// Adds the given data and returns true if there is enough space to hold it,\n\t\t\t// false otherwise.\n\t\t\tbool Add(\n\t\t\t  SenderReport* senderReport,\n\t\t\t  SdesChunk* sdesChunk,\n\t\t\t  DelaySinceLastRr::SsrcInfo* delaySinceLastRrSsrcInfo);\n\t\t\t// RTCP additions per Consumer (pipe).\n\t\t\t// Adds the given data and returns true if there is enough space to hold it,\n\t\t\t// false otherwise.\n\t\t\tbool Add(\n\t\t\t  std::vector<SenderReport*>& senderReports,\n\t\t\t  std::vector<SdesChunk*>& sdesChunks,\n\t\t\t  std::vector<DelaySinceLastRr::SsrcInfo*>& delaySinceLastRrSsrcInfos);\n\t\t\t// RTCP additions per Producer.\n\t\t\t// Adds the given data and returns true if there is enough space to hold it,\n\t\t\t// false otherwise.\n\t\t\tbool Add(std::vector<ReceiverReport*>&, ReceiverReferenceTime*);\n\t\t\tvoid AddSenderReport(SenderReport* report);\n\t\t\tvoid AddReceiverReport(ReceiverReport* report);\n\t\t\tvoid AddSdesChunk(SdesChunk* chunk);\n\t\t\tbool HasSenderReport()\n\t\t\t{\n\t\t\t\treturn this->senderReportPacket.Begin() != this->senderReportPacket.End();\n\t\t\t}\n\t\t\tbool HasReceiverReferenceTime()\n\t\t\t{\n\t\t\t\treturn std::any_of(\n\t\t\t\t  this->xrPacket.Begin(),\n\t\t\t\t  this->xrPacket.End(),\n\t\t\t\t  [](const ExtendedReportBlock* report)\n\t\t\t\t  {\n\t\t\t\t\t  return report->GetType() == ExtendedReportBlock::Type::RRT;\n\t\t\t\t  });\n\t\t\t}\n\t\t\tbool HasDelaySinceLastRr()\n\t\t\t{\n\t\t\t\treturn std::any_of(\n\t\t\t\t  this->xrPacket.Begin(),\n\t\t\t\t  this->xrPacket.End(),\n\t\t\t\t  [](const ExtendedReportBlock* report)\n\t\t\t\t  {\n\t\t\t\t\t  return report->GetType() == ExtendedReportBlock::Type::DLRR;\n\t\t\t\t  });\n\t\t\t}\n\t\t\tvoid Serialize(uint8_t* data);\n\n\t\tprivate:\n\t\t\tuint8_t* header{ nullptr };\n\t\t\tSenderReportPacket senderReportPacket;\n\t\t\tReceiverReportPacket receiverReportPacket;\n\t\t\tSdesPacket sdesPacket;\n\t\t\tExtendedReportPacket xrPacket;\n\t\t\tDelaySinceLastRr* delaySinceLastRr{ nullptr };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/Feedback.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_HPP\n#define MS_RTC_RTCP_FEEDBACK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename T>\n\t\tclass FeedbackPacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for RTP Feedback message.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t senderSsrc;\n\t\t\t\tuint32_t mediaSsrc;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 8 };\n\t\t\tstatic RTCP::Type rtcpType;\n\t\t\tstatic FeedbackPacket<T>* Parse(const uint8_t* data, size_t len);\n\t\t\tstatic const std::string& MessageTypeToString(typename T::MessageType type);\n\n\t\tprivate:\n\t\t\tstatic const absl::flat_hash_map<typename T::MessageType, std::string> MessageType2String;\n\n\t\tpublic:\n\t\t\ttypename T::MessageType GetMessageType() const\n\t\t\t{\n\t\t\t\treturn this->messageType;\n\t\t\t}\n\t\t\tuint32_t GetSenderSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->senderSsrc);\n\t\t\t}\n\t\t\tvoid SetSenderSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->header->senderSsrc = htonl(ssrc);\n\t\t\t}\n\t\t\tuint32_t GetMediaSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->mediaSsrc);\n\t\t\t}\n\t\t\tvoid SetMediaSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->header->mediaSsrc = htonl(ssrc);\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn static_cast<size_t>(GetMessageType());\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn Packet::CommonHeaderSize + HeaderSize;\n\t\t\t}\n\n\t\tpublic:\n\t\t\texplicit FeedbackPacket(CommonHeader* commonHeader);\n\t\t\tFeedbackPacket(typename T::MessageType messageType, uint32_t senderSsrc, uint32_t mediaSsrc);\n\t\t\t~FeedbackPacket() override;\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t\tuint8_t* raw{ nullptr };\n\t\t\ttypename T::MessageType messageType;\n\t\t};\n\n\t\tclass FeedbackPs\n\t\t{\n\t\tpublic:\n\t\t\tenum class MessageType : uint8_t\n\t\t\t{\n\t\t\t\tPLI   = 1,\n\t\t\t\tSLI   = 2,\n\t\t\t\tRPSI  = 3,\n\t\t\t\tFIR   = 4,\n\t\t\t\tTSTR  = 5,\n\t\t\t\tTSTN  = 6,\n\t\t\t\tVBCM  = 7,\n\t\t\t\tPSLEI = 8,\n\t\t\t\tROI   = 9,\n\t\t\t\tAFB   = 15,\n\t\t\t\tEXT   = 31\n\t\t\t};\n\t\t};\n\n\t\tclass FeedbackRtp\n\t\t{\n\t\tpublic:\n\t\t\tenum class MessageType : uint8_t\n\t\t\t{\n\t\t\t\tNACK   = 1,\n\t\t\t\tTMMBR  = 3,\n\t\t\t\tTMMBN  = 4,\n\t\t\t\tSR_REQ = 5,\n\t\t\t\tRAMS   = 6,\n\t\t\t\tTLLEI  = 7,\n\t\t\t\tECN    = 8,\n\t\t\t\tPS     = 9,\n\t\t\t\tTCC    = 15,\n\t\t\t\tEXT    = 31\n\t\t\t};\n\t\t};\n\n\t\tusing FeedbackPsPacket  = FeedbackPacket<RTC::RTCP::FeedbackPs>;\n\t\tusing FeedbackRtpPacket = FeedbackPacket<RTC::RTCP::FeedbackRtp>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackItem.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_ITEM_HPP\n#define MS_RTC_RTCP_FEEDBACK_ITEM_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\ttemplate<typename Item>\n\t\t\tstatic Item* Parse(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\t// data size must be >= header.\n\t\t\t\tif (Item::HeaderSize > len)\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tauto* header =\n\t\t\t\t  const_cast<typename Item::Header*>(reinterpret_cast<const typename Item::Header*>(data));\n\n\t\t\t\treturn new Item(header);\n\t\t\t}\n\n\t\tpublic:\n\t\t\tbool IsCorrect() const\n\t\t\t{\n\t\t\t\treturn this->isCorrect;\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvirtual ~FeedbackItem()\n\t\t\t{\n\t\t\t\tdelete[] this->raw;\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvirtual void Dump(int indentation = 0) const = 0;\n\t\t\tvirtual void Serialize()\n\t\t\t{\n\t\t\t\tdelete[] this->raw;\n\n\t\t\t\tthis->raw = new uint8_t[this->GetSize()];\n\t\t\t\tthis->Serialize(this->raw);\n\t\t\t}\n\t\t\tvirtual size_t Serialize(uint8_t* buffer) = 0;\n\t\t\tvirtual size_t GetSize() const            = 0;\n\n\t\tprotected:\n\t\t\tuint8_t* raw{ nullptr };\n\t\t\tbool isCorrect{ true };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPs.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n#include \"RTC/RTCP/FeedbackItem.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename Item>\n\t\tclass FeedbackPsItemsPacket : public FeedbackPsPacket\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = typename std::vector<Item*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic FeedbackPsItemsPacket<Item>* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit FeedbackPsItemsPacket(CommonHeader* commonHeader) : FeedbackPsPacket(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsItemsPacket(uint32_t senderSsrc, uint32_t mediaSsrc = 0)\n\t\t\t  : FeedbackPsPacket(Item::MessageType, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t}\n\t\t\t~FeedbackPsItemsPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tdelete item;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid AddItem(Item* item)\n\t\t\t{\n\t\t\t\tthis->items.push_back(item);\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->items.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->items.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size = FeedbackPsPacket::GetSize();\n\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tsize += item->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<Item*> items;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsAfb.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_AFB_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_AFB_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsAfbPacket : public FeedbackPsPacket\n\t\t{\n\t\tpublic:\n\t\t\tenum class Application : uint8_t\n\t\t\t{\n\t\t\t\tUNKNOWN = 0,\n\t\t\t\tREMB    = 1\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic FeedbackPsAfbPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit FeedbackPsAfbPacket(\n\t\t\t  CommonHeader* commonHeader, Application application = Application::UNKNOWN)\n\t\t\t  : FeedbackPsPacket(commonHeader)\n\t\t\t{\n\t\t\t\tthis->size = ((static_cast<size_t>(ntohs(commonHeader->length)) + 1) * 4) -\n\t\t\t\t             (Packet::CommonHeaderSize + FeedbackPacket::HeaderSize);\n\n\t\t\t\tthis->data = reinterpret_cast<uint8_t*>(commonHeader) + Packet::CommonHeaderSize +\n\t\t\t\t             FeedbackPacket::HeaderSize;\n\n\t\t\t\tthis->application = application;\n\t\t\t}\n\t\t\tFeedbackPsAfbPacket(\n\t\t\t  uint32_t senderSsrc, uint32_t mediaSsrc, Application application = Application::UNKNOWN)\n\t\t\t  : FeedbackPsPacket(FeedbackPs::MessageType::AFB, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t\tthis->application = application;\n\t\t\t}\n\t\t\t~FeedbackPsAfbPacket() override = default;\n\n\t\t\tApplication GetApplication() const\n\t\t\t{\n\t\t\t\treturn this->application;\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsPacket::GetSize() + this->size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tApplication application{ Application::UNKNOWN };\n\t\t\tuint8_t* data{ nullptr };\n\t\t\tsize_t size{ 0 };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsFir.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_FIR_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_FIR_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 5104\n * Full Intra Request (FIR)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                               SSRC                            |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | Seq nr.       |\n                  | Reserved                                      |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsFirItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint8_t sequenceNumber;\n#ifdef _WIN32\n\t\t\t\tuint8_t reserved[3]; // Alignment.\n#else\n\t\t\t\tuint32_t reserved : 24;\n#endif\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize = 8;\n\t\t\tstatic const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::FIR };\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsFirItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsFirItem(FeedbackPsFirItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsFirItem(uint32_t ssrc, uint8_t sequenceNumber);\n\t\t\t~FeedbackPsFirItem() override = default;\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\t\t\tuint8_t GetSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->header->sequenceNumber;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsFirItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Fir packet declaration.\n\t\tusing FeedbackPsFirPacket = FeedbackPsItemsPacket<FeedbackPsFirItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsLei.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_LEI_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_LEI_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 6642\n * Payload-Specific Third-Party Loss Early Indication (PSLEI)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                              SSRC                             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsLeiItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 4 };\n\t\t\tstatic const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::PSLEI };\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsLeiItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsLeiItem(FeedbackPsLeiItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsLeiItem(uint32_t ssrc);\n\t\t\t~FeedbackPsLeiItem() override = default;\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsLeiItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Lei packet declaration.\n\t\tusing FeedbackPsLeiPacket = FeedbackPsItemsPacket<FeedbackPsLeiItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsPli.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_PLI_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_PLI_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsPliPacket : public FeedbackPsPacket\n\t\t{\n\t\tpublic:\n\t\t\tstatic FeedbackPsPliPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit FeedbackPsPliPacket(CommonHeader* commonHeader) : FeedbackPsPacket(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsPliPacket(uint32_t senderSsrc, uint32_t mediaSsrc)\n\t\t\t  : FeedbackPsPacket(FeedbackPs::MessageType::PLI, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t}\n\t\t\t~FeedbackPsPliPacket() override = default;\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsRemb.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_REMB_HPP\n#define MS_RTC_RTCP_FEEDBACK_REMB_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n#include <vector>\n\n/* draft-alvestrand-rmcat-remb-03\n * RTCP message for Receiver Estimated Maximum Bitrate (REMB)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |V=2|P| FMT=15  |   PT=206      |             length            |\n  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n  |                  SSRC of packet sender                        |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                  SSRC of media source                         |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |  Unique identifier 'R' 'E' 'M' 'B'                            |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |  Num SSRC     | BR Exp    |  BR Mantissa                      |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |   SSRC feedback                                               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |  ...                                                          |\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsRembPacket : public FeedbackPsAfbPacket\n\t\t{\n\t\tpublic:\n\t\t\t// 'R' 'E' 'M' 'B'.\n\t\t\tstatic const uint32_t UniqueIdentifier{ 0x52454D42 };\n\t\t\tstatic const size_t UniqueIdentifierSize{ 4 };\n\n\t\tpublic:\n\t\t\tstatic FeedbackPsRembPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\tFeedbackPsRembPacket(uint32_t senderSsrc, uint32_t mediaSsrc)\n\t\t\t  : FeedbackPsAfbPacket(senderSsrc, mediaSsrc, FeedbackPsAfbPacket::Application::REMB)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsRembPacket(CommonHeader* commonHeader, size_t availableLen);\n\t\t\t~FeedbackPsRembPacket() override = default;\n\n\t\t\tbool IsCorrect() const\n\t\t\t{\n\t\t\t\treturn this->isCorrect;\n\t\t\t}\n\t\t\tvoid SetBitrate(uint64_t bitrate)\n\t\t\t{\n\t\t\t\tthis->bitrate = bitrate;\n\t\t\t}\n\t\t\tvoid SetSsrcs(const std::vector<uint32_t>& ssrcs)\n\t\t\t{\n\t\t\t\tthis->ssrcs = ssrcs;\n\t\t\t}\n\t\t\tuint64_t GetBitrate() const\n\t\t\t{\n\t\t\t\treturn this->bitrate;\n\t\t\t}\n\t\t\tconst std::vector<uint32_t>& GetSsrcs()\n\t\t\t{\n\t\t\t\treturn this->ssrcs;\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\t// NOLINTNEXTLINE(bugprone-parent-virtual-call)\n\t\t\t\treturn FeedbackPsPacket::GetSize() + 8 + (4u * this->ssrcs.size());\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<uint32_t> ssrcs;\n\t\t\t// Bitrate represented in bps.\n\t\t\tuint64_t bitrate{ 0 };\n\t\t\tbool isCorrect{ true };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsRpsi.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_RPSI_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_RPSI_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 4585\n * Reference Picture Selection Indication (RPSI)\n *\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |      PB       |0| Payload Type|    Native RPSI bit string     |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |   defined per codec          ...                | Padding (0) |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsRpsiItem : public FeedbackItem\n\t\t{\n\t\t\tstatic const size_t MaxBitStringSize{ 6 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint8_t paddingBits;\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t payloadType : 7;\n\t\t\t\tuint8_t zero : 1;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t zero : 1;\n\t\t\t\tuint8_t payloadType : 7;\n#endif\n\t\t\t\tuint8_t bitString[MaxBitStringSize];\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize = 8;\n\t\t\tstatic const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::RPSI };\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsRpsiItem(Header* header);\n\t\t\texplicit FeedbackPsRpsiItem(FeedbackPsRpsiItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsRpsiItem(uint8_t payloadType, uint8_t* bitString, size_t length);\n\t\t\t~FeedbackPsRpsiItem() override = default;\n\n\t\t\tbool IsCorrect() const\n\t\t\t{\n\t\t\t\treturn this->isCorrect;\n\t\t\t}\n\t\t\tuint8_t GetPayloadType() const\n\t\t\t{\n\t\t\t\treturn this->header->payloadType;\n\t\t\t}\n\t\t\tuint8_t* GetBitString() const\n\t\t\t{\n\t\t\t\treturn this->header->bitString;\n\t\t\t}\n\t\t\tsize_t GetLength() const\n\t\t\t{\n\t\t\t\treturn this->length;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsRpsiItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t\tsize_t length{ 0 };\n\t\t};\n\n\t\t// Rpsi packet declaration.\n\t\tusing FeedbackPsRpsiPacket = FeedbackPsItemsPacket<FeedbackPsRpsiItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsSli.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_SLI_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_SLI_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 4585\n * Slice Loss Indication (SLI)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |            First        |        Number           | PictureID |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsSliItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t compact;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 4 };\n\t\t\tstatic const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::SLI };\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsSliItem(Header* header);\n\t\t\texplicit FeedbackPsSliItem(FeedbackPsSliItem* item);\n\t\t\tFeedbackPsSliItem(uint16_t first, uint16_t number, uint8_t pictureId);\n\t\t\t~FeedbackPsSliItem() override = default;\n\n\t\t\tuint16_t GetFirst() const\n\t\t\t{\n\t\t\t\treturn this->first;\n\t\t\t}\n\t\t\tvoid SetFirst(uint16_t first)\n\t\t\t{\n\t\t\t\tthis->first = first;\n\t\t\t}\n\t\t\tuint16_t GetNumber() const\n\t\t\t{\n\t\t\t\treturn this->number;\n\t\t\t}\n\t\t\tvoid SetNumber(uint16_t number)\n\t\t\t{\n\t\t\t\tthis->number = number;\n\t\t\t}\n\t\t\tuint8_t GetPictureId() const\n\t\t\t{\n\t\t\t\treturn this->pictureId;\n\t\t\t}\n\t\t\tvoid SetPictureId(uint8_t pictureId)\n\t\t\t{\n\t\t\t\tthis->pictureId = pictureId;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsSliItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t\tuint16_t first{ 0 };\n\t\t\tuint16_t number{ 0 };\n\t\t\tuint8_t pictureId{ 0 };\n\t\t};\n\n\t\t// Sli packet declaration.\n\t\tusing FeedbackPsSliPacket = FeedbackPsItemsPacket<FeedbackPsSliItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsTst.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_TST_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_TST_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 5104\n * Temporal-Spatial Trade-off Request (TSTR)\n * Temporal-Spatial Trade-off Notification (TSTN)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                              SSRC                             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |  Seq nr.      |  Reserved                           | Index   |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename T>\n\t\tclass FeedbackPsTstItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n#ifdef _WIN32\n#pragma pack(push, 1)\n#define MEDIASOUP_PACKED\n#else\n#define MEDIASOUP_PACKED __attribute__((packed))\n#endif\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte due to the usage\n\t\t\t *   of `pack()` and `packed` macros.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint8_t sequenceNumber;\n\t\t\t\tuint16_t reserved1;\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t index : 5;\n\t\t\t\tuint8_t reserved2 : 3;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t reserved2 : 3;\n\t\t\t\tuint8_t index : 5;\n#endif\n\t\t\t} MEDIASOUP_PACKED;\n#ifdef _WIN32\n#pragma pack(pop)\n#endif\n#undef MEDIASOUP_PACKED\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 8 };\n\t\t\tstatic const FeedbackPs::MessageType MessageType;\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsTstItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsTstItem(FeedbackPsTstItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsTstItem(uint32_t ssrc, uint8_t sequenceNumber, uint8_t index);\n\t\t\t~FeedbackPsTstItem() override = default;\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\t\t\tuint8_t GetSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->header->sequenceNumber;\n\t\t\t}\n\t\t\tuint8_t GetIndex() const\n\t\t\t{\n\t\t\t\treturn this->header->index;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackPsTstItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\tclass Tstr\n\t\t{\n\t\t};\n\n\t\tclass Tstn\n\t\t{\n\t\t};\n\n\t\t// Tst classes declaration.\n\t\tusing FeedbackPsTstrItem = FeedbackPsTstItem<Tstr>;\n\t\tusing FeedbackPsTstnItem = FeedbackPsTstItem<Tstn>;\n\n\t\t// Tst packets declaration.\n\t\tusing FeedbackPsTstrPacket = FeedbackPsItemsPacket<FeedbackPsTstrItem>;\n\t\tusing FeedbackPsTstnPacket = FeedbackPsItemsPacket<FeedbackPsTstnItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackPsVbcm.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_PS_VBCM_HPP\n#define MS_RTC_RTCP_FEEDBACK_PS_VBCM_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n\n/* RFC 5104\n * H.271 Video Back Channel Message (VBCM)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                              SSRC                             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | Seq nr.       |\n                  |0| Payload Vbcm|\n                                  | Length                        |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                    VBCM Octet String....      |    Padding    |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackPsVbcmItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint8_t sequenceNumber;\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t payloadType : 7;\n\t\t\t\tuint8_t zero : 1;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t zero : 1;\n\t\t\t\tuint8_t payloadType : 7;\n#endif\n\t\t\t\tuint16_t length;\n\t\t\t\tuint8_t value[];\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 8 };\n\t\t\tstatic const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::FIR };\n\n\t\tpublic:\n\t\t\texplicit FeedbackPsVbcmItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackPsVbcmItem(FeedbackPsVbcmItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackPsVbcmItem(\n\t\t\t  uint32_t ssrc, uint8_t sequenceNumber, uint8_t payloadType, uint16_t length, uint8_t* value);\n\t\t\t~FeedbackPsVbcmItem() override = default;\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\t\t\tuint8_t GetSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->header->sequenceNumber;\n\t\t\t}\n\t\t\tuint8_t GetPayloadType() const\n\t\t\t{\n\t\t\t\treturn uint8_t{ this->header->payloadType };\n\t\t\t}\n\t\t\tuint16_t GetLength() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->length);\n\t\t\t}\n\t\t\tuint8_t* GetValue() const\n\t\t\t{\n\t\t\t\treturn this->header->value;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size = FeedbackPsVbcmItem::HeaderSize + static_cast<size_t>(GetLength());\n\n\t\t\t\t// Consider pading to 32 bits (4 bytes) boundary.\n\t\t\t\treturn (size + 3) & ~3;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Vbcm packet declaration.\n\t\tusing FeedbackPsVbcmPacket = FeedbackPsItemsPacket<FeedbackPsVbcmItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtp.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n#include \"RTC/RTCP/FeedbackItem.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename Item>\n\t\tclass FeedbackRtpItemsPacket : public FeedbackRtpPacket\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = typename std::vector<Item*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic FeedbackRtpItemsPacket<Item>* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit FeedbackRtpItemsPacket(CommonHeader* commonHeader) : FeedbackRtpPacket(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackRtpItemsPacket(uint32_t senderSsrc, uint32_t mediaSsrc = 0)\n\t\t\t  : FeedbackRtpPacket(Item::MessageType, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t}\n\t\t\t~FeedbackRtpItemsPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tdelete item;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid AddItem(Item* item)\n\t\t\t{\n\t\t\t\tthis->items.push_back(item);\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->items.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->items.end();\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size = FeedbackRtpPacket::GetSize();\n\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tsize += item->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<Item*> items;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpEcn.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_ECN_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_ECN_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n\n/* RFC 6679\n * Explicit Congestion Notification (ECN) for RTP over UDP\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | Extended Highest Sequence Number                              |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | ECT (0) Counter                                               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | ECT (1) Counter                                               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | ECN-CE Counter                |\n                                  | not-ECT Counter               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | Lost Packets Counter          |\n                                  | Duplication Counter           |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackRtpEcnItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t sequenceNumber;\n\t\t\t\tuint32_t ect0Counter;\n\t\t\t\tuint32_t ect1Counter;\n\t\t\t\tuint16_t ecnCeCounter;\n\t\t\t\tuint16_t notEctCounter;\n\t\t\t\tuint16_t lostPackets;\n\t\t\t\tuint16_t duplicatedPackets;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 20 };\n\t\t\tstatic const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::ECN };\n\n\t\tpublic:\n\t\t\texplicit FeedbackRtpEcnItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackRtpEcnItem(FeedbackRtpEcnItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\t~FeedbackRtpEcnItem() override = default;\n\n\t\t\tuint32_t GetSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->sequenceNumber);\n\t\t\t}\n\t\t\tuint32_t GetEct0Counter() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ect0Counter);\n\t\t\t}\n\t\t\tuint32_t GetEct1Counter() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ect1Counter);\n\t\t\t}\n\t\t\tuint16_t GetEcnCeCounter() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->ecnCeCounter);\n\t\t\t}\n\t\t\tuint16_t GetNotEctCounter() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->notEctCounter);\n\t\t\t}\n\t\t\tuint16_t GetLostPackets() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->lostPackets);\n\t\t\t}\n\t\t\tuint16_t GetDuplicatedPackets() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->duplicatedPackets);\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackRtpEcnItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Ecn packet declaration.\n\t\tusing FeedbackRtpEcnPacket = FeedbackRtpItemsPacket<FeedbackRtpEcnItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpNack.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_NACK_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_NACK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n\n/* RFC 4585\n * Generic NACK message (NACK)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |              PID              |             BPL               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackRtpNackItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint16_t packetId;\n\t\t\t\tuint16_t lostPacketBitmask;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 4 };\n\t\t\tstatic const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::NACK };\n\n\t\tpublic:\n\t\t\texplicit FeedbackRtpNackItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackRtpNackItem(FeedbackRtpNackItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackRtpNackItem(uint16_t packetId, uint16_t lostPacketBitmask);\n\t\t\t~FeedbackRtpNackItem() override = default;\n\n\t\t\tuint16_t GetPacketId() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->packetId);\n\t\t\t}\n\t\t\tuint16_t GetLostPacketBitmask() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->lostPacketBitmask);\n\t\t\t}\n\t\t\tsize_t CountRequestedPackets() const\n\t\t\t{\n\t\t\t\treturn Utils::Bits::CountSetBits(this->header->lostPacketBitmask) + 1;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackRtpNackItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Nack packet declaration.\n\t\tusing FeedbackRtpNackPacket = FeedbackRtpItemsPacket<FeedbackRtpNackItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpSrReq.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_SR_REQ_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_SR_REQ_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackRtpSrReqPacket : public FeedbackRtpPacket\n\t\t{\n\t\tpublic:\n\t\t\tstatic FeedbackRtpSrReqPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit FeedbackRtpSrReqPacket(CommonHeader* commonHeader) : FeedbackRtpPacket(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackRtpSrReqPacket(uint32_t senderSsrc, uint32_t mediaSsrc)\n\t\t\t  : FeedbackRtpPacket(FeedbackRtp::MessageType::SR_REQ, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t}\n\t\t\t~FeedbackRtpSrReqPacket() override = default;\n\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpTllei.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_TLLEI_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_TLLEI_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n\n/* RFC 4585\n * Transport-Layer Third-Party Loss Early Indication (TLLEI)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |              PID              |             BPL               |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackRtpTlleiItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint16_t packetId;\n\t\t\t\tuint16_t lostPacketBitmask;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 4 };\n\t\t\tstatic const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::TLLEI };\n\n\t\tpublic:\n\t\t\texplicit FeedbackRtpTlleiItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit FeedbackRtpTlleiItem(FeedbackRtpTlleiItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackRtpTlleiItem(uint16_t packetId, uint16_t lostPacketBitmask);\n\t\t\t~FeedbackRtpTlleiItem() override = default;\n\n\t\t\tuint16_t GetPacketId() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->packetId);\n\t\t\t}\n\t\t\tuint16_t GetLostPacketBitmask() const\n\t\t\t{\n\t\t\t\treturn ntohs(this->header->lostPacketBitmask);\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackRtpTlleiItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t};\n\n\t\t// Tllei packet declaration.\n\t\tusing FeedbackRtpTlleiPacket = FeedbackRtpItemsPacket<FeedbackRtpTlleiItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpTmmb.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_TMMB_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_TMMB_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n\n/* RFC 5104\n * Temporary Maximum Media Stream Bit Rate Request (TMMBR)\n * Temporary Maximum Media Stream Bit Rate Notification (TMMBN)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                              SSRC                             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  | MxTBR Exp |  MxTBR Mantissa                 |Measured Overhead|\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n*/\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename T>\n\t\tclass FeedbackRtpTmmbItem : public FeedbackItem\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint32_t compact;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 8 };\n\t\t\tstatic const FeedbackRtp::MessageType MessageType;\n\n\t\tpublic:\n\t\t\tFeedbackRtpTmmbItem() = default;\n\t\t\texplicit FeedbackRtpTmmbItem(const uint8_t* data);\n\t\t\texplicit FeedbackRtpTmmbItem(const Header* header);\n\t\t\t~FeedbackRtpTmmbItem() override = default;\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->ssrc;\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrc = ssrc;\n\t\t\t}\n\t\t\tuint64_t GetBitrate() const\n\t\t\t{\n\t\t\t\treturn this->bitrate;\n\t\t\t}\n\t\t\tvoid SetBitrate(uint64_t bitrate)\n\t\t\t{\n\t\t\t\tthis->bitrate = bitrate;\n\t\t\t}\n\t\t\tuint16_t GetOverhead() const\n\t\t\t{\n\t\t\t\treturn this->overhead;\n\t\t\t}\n\t\t\tvoid SetOverhead(uint16_t overhead)\n\t\t\t{\n\t\t\t\tthis->overhead = overhead;\n\t\t\t}\n\n\t\t\t/* Virtual methods inherited from FeedbackItem. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\treturn FeedbackRtpTmmbItem::HeaderSize;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint32_t ssrc{ 0 };\n\t\t\tuint64_t bitrate{ 0 };\n\t\t\tuint16_t overhead{ 0 };\n\t\t};\n\n\t\t// Tmmb types declaration.\n\t\tclass FeedbackRtpTmmbr\n\t\t{\n\t\t};\n\t\tclass FeedbackRtpTmmbn\n\t\t{\n\t\t};\n\n\t\t// Tmmbn classes declaration.\n\t\tusing FeedbackRtpTmmbrItem = FeedbackRtpTmmbItem<FeedbackRtpTmmbr>;\n\t\tusing FeedbackRtpTmmbnItem = FeedbackRtpTmmbItem<FeedbackRtpTmmbn>;\n\n\t\t// Tmmbn packets declaration.\n\t\tusing FeedbackRtpTmmbrPacket = FeedbackRtpItemsPacket<FeedbackRtpTmmbrItem>;\n\t\tusing FeedbackRtpTmmbnPacket = FeedbackRtpItemsPacket<FeedbackRtpTmmbnItem>;\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/FeedbackRtpTransport.hpp",
    "content": "#ifndef MS_RTC_RTCP_FEEDBACK_RTP_TRANSPORT_HPP\n#define MS_RTC_RTCP_FEEDBACK_RTP_TRANSPORT_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n#include <vector>\n\n/* RTP Extensions for Transport-wide Congestion Control\n * draft-holmer-rmcat-transport-wide-cc-extensions-01\n\n   0               1               2               3\n   0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |V=2|P|  FMT=15 |    PT=205     |           length              |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                     SSRC of packet sender                     |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                      SSRC of media source                     |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |      base sequence number     |      packet status count      |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                 reference time                | fb pkt. count |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |          packet chunk         |         packet chunk          |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  .                                                               .\n  .                                                               .\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |         packet chunk          |  recv delta   |  recv delta   |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  .                                                               .\n  .                                                               .\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |           recv delta          |  recv delta   | zero padding  |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass FeedbackRtpTransportPacket : public FeedbackRtpPacket\n\t\t{\n\t\tpublic:\n\t\t\tstatic constexpr int64_t BaseTimeTick   = 64;\n\t\t\tstatic constexpr int64_t TimeWrapPeriod = BaseTimeTick * (1ll << 24);\n\n\t\tpublic:\n\t\t\tstruct PacketResult\n\t\t\t{\n\t\t\t\tPacketResult(uint16_t sequenceNumber, bool received)\n\t\t\t\t  : sequenceNumber(sequenceNumber), received(received)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tuint16_t sequenceNumber;   // Wide sequence number.\n\t\t\t\tint16_t delta{ 0 };        // Delta.\n\t\t\t\tbool received{ false };    // Packet received or not.\n\t\t\t\tint64_t receivedAtMs{ 0 }; // Received time (ms) in remote timestamp reference.\n\t\t\t};\n\n\t\tpublic:\n\t\t\tenum class AddPacketResult : uint8_t\n\t\t\t{\n\t\t\t\tSUCCESS,\n\t\t\t\tMAX_SIZE_EXCEEDED,\n\t\t\t\tFATAL\n\t\t\t};\n\n\t\tprivate:\n\t\t\tenum Status : uint8_t\n\t\t\t{\n\t\t\t\tNotReceived = 0,\n\t\t\t\tSmallDelta,\n\t\t\t\tLargeDelta,\n\t\t\t\tReserved,\n\t\t\t\tNone\n\t\t\t};\n\n\t\tprivate:\n\t\t\tstruct Context\n\t\t\t{\n\t\t\t\tbool allSameStatus{ true };\n\t\t\t\tStatus currentStatus{ Status::None };\n\t\t\t\tstd::vector<Status> statuses;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tclass Chunk\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstatic Chunk* Parse(const uint8_t* data, size_t len, uint16_t count);\n\n\t\t\tpublic:\n\t\t\t\tChunk()          = default;\n\t\t\t\tvirtual ~Chunk() = default;\n\n\t\t\t\tvirtual bool AddDeltas(\n\t\t\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset) = 0;\n\t\t\t\tvirtual void Dump(int indentation = 0) const                                     = 0;\n\t\t\t\tvirtual uint16_t GetCount() const                                                = 0;\n\t\t\t\tvirtual uint16_t GetReceivedStatusCount() const                                  = 0;\n\t\t\t\tvirtual void FillResults(\n\t\t\t\t  std::vector<struct PacketResult>& packetResults, uint16_t& currentSequenceNumber) const = 0;\n\t\t\t\tvirtual size_t Serialize(uint8_t* buffer) = 0;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tclass RunLengthChunk : public Chunk\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tRunLengthChunk(Status status, uint16_t count) : status(status), count(count)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t\texplicit RunLengthChunk(uint16_t buffer);\n\n\t\t\tpublic:\n\t\t\t\tbool AddDeltas(\n\t\t\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset) override;\n\t\t\t\tStatus GetStatus() const\n\t\t\t\t{\n\t\t\t\t\treturn this->status;\n\t\t\t\t}\n\t\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\t\tuint16_t GetCount() const override\n\t\t\t\t{\n\t\t\t\t\treturn this->count;\n\t\t\t\t}\n\t\t\t\tuint16_t GetReceivedStatusCount() const override;\n\t\t\t\tvoid FillResults(\n\t\t\t\t  std::vector<struct PacketResult>& packetResults,\n\t\t\t\t  uint16_t& currentSequenceNumber) const override;\n\t\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\n\t\t\tprivate:\n\t\t\t\tStatus status{ Status::None };\n\t\t\t\tuint16_t count{ 0u };\n\t\t\t};\n\n\t\tprivate:\n\t\t\tclass OneBitVectorChunk : public Chunk\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit OneBitVectorChunk(const std::vector<Status>& statuses) : statuses(statuses)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t\tOneBitVectorChunk(uint16_t buffer, uint16_t count);\n\n\t\t\tpublic:\n\t\t\t\tbool AddDeltas(\n\t\t\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset) override;\n\t\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\t\tuint16_t GetCount() const override\n\t\t\t\t{\n\t\t\t\t\treturn this->statuses.size();\n\t\t\t\t}\n\t\t\t\tuint16_t GetReceivedStatusCount() const override;\n\t\t\t\tvoid FillResults(\n\t\t\t\t  std::vector<struct PacketResult>& packetResults,\n\t\t\t\t  uint16_t& currentSequenceNumber) const override;\n\t\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\n\t\t\tprivate:\n\t\t\t\tstd::vector<Status> statuses;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tclass TwoBitVectorChunk : public Chunk\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit TwoBitVectorChunk(const std::vector<Status>& statuses) : statuses(statuses)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t\tTwoBitVectorChunk(uint16_t buffer, uint16_t count);\n\n\t\t\tpublic:\n\t\t\t\tbool AddDeltas(\n\t\t\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset) override;\n\t\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\t\tuint16_t GetCount() const override\n\t\t\t\t{\n\t\t\t\t\treturn this->statuses.size();\n\t\t\t\t}\n\t\t\t\tuint16_t GetReceivedStatusCount() const override;\n\t\t\t\tvoid FillResults(\n\t\t\t\t  std::vector<struct PacketResult>& packetResults,\n\t\t\t\t  uint16_t& currentSequenceNumber) const override;\n\t\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\n\t\t\tprivate:\n\t\t\t\tstd::vector<Status> statuses;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic size_t fixedHeaderSize;\n\t\t\tstatic uint16_t maxMissingPackets;\n\t\t\tstatic uint16_t maxPacketStatusCount;\n\t\t\tstatic int16_t maxPacketDelta;\n\n\t\tpublic:\n\t\t\tstatic FeedbackRtpTransportPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tprivate:\n\t\t\tstatic const absl::flat_hash_map<Status, std::string> Status2String;\n\n\t\tpublic:\n\t\t\tFeedbackRtpTransportPacket(uint32_t senderSsrc, uint32_t mediaSsrc)\n\t\t\t  : FeedbackRtpPacket(RTC::RTCP::FeedbackRtp::MessageType::TCC, senderSsrc, mediaSsrc)\n\t\t\t{\n\t\t\t}\n\t\t\tFeedbackRtpTransportPacket(CommonHeader* commonHeader, size_t availableLen);\n\t\t\t~FeedbackRtpTransportPacket() override;\n\n\t\tpublic:\n\t\t\tbool IsBaseSet() const\n\t\t\t{\n\t\t\t\treturn this->baseSet;\n\t\t\t}\n\t\t\tvoid SetBase(uint16_t sequenceNumber, uint64_t timestamp);\n\t\t\tAddPacketResult AddPacket(uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen);\n\t\t\t// Just for locally generated packets.\n\t\t\tvoid Finish();\n\t\t\tbool IsFull() const\n\t\t\t{\n\t\t\t\t// NOTE: Since AddPendingChunks() is called at the end, we cannot track\n\t\t\t\t// the exact ongoing value of packetStatusCount. Hence, let's reserve 7\n\t\t\t\t// packets just in case.\n\t\t\t\treturn this->packetStatusCount >= FeedbackRtpTransportPacket::maxPacketStatusCount - 7;\n\t\t\t}\n\t\t\tbool IsSerializable() const\n\t\t\t{\n\t\t\t\treturn !this->deltas.empty();\n\t\t\t}\n\t\t\tbool IsCorrect() const // Just for locally generated packets.\n\t\t\t{\n\t\t\t\treturn this->isCorrect;\n\t\t\t}\n\t\t\tuint16_t GetBaseSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->baseSequenceNumber;\n\t\t\t}\n\t\t\tuint16_t GetPacketStatusCount() const\n\t\t\t{\n\t\t\t\treturn this->packetStatusCount;\n\t\t\t}\n\t\t\tint32_t GetReferenceTime() const\n\t\t\t{\n\t\t\t\treturn this->referenceTime;\n\t\t\t}\n\t\t\t// NOTE: We only use this for testing purpose.\n\t\t\tvoid SetReferenceTime(int64_t referenceTime)\n\t\t\t{\n\t\t\t\tthis->referenceTime = (referenceTime % TimeWrapPeriod) / BaseTimeTick;\n\t\t\t}\n\t\t\tint64_t GetReferenceTimestamp() const // Reference time in ms.\n\t\t\t{\n\t\t\t\treturn TimeWrapPeriod + (static_cast<int64_t>(this->referenceTime) * BaseTimeTick);\n\t\t\t}\n\t\t\tint64_t GetBaseDelta(const int64_t previousTimestampMs) const\n\t\t\t{\n\t\t\t\tint64_t delta = GetReferenceTimestamp() - previousTimestampMs;\n\n\t\t\t\t// Compensate for wrap around.\n\t\t\t\tif (std::abs(delta - TimeWrapPeriod) < std::abs(delta))\n\t\t\t\t{\n\t\t\t\t\tdelta -= TimeWrapPeriod;\n\t\t\t\t}\n\t\t\t\telse if (std::abs(delta + TimeWrapPeriod) < std::abs(delta))\n\t\t\t\t{\n\t\t\t\t\tdelta += TimeWrapPeriod;\n\t\t\t\t}\n\n\t\t\t\treturn delta;\n\t\t\t}\n\t\t\tuint8_t GetFeedbackPacketCount() const\n\t\t\t{\n\t\t\t\treturn this->feedbackPacketCount;\n\t\t\t}\n\t\t\tvoid SetFeedbackPacketCount(uint8_t count)\n\t\t\t{\n\t\t\t\tthis->feedbackPacketCount = count;\n\t\t\t}\n\t\t\tuint16_t GetLatestSequenceNumber() const // Just for locally generated packets.\n\t\t\t{\n\t\t\t\treturn this->latestSequenceNumber;\n\t\t\t}\n\t\t\tuint64_t GetLatestTimestamp() const // Just for locally generated packets.\n\t\t\t{\n\t\t\t\treturn this->latestTimestamp;\n\t\t\t}\n\t\t\tstd::vector<struct PacketResult> GetPacketResults() const;\n\t\t\tuint8_t GetPacketFractionLost() const;\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tif (this->size)\n\t\t\t\t{\n\t\t\t\t\treturn this->size;\n\t\t\t\t}\n\n\t\t\t\t// Fixed packet size.\n\t\t\t\tsize_t size = FeedbackRtpPacket::GetSize();\n\n\t\t\t\tsize += FeedbackRtpTransportPacket::fixedHeaderSize;\n\t\t\t\tsize += this->deltasAndChunksSize;\n\n\t\t\t\t// 32 bits padding.\n\t\t\t\tsize += (-size) & 3;\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid FillChunk(uint16_t previousSequenceNumber, uint16_t sequenceNumber, int16_t delta);\n\t\t\tvoid CreateRunLengthChunk(Status status, uint16_t count);\n\t\t\tvoid CreateOneBitVectorChunk(std::vector<Status>& statuses);\n\t\t\tvoid CreateTwoBitVectorChunk(std::vector<Status>& statuses);\n\t\t\tvoid AddPendingChunks();\n\n\t\tprivate:\n\t\t\t// Whether baseSequenceNumber has been set.\n\t\t\tbool baseSet{ false };\n\t\t\tuint16_t baseSequenceNumber{ 0u };\n\t\t\t// 24 bits signed integer.\n\t\t\tint32_t referenceTime{ 0 };\n\t\t\t// Just for locally generated packets.\n\t\t\tuint16_t latestSequenceNumber{ 0u };\n\t\t\t// Just for locally generated packets.\n\t\t\tuint64_t latestTimestamp{ 0u };\n\t\t\tuint16_t packetStatusCount{ 0u };\n\t\t\tuint8_t feedbackPacketCount{ 0u };\n\t\t\tstd::vector<Chunk*> chunks;\n\t\t\tstd::vector<int16_t> deltas;\n\t\t\t// Just for locally generated packets.\n\t\t\tContext context;\n\t\t\tsize_t deltasAndChunksSize{ 0u };\n\t\t\tsize_t size{ 0 };\n\t\t\tbool isCorrect{ true };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/Packet.hpp",
    "content": "#ifndef MS_RTC_RTCP_PACKET_HPP\n#define MS_RTC_RTCP_PACKET_HPP\n\n#include \"common.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t// Internal buffer for RTCP serialization.\n\t\tconstexpr size_t SerializationBufferSize{ 65536 };\n\t\textern uint8_t SerializationBuffer[SerializationBufferSize];\n\n\t\t// Maximum interval for regular RTCP mode.\n\t\tconstexpr uint16_t MaxAudioIntervalMs{ 5000 };\n\t\tconstexpr uint16_t MaxVideoIntervalMs{ 1000 };\n\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tSR    = 200,\n\t\t\tRR    = 201,\n\t\t\tSDES  = 202,\n\t\t\tBYE   = 203,\n\t\t\tAPP   = 204,\n\t\t\tRTPFB = 205,\n\t\t\tPSFB  = 206,\n\t\t\tXR    = 207\n\t\t};\n\n\t\tclass Packet\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for RTCP common header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct CommonHeader\n\t\t\t{\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t count : 5;\n\t\t\t\tuint8_t padding : 1;\n\t\t\t\tuint8_t version : 2;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t version : 2;\n\t\t\t\tuint8_t padding : 1;\n\t\t\t\tuint8_t count : 5;\n#endif\n\t\t\t\tuint8_t packetType;\n\t\t\t\tuint16_t length;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t CommonHeaderSize{ 4 };\n\t\t\tstatic bool IsRtcp(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\t\t// clang-format off\n\t\t\t\treturn (\n\t\t\t\t\t(len >= CommonHeaderSize) &&\n\t\t\t\t\t// @see RFC 7983.\n\t\t\t\t\t(data[0] > 127 && data[0] < 192) &&\n\t\t\t\t\t// RTP Version must be 2.\n\t\t\t\t\t(header->version == 2) &&\n\t\t\t\t\t// RTCP packet types defined by IANA:\n\t\t\t\t\t// http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4\n\t\t\t\t\t// RFC 5761 (RTCP-mux) states this range for secure RTCP/RTP detection.\n\t\t\t\t\t(header->packetType >= 192 && header->packetType <= 223)\n\t\t\t\t);\n\t\t\t\t// clang-format on\n\t\t\t}\n\t\t\tstatic Packet* Parse(const uint8_t* data, size_t len);\n\t\t\tstatic const std::string& TypeToString(Type type);\n\n\t\tprivate:\n\t\t\tstatic const absl::flat_hash_map<Type, std::string> Type2String;\n\n\t\tpublic:\n\t\t\texplicit Packet(Type type) : type(type)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit Packet(CommonHeader* commonHeader)\n\t\t\t{\n\t\t\t\tthis->type   = RTCP::Type(commonHeader->packetType);\n\t\t\t\tthis->header = commonHeader;\n\t\t\t}\n\t\t\tvirtual ~Packet() = default;\n\n\t\t\tvoid SetNext(Packet* packet)\n\t\t\t{\n\t\t\t\tthis->next = packet;\n\t\t\t}\n\t\t\tPacket* GetNext() const\n\t\t\t{\n\t\t\t\treturn this->next;\n\t\t\t}\n\t\t\tconst uint8_t* GetData() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<uint8_t*>(this->header);\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvirtual void Dump(int indentation = 0) const = 0;\n\t\t\tvirtual size_t Serialize(uint8_t* buffer)    = 0;\n\t\t\tvirtual Type GetType() const\n\t\t\t{\n\t\t\t\treturn this->type;\n\t\t\t}\n\t\t\tvirtual size_t GetCount() const\n\t\t\t{\n\t\t\t\treturn 0u;\n\t\t\t}\n\t\t\tvirtual size_t GetSize() const = 0;\n\n\t\tprivate:\n\t\t\tType type;\n\t\t\tPacket* next{ nullptr };\n\t\t\tCommonHeader* header{ nullptr };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/ReceiverReport.hpp",
    "content": "#ifndef MS_RTC_RTCP_RECEIVER_REPORT_HPP\n#define MS_RTC_RTCP_RECEIVER_REPORT_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass ReceiverReport\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for RTCP receiver report.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint32_t fractionLost : 8;\n\t\t\t\tuint32_t totalLost : 24;\n\t\t\t\tuint32_t lastSeq;\n\t\t\t\tuint32_t jitter;\n\t\t\t\tuint32_t lsr;\n\t\t\t\tuint32_t dlsr;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 24 };\n\t\t\tstatic ReceiverReport* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Locally generated Report. Holds the data internally.\n\t\t\tReceiverReport()\n\t\t\t{\n\t\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\t\t\t}\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit ReceiverReport(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit ReceiverReport(ReceiverReport* report) : header(report->header)\n\t\t\t{\n\t\t\t}\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tsize_t Serialize(uint8_t* buffer);\n\t\t\tsize_t GetSize() const\n\t\t\t{\n\t\t\t\treturn HeaderSize;\n\t\t\t}\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->header->ssrc = htonl(ssrc);\n\t\t\t}\n\t\t\tuint8_t GetFractionLost() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get1Byte(reinterpret_cast<uint8_t*>(this->header), 4);\n\t\t\t}\n\t\t\tvoid SetFractionLost(uint8_t fractionLost)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set1Byte(reinterpret_cast<uint8_t*>(this->header), 4, fractionLost);\n\t\t\t}\n\t\t\tint32_t GetTotalLost() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get3BytesSigned(reinterpret_cast<uint8_t*>(this->header), 5);\n\t\t\t}\n\t\t\tvoid SetTotalLost(int32_t totalLost)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set3BytesSigned(reinterpret_cast<uint8_t*>(this->header), 5, totalLost);\n\t\t\t}\n\t\t\tuint32_t GetLastSeq() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->lastSeq);\n\t\t\t}\n\t\t\tvoid SetLastSeq(uint32_t lastSeq)\n\t\t\t{\n\t\t\t\tthis->header->lastSeq = htonl(lastSeq);\n\t\t\t}\n\t\t\tuint32_t GetJitter() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->jitter);\n\t\t\t}\n\t\t\tvoid SetJitter(float jitter)\n\t\t\t{\n\t\t\t\tthis->header->jitter = htonl(static_cast<uint32_t>(jitter));\n\t\t\t}\n\t\t\tuint32_t GetLastSenderReport() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->lsr);\n\t\t\t}\n\t\t\tvoid SetLastSenderReport(uint32_t lsr)\n\t\t\t{\n\t\t\t\tthis->header->lsr = htonl(lsr);\n\t\t\t}\n\t\t\tuint32_t GetDelaySinceLastSenderReport() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->dlsr);\n\t\t\t}\n\t\t\tvoid SetDelaySinceLastSenderReport(uint32_t dlsr)\n\t\t\t{\n\t\t\t\tthis->header->dlsr = htonl(dlsr);\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t\tuint8_t raw[HeaderSize]{ 0u };\n\t\t};\n\n\t\tclass ReceiverReportPacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\tstatic size_t maxReportsPerPacket;\n\n\t\t\tusing Iterator = std::vector<ReceiverReport*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic ReceiverReportPacket* Parse(const uint8_t* data, size_t len, size_t offset = 0);\n\n\t\tpublic:\n\t\t\tReceiverReportPacket() : Packet(Type::RR)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit ReceiverReportPacket(CommonHeader* commonHeader) : Packet(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\t~ReceiverReportPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* report : this->reports)\n\t\t\t\t{\n\t\t\t\t\tdelete report;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->ssrc;\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrc = ssrc;\n\t\t\t}\n\t\t\tvoid AddReport(ReceiverReport* report)\n\t\t\t{\n\t\t\t\tthis->reports.push_back(report);\n\t\t\t}\n\t\t\tvoid RemoveReport(ReceiverReport* report)\n\t\t\t{\n\t\t\t\tauto it = std::find(this->reports.begin(), this->reports.end(), report);\n\n\t\t\t\tif (it != this->reports.end())\n\t\t\t\t{\n\t\t\t\t\tthis->reports.erase(it);\n\t\t\t\t}\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->reports.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->reports.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\t// NOTE: We need to force this since when we parse a SenderReportPacket\n\t\t\t// that contains receive report blocks we also generate a second\n\t\t\t// ReceiverReportPacket/ from same data and len, so parent\n\t\t\t// Packet::GetType() would return this->type which would be SR instead of\n\t\t\t// RR.\n\t\t\tType GetType() const override\n\t\t\t{\n\t\t\t\treturn Type::RR;\n\t\t\t}\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn this->reports.size();\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\t// A serialized packet can contain a maximum of 31 reports.\n\t\t\t\t// If number of reports exceeds 31 then the required number of packets\n\t\t\t\t// will be serialized which will take the size calculated below.\n\t\t\t\tsize_t size = (Packet::CommonHeaderSize + 4u /* this->ssrc */) *\n\t\t\t\t              ((this->GetCount() / maxReportsPerPacket) + 1);\n\t\t\t\tsize += ReceiverReport::HeaderSize * this->GetCount();\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\t// SSRC of packet sender.\n\t\t\tuint32_t ssrc{ 0u };\n\t\t\tstd::vector<ReceiverReport*> reports;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/Sdes.hpp",
    "content": "#ifndef MS_RTC_RTCP_SDES_HPP\n#define MS_RTC_RTCP_SDES_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* SDES Item. */\n\t\tclass SdesItem\n\t\t{\n\t\tpublic:\n\t\t\tenum class Type : uint8_t\n\t\t\t{\n\t\t\t\tEND = 0,\n\t\t\t\tCNAME,\n\t\t\t\tNAME,\n\t\t\t\tEMAIL,\n\t\t\t\tPHONE,\n\t\t\t\tLOC,\n\t\t\t\tTOOL,\n\t\t\t\tNOTE,\n\t\t\t\tPRIV\n\t\t\t};\n\n#ifdef MS_TEST\n\t\tpublic:\n#else\n\t\tprivate:\n#endif\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tSdesItem::Type type;\n\t\t\t\tuint8_t length;\n\t\t\t\tchar value[];\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize = 2;\n\t\t\tstatic SdesItem* Parse(const uint8_t* data, size_t len);\n\t\t\tstatic const std::string& TypeToString(SdesItem::Type type);\n\n\t\tpublic:\n\t\t\texplicit SdesItem(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit SdesItem(SdesItem* item) : header(item->header)\n\t\t\t{\n\t\t\t}\n\t\t\tSdesItem(SdesItem::Type type, size_t len, const char* value);\n\t\t\t~SdesItem() = default;\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tsize_t Serialize(uint8_t* buffer);\n\t\t\tsize_t GetSize() const\n\t\t\t{\n\t\t\t\treturn 2 + size_t{ this->header->length };\n\t\t\t}\n\n\t\t\tSdesItem::Type GetType() const\n\t\t\t{\n\t\t\t\treturn this->header->type;\n\t\t\t}\n\t\t\tuint8_t GetLength() const\n\t\t\t{\n\t\t\t\treturn this->header->length;\n\t\t\t}\n\t\t\tchar* GetValue() const\n\t\t\t{\n\t\t\t\treturn this->header->value;\n\t\t\t}\n\n\t\tprivate:\n\t\t\t// Passed by argument.\n\t\t\tHeader* header{ nullptr };\n\t\t\tstd::unique_ptr<uint8_t[]> raw;\n\n\t\tprivate:\n\t\t\tstatic const absl::flat_hash_map<SdesItem::Type, std::string> Type2String;\n\t\t};\n\n\t\tclass SdesChunk\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = std::vector<SdesItem*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic SdesChunk* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\texplicit SdesChunk(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrc = ssrc;\n\t\t\t}\n\t\t\texplicit SdesChunk(SdesChunk* chunk)\n\t\t\t{\n\t\t\t\tthis->ssrc = chunk->ssrc;\n\n\t\t\t\tfor (auto it = chunk->Begin(); it != chunk->End(); ++it)\n\t\t\t\t{\n\t\t\t\t\tthis->AddItem(new SdesItem(*it));\n\t\t\t\t}\n\t\t\t}\n\t\t\t~SdesChunk()\n\t\t\t{\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tdelete item;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tvoid Serialize();\n\t\t\tsize_t Serialize(uint8_t* buffer);\n\t\t\tsize_t GetSize() const\n\t\t\t{\n\t\t\t\tsize_t size = 4u /*ssrc*/;\n\n\t\t\t\tfor (auto* item : this->items)\n\t\t\t\t{\n\t\t\t\t\tsize += item->GetSize();\n\t\t\t\t}\n\n\t\t\t\t// Add the mandatory null octet.\n\t\t\t\t++size;\n\n\t\t\t\t// Consider pading to 32 bits (4 bytes) boundary.\n\t\t\t\t// http://stackoverflow.com/questions/11642210/computing-padding-required-for-n-byte-alignment\n\t\t\t\treturn (size + 3) & ~3;\n\t\t\t}\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->ssrc;\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrc = htonl(ssrc);\n\t\t\t}\n\t\t\tvoid AddItem(SdesItem* item)\n\t\t\t{\n\t\t\t\tthis->items.push_back(item);\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->items.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->items.end();\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint32_t ssrc{ 0u };\n\t\t\tstd::vector<SdesItem*> items;\n\t\t};\n\n\t\tclass SdesPacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\tstatic size_t maxChunksPerPacket;\n\n\t\t\tusing Iterator = std::vector<SdesChunk*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic SdesPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\tSdesPacket() : Packet(RTCP::Type::SDES)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit SdesPacket(CommonHeader* commonHeader) : Packet(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\t~SdesPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t\t{\n\t\t\t\t\tdelete chunk;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid AddChunk(SdesChunk* chunk)\n\t\t\t{\n\t\t\t\tthis->chunks.push_back(chunk);\n\t\t\t}\n\t\t\tvoid RemoveChunk(SdesChunk* chunk)\n\t\t\t{\n\t\t\t\tauto it = std::find(this->chunks.begin(), this->chunks.end(), chunk);\n\n\t\t\t\tif (it != this->chunks.end())\n\t\t\t\t{\n\t\t\t\t\tthis->chunks.erase(it);\n\t\t\t\t}\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->chunks.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->chunks.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn this->chunks.size();\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\t// A serialized packet can contain a maximum of 31 chunks.\n\t\t\t\t// If number of chunks exceeds 31 then the required number of packets\n\t\t\t\t// will be serialized which will take the size calculated below.\n\t\t\t\tsize_t size =\n\t\t\t\t  Packet::CommonHeaderSize * ((this->GetCount() / (SdesPacket::maxChunksPerPacket + 1)) + 1);\n\n\t\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t\t{\n\t\t\t\t\tsize += chunk->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<SdesChunk*> chunks;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/SenderReport.hpp",
    "content": "#ifndef MS_RTC_RTCP_SENDER_REPORT_HPP\n#define MS_RTC_RTCP_SENDER_REPORT_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass SenderReport\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for RTCP sender report.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Header\n\t\t\t{\n\t\t\t\tuint32_t ssrc;\n\t\t\t\tuint32_t ntpSec;\n\t\t\t\tuint32_t ntpFrac;\n\t\t\t\tuint32_t rtpTs;\n\t\t\t\tuint32_t packetCount;\n\t\t\t\tuint32_t octetCount;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t HeaderSize{ 24 };\n\t\t\tstatic SenderReport* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Locally generated Report. Holds the data internally.\n\t\t\tSenderReport()\n\t\t\t{\n\t\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\t\t\t}\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit SenderReport(Header* header) : header(header)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit SenderReport(SenderReport* report) : header(report->header)\n\t\t\t{\n\t\t\t}\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tsize_t Serialize(uint8_t* buffer);\n\t\t\tsize_t GetSize() const\n\t\t\t{\n\t\t\t\treturn HeaderSize;\n\t\t\t}\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ssrc);\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->header->ssrc = htonl(ssrc);\n\t\t\t}\n\t\t\tuint32_t GetNtpSec() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ntpSec);\n\t\t\t}\n\t\t\tvoid SetNtpSec(uint32_t ntpSec)\n\t\t\t{\n\t\t\t\tthis->header->ntpSec = htonl(ntpSec);\n\t\t\t}\n\t\t\tuint32_t GetNtpFrac() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->ntpFrac);\n\t\t\t}\n\t\t\tvoid SetNtpFrac(uint32_t ntpFrac)\n\t\t\t{\n\t\t\t\tthis->header->ntpFrac = htonl(ntpFrac);\n\t\t\t}\n\t\t\tuint32_t GetRtpTs() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->rtpTs);\n\t\t\t}\n\t\t\tvoid SetRtpTs(uint32_t rtpTs)\n\t\t\t{\n\t\t\t\tthis->header->rtpTs = htonl(rtpTs);\n\t\t\t}\n\t\t\tuint32_t GetPacketCount() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->packetCount);\n\t\t\t}\n\t\t\tvoid SetPacketCount(uint32_t packetCount)\n\t\t\t{\n\t\t\t\tthis->header->packetCount = htonl(packetCount);\n\t\t\t}\n\t\t\tuint32_t GetOctetCount() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->header->octetCount);\n\t\t\t}\n\t\t\tvoid SetOctetCount(uint32_t octetCount)\n\t\t\t{\n\t\t\t\tthis->header->octetCount = htonl(octetCount);\n\t\t\t}\n\n\t\tprivate:\n\t\t\tHeader* header{ nullptr };\n\t\t\tuint8_t raw[HeaderSize]{ 0 };\n\t\t};\n\n\t\tclass SenderReportPacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = std::vector<SenderReport*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic SenderReportPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\tSenderReportPacket() : Packet(Type::SR)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit SenderReportPacket(CommonHeader* commonHeader) : Packet(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\t~SenderReportPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* report : this->reports)\n\t\t\t\t{\n\t\t\t\t\tdelete report;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid AddReport(SenderReport* report)\n\t\t\t{\n\t\t\t\tthis->reports.push_back(report);\n\t\t\t}\n\t\t\tvoid RemoveReport(SenderReport* report)\n\t\t\t{\n\t\t\t\tauto it = std::find(this->reports.begin(), this->reports.end(), report);\n\n\t\t\t\tif (it != this->reports.end())\n\t\t\t\t{\n\t\t\t\t\tthis->reports.erase(it);\n\t\t\t\t}\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->reports.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->reports.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn this->reports.size();\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\t// A serialized packet consists of a series of SR packets with\n\t\t\t\t// one SR report each.\n\t\t\t\tsize_t size{ 0 };\n\n\t\t\t\tfor (auto* report : this->reports)\n\t\t\t\t{\n\t\t\t\t\tsize += Packet::CommonHeaderSize;\n\t\t\t\t\tsize += report->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<SenderReport*> reports;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/XR.hpp",
    "content": "#ifndef MS_RTC_RTCP_XR_HPP\n#define MS_RTC_RTCP_XR_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <vector>\n\n/* https://tools.ietf.org/html/rfc3611\n * RTP Control Protocol Extended Reports (RTCP XR)\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |V=2|P|reserved |   PT=XR=207   |             length            |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                              SSRC                             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  :                         report blocks                         :\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass ExtendedReportBlock\n\t\t{\n\t\tpublic:\n\t\t\tenum class Type : uint8_t\n\t\t\t{\n\t\t\t\tLRLE = 1,\n\t\t\t\tDRLE = 2,\n\t\t\t\tPRT  = 3,\n\t\t\t\tRRT  = 4,\n\t\t\t\tDLRR = 5,\n\t\t\t\tSS   = 6,\n\t\t\t\tVM   = 7\n\t\t\t};\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for Extended Report Block common header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct CommonHeader\n\t\t\t{\n\t\t\t\tuint8_t blockType;\n\t\t\t\tuint8_t reserved;\n\t\t\t\tuint16_t length;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t CommonHeaderSize{ 4 };\n\t\t\tstatic ExtendedReportBlock* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\texplicit ExtendedReportBlock(Type type) : type(type)\n\t\t\t{\n\t\t\t\tthis->header = reinterpret_cast<CommonHeader*>(this->raw);\n\n\t\t\t\tthis->header->reserved = 0;\n\t\t\t}\n\t\t\tvirtual ~ExtendedReportBlock() = default;\n\n\t\tpublic:\n\t\t\tvirtual void Dump(int indentation = 0) const = 0;\n\t\t\tvirtual size_t Serialize(uint8_t* buffer)    = 0;\n\t\t\tvirtual size_t GetSize() const               = 0;\n\t\t\tExtendedReportBlock::Type GetType() const\n\t\t\t{\n\t\t\t\treturn this->type;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tType type;\n\t\t\tCommonHeader* header{ nullptr };\n\n\t\tprivate:\n\t\t\tuint8_t raw[CommonHeaderSize] = { 0 };\n\t\t};\n\n\t\tclass ExtendedReportPacket : public Packet\n\t\t{\n\t\tpublic:\n\t\t\tusing Iterator = std::vector<ExtendedReportBlock*>::iterator;\n\n\t\tpublic:\n\t\t\tstatic ExtendedReportPacket* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\tExtendedReportPacket() : Packet(Type::XR)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit ExtendedReportPacket(CommonHeader* commonHeader) : Packet(commonHeader)\n\t\t\t{\n\t\t\t}\n\t\t\t~ExtendedReportPacket() override\n\t\t\t{\n\t\t\t\tfor (auto* report : this->reports)\n\t\t\t\t{\n\t\t\t\t\tdelete report;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid AddReport(ExtendedReportBlock* report)\n\t\t\t{\n\t\t\t\tthis->reports.push_back(report);\n\t\t\t}\n\t\t\tvoid RemoveReport(ExtendedReportBlock* report)\n\t\t\t{\n\t\t\t\tauto it = std::find(this->reports.begin(), this->reports.end(), report);\n\n\t\t\t\tif (it != this->reports.end())\n\t\t\t\t{\n\t\t\t\t\tthis->reports.erase(it);\n\t\t\t\t}\n\t\t\t}\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->ssrc;\n\t\t\t}\n\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t{\n\t\t\t\tthis->ssrc = ssrc;\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->reports.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->reports.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from Packet. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetCount() const override\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size = Packet::CommonHeaderSize + 4u /*ssrc*/;\n\n\t\t\t\tfor (auto* report : this->reports)\n\t\t\t\t{\n\t\t\t\t\tsize += report->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint32_t ssrc{ 0u };\n\t\t\tstd::vector<ExtendedReportBlock*> reports;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/XrDelaySinceLastRr.hpp",
    "content": "#ifndef MS_RTC_RTCP_XR_DELAY_SINCE_LAST_RR_HPP\n#define MS_RTC_RTCP_XR_DELAY_SINCE_LAST_RR_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/XR.hpp\"\n\n/* https://tools.ietf.org/html/rfc3611\n * Delay Since Last Receiver Report (DLRR) Report Block\n\n  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |     BT=5      |   reserved    |         block length          |\n  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n  |                 SSRC_1 (SSRC of first receiver)               | sub-\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block\n  |                         last RR (LRR)                         |   1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |                   delay since last RR (DLRR)                  |\n  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n  |                 SSRC_2 (SSRC of second receiver)              | sub-\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block\n  :                               ...                             :   2\n  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass DelaySinceLastRr : public ExtendedReportBlock\n\t\t{\n\t\tpublic:\n\t\t\tstatic DelaySinceLastRr* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\tclass SsrcInfo\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstatic const size_t BodySize{ 12 };\n\t\t\t\tstatic SsrcInfo* Parse(const uint8_t* data, size_t len);\n\n\t\t\tpublic:\n\t\t\t\t/**\n\t\t\t\t * @remarks\n\t\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t\t */\n\t\t\t\tstruct Body\n\t\t\t\t{\n\t\t\t\t\tuint32_t ssrc;\n\t\t\t\t\tuint32_t lrr;\n\t\t\t\t\tuint32_t dlrr;\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\t// Locally generated Report. Holds the data internally.\n\t\t\t\tSsrcInfo()\n\t\t\t\t{\n\t\t\t\t\tthis->body = reinterpret_cast<Body*>(this->raw);\n\t\t\t\t}\n\t\t\t\t// Parsed Report. Points to an external data.\n\t\t\t\texplicit SsrcInfo(Body* body) : body(body)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\t\tsize_t Serialize(uint8_t* buffer);\n\t\t\t\tsize_t GetSize() const\n\t\t\t\t{\n\t\t\t\t\treturn BodySize;\n\t\t\t\t}\n\t\t\t\tuint32_t GetSsrc() const\n\t\t\t\t{\n\t\t\t\t\treturn ntohl(this->body->ssrc);\n\t\t\t\t}\n\t\t\t\tvoid SetSsrc(uint32_t ssrc)\n\t\t\t\t{\n\t\t\t\t\tthis->body->ssrc = htonl(ssrc);\n\t\t\t\t}\n\t\t\t\tuint32_t GetLastReceiverReport() const\n\t\t\t\t{\n\t\t\t\t\treturn ntohl(this->body->lrr);\n\t\t\t\t}\n\t\t\t\tvoid SetLastReceiverReport(uint32_t lrr)\n\t\t\t\t{\n\t\t\t\t\tthis->body->lrr = htonl(lrr);\n\t\t\t\t}\n\t\t\t\tuint32_t GetDelaySinceLastReceiverReport() const\n\t\t\t\t{\n\t\t\t\t\treturn ntohl(this->body->dlrr);\n\t\t\t\t}\n\t\t\t\tvoid SetDelaySinceLastReceiverReport(uint32_t dlrr)\n\t\t\t\t{\n\t\t\t\t\tthis->body->dlrr = htonl(dlrr);\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\tBody* body{ nullptr };\n\t\t\t\tuint8_t raw[BodySize] = { 0 };\n\t\t\t};\n\n\t\tpublic:\n\t\t\tusing Iterator = std::vector<SsrcInfo*>::iterator;\n\n\t\tpublic:\n\t\t\tDelaySinceLastRr() : ExtendedReportBlock(ExtendedReportBlock::Type::DLRR)\n\t\t\t{\n\t\t\t}\n\t\t\texplicit DelaySinceLastRr(CommonHeader* header)\n\t\t\t  : ExtendedReportBlock(ExtendedReportBlock::Type::DLRR)\n\t\t\t{\n\t\t\t\tthis->header = header;\n\t\t\t}\n\t\t\t~DelaySinceLastRr() override\n\t\t\t{\n\t\t\t\tfor (auto* ssrcInfo : this->ssrcInfos)\n\t\t\t\t{\n\t\t\t\t\tdelete ssrcInfo;\n\t\t\t\t}\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvoid AddSsrcInfo(SsrcInfo* ssrcInfo)\n\t\t\t{\n\t\t\t\tthis->ssrcInfos.push_back(ssrcInfo);\n\t\t\t}\n\t\t\t// NOTE: This method not only removes given number of ssrc info sub-blocks\n\t\t\t// but also deletes their SsrcInfo instances.\n\t\t\tvoid RemoveLastSsrcInfos(size_t number)\n\t\t\t{\n\t\t\t\twhile (!this->ssrcInfos.empty() && number-- > 0)\n\t\t\t\t{\n\t\t\t\t\tauto* ssrcInfo = this->ssrcInfos.back();\n\n\t\t\t\t\tthis->ssrcInfos.pop_back();\n\n\t\t\t\t\tdelete ssrcInfo;\n\t\t\t\t}\n\t\t\t}\n\t\t\tIterator Begin()\n\t\t\t{\n\t\t\t\treturn this->ssrcInfos.begin();\n\t\t\t}\n\t\t\tIterator End()\n\t\t\t{\n\t\t\t\treturn this->ssrcInfos.end();\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from ExtendedReportBlock. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size{ 4u }; // Common header.\n\n\t\t\t\tfor (auto* ssrcInfo : this->ssrcInfos)\n\t\t\t\t{\n\t\t\t\t\tsize += ssrcInfo->GetSize();\n\t\t\t\t}\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::vector<SsrcInfo*> ssrcInfos;\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTCP/XrReceiverReferenceTime.hpp",
    "content": "#ifndef MS_RTC_RTCP_XR_RECEIVER_REFERENCE_TIME_HPP\n#define MS_RTC_RTCP_XR_RECEIVER_REFERENCE_TIME_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTCP/XR.hpp\"\n\n/* https://tools.ietf.org/html/rfc3611\n * Receiver Reference Time Report Block\n\n   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |     BT=4      |   reserved    |       block length = 2        |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |              NTP timestamp, most significant word             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  |             NTP timestamp, least significant word             |\n  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tclass ReceiverReferenceTime : public ExtendedReportBlock\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct Body\n\t\t\t{\n\t\t\t\tuint32_t ntpSec;\n\t\t\t\tuint32_t ntpFrac;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t BodySize{ 8 };\n\t\t\tstatic ReceiverReferenceTime* Parse(const uint8_t* data, size_t len);\n\n\t\tpublic:\n\t\t\t// Locally generated Report. Holds the data internally.\n\t\t\tReceiverReferenceTime() : ExtendedReportBlock(RTCP::ExtendedReportBlock::Type::RRT)\n\t\t\t{\n\t\t\t\tthis->body = reinterpret_cast<Body*>(this->raw);\n\t\t\t}\n\t\t\t// Parsed Report. Points to an external data.\n\t\t\texplicit ReceiverReferenceTime(CommonHeader* header)\n\t\t\t  : ExtendedReportBlock(ExtendedReportBlock::Type::RRT)\n\t\t\t{\n\t\t\t\tthis->header = header;\n\t\t\t\tthis->body   = reinterpret_cast<Body*>((header) + 1);\n\t\t\t}\n\n\t\tpublic:\n\t\t\tuint32_t GetNtpSec() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->body->ntpSec);\n\t\t\t}\n\t\t\tvoid SetNtpSec(uint32_t ntpSec)\n\t\t\t{\n\t\t\t\tthis->body->ntpSec = htonl(ntpSec);\n\t\t\t}\n\t\t\tuint32_t GetNtpFrac() const\n\t\t\t{\n\t\t\t\treturn ntohl(this->body->ntpFrac);\n\t\t\t}\n\t\t\tvoid SetNtpFrac(uint32_t ntpFrac)\n\t\t\t{\n\t\t\t\tthis->body->ntpFrac = htonl(ntpFrac);\n\t\t\t}\n\n\t\t\t/* Pure virtual methods inherited from ExtendedReportBlock. */\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\tsize_t Serialize(uint8_t* buffer) override;\n\t\t\tsize_t GetSize() const override\n\t\t\t{\n\t\t\t\tsize_t size{ 4 }; // Common header.\n\n\t\t\t\tsize += BodySize;\n\n\t\t\t\treturn size;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tBody* body{ nullptr };\n\t\t\tuint8_t raw[BodySize] = { 0 };\n\t\t};\n\t} // namespace RTCP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/AV1.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_AV1_HPP\n#define MS_RTC_RTP_CODECS_AV1_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass AV1\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct PayloadDescriptor : public Codecs::PayloadDescriptor\n\t\t\t\t{\n\t\t\t\t\tstruct EncodingData\n\t\t\t\t\t{\n\t\t\t\t\t\tuint32_t maxSpatialLayer{ 0 };\n\t\t\t\t\t\tuint32_t maxTemporalLayer{ 0 };\n\t\t\t\t\t};\n\n\t\t\t\t\tstruct Encoder : public Codecs::PayloadDescriptor::Encoder\n\t\t\t\t\t{\n\t\t\t\t\t\t~Encoder() override = default;\n\t\t\t\t\t\texplicit Encoder(EncodingData encodingData) : encodingData(encodingData)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvoid Encode(AV1::PayloadDescriptor* payloadDescriptor) const;\n\n\t\t\t\t\t\tEncodingData encodingData;\n\t\t\t\t\t};\n\n\t\t\t\t\texplicit PayloadDescriptor(std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor);\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::PayloadDescriptor. */\n\t\t\t\t\t~PayloadDescriptor() override = default;\n\n\t\t\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\t\t\tvoid UpdateListener(Codecs::DependencyDescriptor::Listener* listener) const\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->dependencyDescriptor)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->dependencyDescriptor->UpdateListener(listener);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode();\n\t\t\t\t\tvoid Restore() const;\n\t\t\t\t\tvoid UpdateActiveDecodeTargets(uint16_t spatialLayer, uint16_t temporalLayer);\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->encoder.has_value())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn std::make_unique<Encoder>(this->encoder.value());\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvoid CreateEncoder(EncodingData encodingData)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->encoder = Encoder(encodingData);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fields in Dependency Descriptor extension.\n\t\t\t\t\tbool startOfFrame{ false };\n\t\t\t\t\tbool endOfFrame{ false };\n\t\t\t\t\tuint16_t frameNumber{ 0 };\n\t\t\t\t\tuint8_t spatialLayer{ 0 };\n\t\t\t\t\tuint8_t temporalLayer{ 0 };\n\t\t\t\t\tstd::unique_ptr<Codecs::DependencyDescriptor> dependencyDescriptor{ nullptr };\n\t\t\t\t\t// Parsed values.\n\t\t\t\t\tbool isKeyFrame{ false };\n\t\t\t\t\tstd::optional<Encoder> encoder{ std::nullopt };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstatic AV1::PayloadDescriptor* Parse(\n\t\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor);\n\t\t\t\tstatic void ProcessRtpPacket(\n\t\t\t\t  RTP::Packet* packet,\n\t\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t\t\t\t    templateDependencyStructure);\n\n\t\t\tpublic:\n\t\t\t\tclass EncodingContext : public Codecs::EncodingContext\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params)\n\t\t\t\t\t  : Codecs::EncodingContext(params)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\t~EncodingContext() override = default;\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::EncodingContext. */\n\t\t\t\tpublic:\n\t\t\t\t\tvoid SyncRequired() override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->syncRequired = true;\n\t\t\t\t\t}\n\n\t\t\t\tpublic:\n\t\t\t\t\tbool syncRequired{ false };\n\t\t\t\t};\n\n\t\t\t\tclass PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor);\n\t\t\t\t\t~PayloadDescriptorHandler() override = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvoid Dump(int indentation = 0) const override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->Dump(indentation);\n\t\t\t\t\t}\n\t\t\t\t\tbool Process(\n\t\t\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override;\n\t\t\t\t\tvoid RtpPacketChanged(RTP::Packet* packet) override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->UpdateListener(packet);\n\t\t\t\t\t};\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->GetEncoder();\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override;\n\t\t\t\t\tvoid Restore(RTP::Packet* packet) override;\n\t\t\t\t\tuint8_t GetSpatialLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->spatialLayer;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetTemporalLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->temporalLayer;\n\t\t\t\t\t}\n\t\t\t\t\tbool IsKeyFrame() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->isKeyFrame;\n\t\t\t\t\t}\n\n\t\t\t\tprivate:\n\t\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor;\n\t\t\t\t};\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/DependencyDescriptor.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP\n#define MS_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\" // BitStream.\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tstruct DependencyDescriptor\n\t\t\t{\n\t\t\t\tclass Listener\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\tvirtual ~Listener() = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvirtual void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) = 0;\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tenum class DecodeTargetIndication : uint8_t\n\t\t\t\t{\n\t\t\t\t\tNOT_PRESENT = 0,\n\t\t\t\t\tDISCARDABLE = 1,\n\t\t\t\t\tSWITCH      = 2,\n\t\t\t\t\tREQUIRED    = 3\n\t\t\t\t};\n\n\t\t\tprivate:\n\t\t\t\tstatic std::unordered_map<DecodeTargetIndication, std::string> dtiToString;\n\n\t\t\tprivate:\n\t\t\t\tstruct FrameDependencyTemplate\n\t\t\t\t{\n\t\t\t\t\tFrameDependencyTemplate(uint32_t spatialLayer, uint32_t temporalLayer)\n\t\t\t\t\t  : spatialLayer(spatialLayer), temporalLayer(temporalLayer)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\n\t\t\t\t\tuint32_t spatialLayer;\n\t\t\t\t\tuint32_t temporalLayer;\n\t\t\t\t\tstd::vector<DecodeTargetIndication> decodeTargetIndications;\n\t\t\t\t\tstd::vector<uint8_t> frameDiffs;\n\t\t\t\t\tstd::vector<uint8_t> frameDiffChains;\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstruct TemplateDependencyStructure\n\t\t\t\t{\n\t\t\t\t\tuint32_t spatialLayers{ 0 };\n\t\t\t\t\tuint32_t temporalLayers{ 0 };\n\t\t\t\t\tuint8_t templateIdOffset{ 0 };\n\t\t\t\t\tuint8_t decodeTargetCount{ 0 };\n\t\t\t\t\tstd::vector<FrameDependencyTemplate> templateLayers;\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tbool startOfFrame{ false };\n\t\t\t\tbool endOfFrame{ false };\n\t\t\t\tuint8_t frameDependencyTemplateId{ 0 };\n\t\t\t\tuint16_t frameNumber{ 0 };\n\t\t\t\tuint8_t templateId{ 0 };\n\t\t\t\t// Given by argument.\n\t\t\t\tTemplateDependencyStructure* templateDependencyStructure;\n\t\t\t\tstd::vector<uint8_t> decodeTargetProtectedBy;\n\t\t\t\tstd::optional<uint32_t> activeDecodeTargetsBitmask{ std::nullopt };\n\t\t\t\t// Calculated.\n\t\t\t\tuint8_t temporalLayer{ 0 };\n\t\t\t\tuint8_t spatialLayer{ 0 };\n\t\t\t\t// Whether the frame is a key frame. Set to true if the descriptor contains template layers.\n\t\t\t\tbool isKeyFrame{ false };\n\n\t\t\tprivate:\n\t\t\t\tDependencyDescriptor::Listener* listener{ nullptr };\n\n\t\t\tpublic:\n\t\t\t\tstatic DependencyDescriptor* Parse(\n\t\t\t\t  const uint8_t* data,\n\t\t\t\t  size_t len,\n\t\t\t\t  DependencyDescriptor::Listener* listener,\n\t\t\t\t  std::unique_ptr<TemplateDependencyStructure>& templateDependencyStructure);\n\n\t\t\t\tDependencyDescriptor(\n\t\t\t\t  const uint8_t* data,\n\t\t\t\t  size_t len,\n\t\t\t\t  DependencyDescriptor::Listener* listener,\n\t\t\t\t  TemplateDependencyStructure* templateDependencyStructure);\n\n\t\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\t\tvoid UpdateListener(DependencyDescriptor::Listener* listener);\n\t\t\t\tconst uint8_t* Serialize(uint8_t& len);\n\t\t\t\tbool UpdateActiveDecodeTargets(uint32_t maxSpatialLayer, uint32_t maxTemporalLayer);\n\n\t\t\tprivate:\n\t\t\t\tuint8_t GetSpatialLayer() const;\n\t\t\t\tuint8_t GetTemporalLayer() const;\n\n\t\t\t\tbool ReadMandatoryDescriptorFields();\n\t\t\t\tbool ReadExtendedDescriptorFields();\n\t\t\t\tbool ReadTemplateDependencyStructure();\n\t\t\t\tbool ReadTemplateLayers();\n\t\t\t\tbool ReadTemplateDecodeTargetIndications();\n\t\t\t\tbool ReadTemplateFrameDiffs();\n\t\t\t\tbool ReadTemplateFrameDiffChains();\n\t\t\t\tbool ReadFrameDependencyDefinition();\n\t\t\t\tbool WriteMandatoryDescriptorFields();\n\t\t\t\tbool WriteExtendedDescriptorFields();\n\n\t\t\tprivate:\n\t\t\t\tUtils::BitStream bitStream;\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/H264.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_H264_HPP\n#define MS_RTC_RTP_CODECS_H264_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass H264\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct PayloadDescriptor : public Codecs::PayloadDescriptor\n\t\t\t\t{\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::PayloadDescriptor. */\n\t\t\t\t\t~PayloadDescriptor() override = default;\n\n\t\t\t\t\tvoid Dump(int indentation = 0) const override;\n\n\t\t\t\t\t// Fields in Dependency Descriptor extension.\n\t\t\t\t\tbool startOfFrame{ false };\n\t\t\t\t\tbool endOfFrame{ false };\n\t\t\t\t\tuint8_t spatialLayer{ 0 };\n\t\t\t\t\tuint8_t temporalLayer{ 0 };\n\n\t\t\t\t\t// Parsed values.\n\t\t\t\t\tbool isKeyFrame{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstatic H264::PayloadDescriptor* Parse(\n\t\t\t\t  const uint8_t* data, size_t len, Codecs::DependencyDescriptor* dependencyDescriptor);\n\t\t\t\tstatic bool IsKeyFrame(const uint8_t* data, size_t len);\n\t\t\t\tstatic void ProcessRtpPacket(\n\t\t\t\t  RTP::Packet* packet,\n\t\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t\t\t\t    templateDependencyStructure);\n\n\t\t\tpublic:\n\t\t\t\tclass EncodingContext : public Codecs::EncodingContext\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params)\n\t\t\t\t\t  : Codecs::EncodingContext(params)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\t~EncodingContext() override = default;\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::EncodingContext. */\n\t\t\t\tpublic:\n\t\t\t\t\tvoid SyncRequired() override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tclass PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor);\n\t\t\t\t\t~PayloadDescriptorHandler() override = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvoid Dump(int indentation = 0) const override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->Dump(indentation);\n\t\t\t\t\t}\n\t\t\t\t\tbool Process(\n\t\t\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override;\n\t\t\t\t\tvoid RtpPacketChanged(RTP::Packet* packet) override {};\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tvoid Restore(RTP::Packet* packet) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetSpatialLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0u;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetTemporalLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->temporalLayer;\n\t\t\t\t\t}\n\t\t\t\t\tbool IsKeyFrame() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->isKeyFrame;\n\t\t\t\t\t}\n\n\t\t\t\tprivate:\n\t\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor;\n\t\t\t\t};\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/Opus.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_OPUS_HPP\n#define MS_RTC_RTP_CODECS_OPUS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass Opus\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct PayloadDescriptor : public Codecs::PayloadDescriptor\n\t\t\t\t{\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::PayloadDescriptor. */\n\t\t\t\t\t~PayloadDescriptor() override = default;\n\n\t\t\t\t\tvoid Dump(int indentation = 0) const override;\n\n\t\t\t\t\t// Mandatory fields.\n\t\t\t\t\tuint8_t stereo : 1;\n\t\t\t\t\tuint8_t code : 2;\n\t\t\t\t\t// Parsed values.\n\t\t\t\t\tbool isDtx{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstatic Opus::PayloadDescriptor* Parse(const uint8_t* data, size_t len);\n\t\t\t\tstatic void ProcessRtpPacket(RTP::Packet* packet);\n\n\t\t\tpublic:\n\t\t\t\tclass EncodingContext : public Codecs::EncodingContext\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params)\n\t\t\t\t\t  : Codecs::EncodingContext(params)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\t~EncodingContext() override = default;\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::EncodingContext. */\n\t\t\t\tpublic:\n\t\t\t\t\tvoid SyncRequired() override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->syncRequired = true;\n\t\t\t\t\t}\n\n\t\t\t\tpublic:\n\t\t\t\t\tbool syncRequired{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tclass PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor);\n\t\t\t\t\t~PayloadDescriptorHandler() override = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvoid Dump(int indentation = 0) const override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->Dump(indentation);\n\t\t\t\t\t}\n\t\t\t\t\tbool Process(\n\t\t\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override;\n\t\t\t\t\tvoid RtpPacketChanged(RTP::Packet* packet) override {};\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tvoid Restore(RTP::Packet* packet) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetSpatialLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0u;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetTemporalLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0u;\n\t\t\t\t\t}\n\t\t\t\t\tbool IsKeyFrame() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\tprivate:\n\t\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor;\n\t\t\t\t};\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/PayloadDescriptorHandler.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_PAYLOAD_DESCRIPTOR_HANDLER_HPP\n#define MS_RTC_RTP_CODECS_PAYLOAD_DESCRIPTOR_HANDLER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/ConsumerTypes.hpp\"\n#include \"RTC/RTP/Codecs/DependencyDescriptor.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <deque>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass Packet;\n\n\t\tnamespace Codecs\n\t\t{\n\t\t\t// Codec payload descriptor.\n\t\t\tstruct PayloadDescriptor\n\t\t\t{\n\t\t\t\tstruct Encoder\n\t\t\t\t{\n\t\t\t\t\tvirtual ~Encoder() = default;\n\t\t\t\t};\n\n\t\t\t\tvirtual ~PayloadDescriptor()                 = default;\n\t\t\t\tvirtual void Dump(int indentation = 0) const = 0;\n\t\t\t};\n\n\t\t\tclass PictureIdList\n\t\t\t{\n\t\t\t\tstatic constexpr uint16_t MaxCurrentLayerPictureIdNum{ 1000u };\n\n\t\t\tpublic:\n\t\t\t\texplicit PictureIdList() = default;\n\n\t\t\t\t~PictureIdList()\n\t\t\t\t{\n\t\t\t\t\tthis->layerChanges.clear();\n\t\t\t\t}\n\n\t\t\t\tvoid Push(uint16_t pictureId, int16_t layer)\n\t\t\t\t{\n\t\t\t\t\tfor (const auto& it : this->layerChanges)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Layers can be changed only with ordered pictureId values.\n\t\t\t\t\t\t// If pictureId is lower than the previous one, then it has rolled over the max value.\n\t\t\t\t\t\tuint16_t diff = pictureId > it.first\n\t\t\t\t\t\t                  ? pictureId - it.first\n\t\t\t\t\t\t                  : pictureId + RTC::SeqManager<uint16_t, 15>::MaxValue - it.first;\n\n\t\t\t\t\t\tif (diff > MaxCurrentLayerPictureIdNum)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->layerChanges.pop_front();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->layerChanges.emplace_back(pictureId, layer);\n\t\t\t\t}\n\n\t\t\t\tint16_t GetLayer(uint16_t pictureId) const\n\t\t\t\t{\n\t\t\t\t\tif (this->layerChanges.size() <= 1)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (auto it = std::next(this->layerChanges.begin()); it != this->layerChanges.end(); ++it)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (RTC::SeqManager<uint16_t, 15>::IsSeqHigherThan(it->first, pictureId))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn std::prev(it)->second;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// List populated with the spatial/temporal layer changes\n\t\t\t\t// indexed by the corresponding pictureId.\n\t\t\t\tstd::deque<std::pair<uint16_t, int16_t>> layerChanges;\n\t\t\t};\n\n\t\t\t// Encoding context used by PayloadDescriptorHandler to properly rewrite the\n\t\t\t// PayloadDescriptor.\n\t\t\tclass EncodingContext\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct Params\n\t\t\t\t{\n\t\t\t\t\tuint8_t spatialLayers{ 1u };\n\t\t\t\t\tuint8_t temporalLayers{ 1u };\n\t\t\t\t\tbool ksvc{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params) : params(params)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t\tvirtual ~EncodingContext() = default;\n\n\t\t\tpublic:\n\t\t\t\tuint8_t GetSpatialLayers() const\n\t\t\t\t{\n\t\t\t\t\treturn this->params.spatialLayers;\n\t\t\t\t}\n\t\t\t\tuint8_t GetTemporalLayers() const\n\t\t\t\t{\n\t\t\t\t\treturn this->params.temporalLayers;\n\t\t\t\t}\n\t\t\t\tbool IsKSvc() const\n\t\t\t\t{\n\t\t\t\t\treturn this->params.ksvc;\n\t\t\t\t}\n\t\t\t\tint16_t GetTargetSpatialLayer() const\n\t\t\t\t{\n\t\t\t\t\treturn this->targetLayers.spatial;\n\t\t\t\t}\n\t\t\t\tint16_t GetTargetTemporalLayer() const\n\t\t\t\t{\n\t\t\t\t\treturn this->targetLayers.temporal;\n\t\t\t\t}\n\t\t\t\tconst RTC::ConsumerTypes::VideoLayers& GetTargetLayers() const\n\t\t\t\t{\n\t\t\t\t\treturn this->targetLayers;\n\t\t\t\t}\n\t\t\t\tint16_t GetCurrentSpatialLayer() const\n\t\t\t\t{\n\t\t\t\t\treturn this->currentLayers.spatial;\n\t\t\t\t}\n\t\t\t\tint16_t GetCurrentTemporalLayer() const\n\t\t\t\t{\n\t\t\t\t\treturn this->currentLayers.temporal;\n\t\t\t\t}\n\t\t\t\tconst RTC::ConsumerTypes::VideoLayers& GetCurrentLayers() const\n\t\t\t\t{\n\t\t\t\t\treturn this->currentLayers;\n\t\t\t\t}\n\t\t\t\tbool GetIgnoreDtx() const\n\t\t\t\t{\n\t\t\t\t\treturn this->ignoreDtx;\n\t\t\t\t}\n\t\t\t\tvoid SetTargetSpatialLayer(int16_t spatialLayer)\n\t\t\t\t{\n\t\t\t\t\tthis->targetLayers.spatial = spatialLayer;\n\t\t\t\t}\n\t\t\t\tvoid SetTargetTemporalLayer(int16_t temporalLayer)\n\t\t\t\t{\n\t\t\t\t\tthis->targetLayers.temporal = temporalLayer;\n\t\t\t\t}\n\t\t\t\tvoid SetCurrentSpatialLayer(int16_t spatialLayer)\n\t\t\t\t{\n\t\t\t\t\tthis->currentLayers.spatial = spatialLayer;\n\t\t\t\t}\n\t\t\t\tvoid SetCurrentTemporalLayer(int16_t temporalLayer)\n\t\t\t\t{\n\t\t\t\t\tthis->currentLayers.temporal = temporalLayer;\n\t\t\t\t}\n\t\t\t\tvoid SetIgnoreDtx(bool ignoreDtx)\n\t\t\t\t{\n\t\t\t\t\tthis->ignoreDtx = ignoreDtx;\n\t\t\t\t}\n\t\t\t\tvirtual void SyncRequired() = 0;\n\t\t\t\tvoid SetCurrentSpatialLayer(int16_t spatialLayer, uint16_t pictureId)\n\t\t\t\t{\n\t\t\t\t\tif (this->currentLayers.spatial == spatialLayer)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->spatialLayerPictureIdList.Push(pictureId, spatialLayer);\n\t\t\t\t\tthis->currentLayers.spatial = spatialLayer;\n\t\t\t\t}\n\t\t\t\tvoid SetCurrentTemporalLayer(int16_t temporalLayer, uint16_t pictureId)\n\t\t\t\t{\n\t\t\t\t\tif (this->currentLayers.temporal == temporalLayer)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->temporalLayerPictureIdList.Push(pictureId, temporalLayer);\n\t\t\t\t\tthis->currentLayers.temporal = temporalLayer;\n\t\t\t\t}\n\t\t\t\tint16_t GetSpatialLayerForPictureId(uint16_t pictureId) const\n\t\t\t\t{\n\t\t\t\t\tint16_t layer = this->spatialLayerPictureIdList.GetLayer(pictureId);\n\n\t\t\t\t\tif (layer > -1)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn layer;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this->currentLayers.spatial;\n\t\t\t\t}\n\t\t\t\tint16_t GetTemporalLayerForPictureId(uint16_t pictureId) const\n\t\t\t\t{\n\t\t\t\t\tint16_t layer = this->temporalLayerPictureIdList.GetLayer(pictureId);\n\n\t\t\t\t\tif (layer > -1)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn layer;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this->currentLayers.temporal;\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\tParams params;\n\t\t\t\tRTC::ConsumerTypes::VideoLayers targetLayers;\n\t\t\t\tRTC::ConsumerTypes::VideoLayers currentLayers;\n\t\t\t\tbool ignoreDtx{ false };\n\n\t\t\tprivate:\n\t\t\t\t// List of spatial/temporal layer changes indexed by the corresponding pictureId.\n\t\t\t\tPictureIdList spatialLayerPictureIdList;\n\t\t\t\tPictureIdList temporalLayerPictureIdList;\n\t\t\t};\n\n\t\t\tclass PayloadDescriptorHandler\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~PayloadDescriptorHandler() = default;\n\n\t\t\tpublic:\n\t\t\t\tvirtual void Dump(int indentation = 0) const                                      = 0;\n\t\t\t\tvirtual bool Process(EncodingContext* context, RTP::Packet* packet, bool& marker) = 0;\n\t\t\t\tvirtual void RtpPacketChanged(RTP::Packet* packet)                                = 0;\n\t\t\t\tvirtual std::unique_ptr<PayloadDescriptor::Encoder> GetEncoder() const            = 0;\n\t\t\t\tvirtual void Encode(RTP::Packet* packet, PayloadDescriptor::Encoder* encoder)     = 0;\n\t\t\t\tvirtual void Restore(RTP::Packet* packet)                                         = 0;\n\t\t\t\tvirtual uint8_t GetSpatialLayer() const                                           = 0;\n\t\t\t\tvirtual uint8_t GetTemporalLayer() const                                          = 0;\n\t\t\t\tvirtual bool IsKeyFrame() const                                                   = 0;\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/Tools.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_TOOLS_HPP\n#define MS_RTC_RTP_CODECS_TOOLS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/AV1.hpp\"\n#include \"RTC/RTP/Codecs/H264.hpp\"\n#include \"RTC/RTP/Codecs/Opus.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Codecs/VP8.hpp\"\n#include \"RTC/RTP/Codecs/VP9.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass Tools\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstatic bool CanBeKeyFrame(const RTC::RtpCodecMimeType& mimeType)\n\t\t\t\t{\n\t\t\t\t\tswitch (mimeType.type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP8:\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP9:\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::H264:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstatic void ProcessRtpPacket(\n\t\t\t\t  RTP::Packet* packet,\n\t\t\t\t  const RTC::RtpCodecMimeType& mimeType,\n\t\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t\t\t\t    templateDependencyStructure)\n\t\t\t\t{\n\t\t\t\t\tswitch (mimeType.type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP8:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tCodecs::VP8::ProcessRtpPacket(packet);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP9:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tCodecs::VP9::ProcessRtpPacket(packet);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::H264:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tCodecs::H264::ProcessRtpPacket(packet, templateDependencyStructure);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::AV1:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tCodecs::AV1::ProcessRtpPacket(packet, templateDependencyStructure);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::AUDIO:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::OPUS:\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::MULTIOPUS:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tCodecs::Opus::ProcessRtpPacket(packet);\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstatic bool IsValidTypeForCodec(\n\t\t\t\t  RTC::RtpParameters::Type type, const RTC::RtpCodecMimeType& mimeType)\n\t\t\t\t{\n\t\t\t\t\tswitch (type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RtpParameters::Type::SIMPLE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RtpParameters::Type::SIMULCAST:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.type)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP8:\n\t\t\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::H264:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RtpParameters::Type::SVC:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.type)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP9:\n\t\t\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::AV1:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RtpParameters::Type::PIPE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstatic EncodingContext* GetEncodingContext(\n\t\t\t\t  const RTC::RtpCodecMimeType& mimeType, Codecs::EncodingContext::Params& params)\n\t\t\t\t{\n\t\t\t\t\tswitch (mimeType.type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP8:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn new Codecs::VP8::EncodingContext(params);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::VP9:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn new Codecs::VP9::EncodingContext(params);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::AV1:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn new Codecs::AV1::EncodingContext(params);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::H264:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn new Codecs::H264::EncodingContext(params);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::AUDIO:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (mimeType.subtype)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::OPUS:\n\t\t\t\t\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::MULTIOPUS:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn new Codecs::Opus::EncodingContext(params);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/VP8.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_VP8_HPP\n#define MS_RTC_RTP_CODECS_VP8_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/SeqManager.hpp\"\n\n/* RFC 7741\n * VP8 Payload Descriptor\n\n  Single octet PictureID (M = 0)        Dual octet PictureID (M = 1)\n  ==============================        ============================\n\n      0 1 2 3 4 5 6 7                       0 1 2 3 4 5 6 7\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\n     |X|R|N|S|R| PID | (REQUIRED)          |X|R|N|S|R| PID | (REQUIRED)\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\nX:   |I|L|T|K| RSV   | (OPTIONAL)       X: |I|L|T|K| RSV   | (OPTIONAL)\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\nI:   |M| PictureID   | (OPTIONAL)       I: |M| PictureID   | (OPTIONAL)\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\nL:   |   TL0PICIDX   | (OPTIONAL)          |   PictureID   |\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\nT/K: |TID|Y| KEYIDX  | (OPTIONAL)       L: |   TL0PICIDX   | (OPTIONAL)\n     +-+-+-+-+-+-+-+-+                     +-+-+-+-+-+-+-+-+\n                                      T/K: |TID|Y| KEYIDX  | (OPTIONAL)\n                                           +-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass VP8\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct PayloadDescriptor : public Codecs::PayloadDescriptor\n\t\t\t\t{\n\t\t\t\t\tstruct EncodingData\n\t\t\t\t\t{\n\t\t\t\t\t\tuint16_t pictureId;\n\t\t\t\t\t\tuint8_t tl0PictureIndex;\n\t\t\t\t\t};\n\n\t\t\t\t\tstruct Encoder : public Codecs::PayloadDescriptor::Encoder\n\t\t\t\t\t{\n\t\t\t\t\t\t~Encoder() override = default;\n\t\t\t\t\t\texplicit Encoder(EncodingData encodingData) : encodingData(encodingData)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvoid Encode(uint8_t* data, const VP8::PayloadDescriptor* payloadDescriptor) const;\n\n\t\t\t\t\t\tEncodingData encodingData;\n\t\t\t\t\t};\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::PayloadDescriptor. */\n\t\t\t\t\t~PayloadDescriptor() override = default;\n\n\t\t\t\t\tvoid Dump(int indentation = 0) const override;\n\t\t\t\t\t// Rewrite the buffer with the given pictureId and tl0PictureIndex values.\n\t\t\t\t\tvoid Encode(uint8_t* data, uint16_t pictureId, uint8_t tl0PictureIndex) const;\n\t\t\t\t\tvoid Encode(uint8_t* data) const;\n\t\t\t\t\tvoid Restore(uint8_t* data) const;\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->encoder.has_value())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn std::make_unique<Encoder>(this->encoder.value());\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tvoid CreateEncoder(EncodingData encodingData)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->encoder = Encoder(encodingData);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Mandatory fields.\n\t\t\t\t\tuint8_t extended : 1;\n\t\t\t\t\tuint8_t nonReference : 1;\n\t\t\t\t\tuint8_t start : 1;\n\t\t\t\t\tuint8_t partitionIndex : 4;\n\t\t\t\t\t// Optional field flags.\n\t\t\t\t\tuint8_t i : 1; // PictureID present.\n\t\t\t\t\tuint8_t l : 1; // TL0PICIDX present.\n\t\t\t\t\tuint8_t t : 1; // TID present.\n\t\t\t\t\tuint8_t k : 1; // KEYIDX present.\n\t\t\t\t\t// Optional fields.\n\t\t\t\t\tuint16_t pictureId;\n\t\t\t\t\tuint8_t tl0PictureIndex;\n\t\t\t\t\tuint8_t tlIndex : 2;\n\t\t\t\t\tuint8_t y : 1;\n\t\t\t\t\tuint8_t keyIndex : 5;\n\t\t\t\t\t// Parsed values.\n\t\t\t\t\tbool isKeyFrame{ false };\n\t\t\t\t\tbool hasPictureId{ false };\n\t\t\t\t\tbool hasOneBytePictureId{ false };\n\t\t\t\t\tbool hasTwoBytesPictureId{ false };\n\t\t\t\t\tbool hasTl0PictureIndex{ false };\n\t\t\t\t\tbool hasTlIndex{ false };\n\n\t\t\t\t\tstd::optional<Encoder> encoder{ std::nullopt };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstatic VP8::PayloadDescriptor* Parse(const uint8_t* data, size_t len);\n\t\t\t\tstatic void ProcessRtpPacket(RTP::Packet* packet);\n\n\t\t\tpublic:\n\t\t\t\tclass EncodingContext : public Codecs::EncodingContext\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params)\n\t\t\t\t\t  : Codecs::EncodingContext(params)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\t~EncodingContext() override = default;\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::EncodingContext. */\n\t\t\t\tpublic:\n\t\t\t\t\tvoid SyncRequired() override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->syncRequired = true;\n\t\t\t\t\t}\n\n\t\t\t\tpublic:\n\t\t\t\t\tRTC::SeqManager<uint16_t, 15> pictureIdManager;\n\t\t\t\t\tRTC::SeqManager<uint8_t> tl0PictureIndexManager;\n\t\t\t\t\tbool syncRequired{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tclass PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor);\n\t\t\t\t\t~PayloadDescriptorHandler() override = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvoid Dump(int indentation = 0) const override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->Dump(indentation);\n\t\t\t\t\t}\n\t\t\t\t\tbool Process(\n\t\t\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override;\n\t\t\t\t\tvoid RtpPacketChanged(RTP::Packet* packet) override {};\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->GetEncoder();\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override;\n\t\t\t\t\tvoid Restore(RTP::Packet* packet) override;\n\t\t\t\t\tuint8_t GetSpatialLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0u;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetTemporalLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->hasTlIndex ? this->payloadDescriptor->tlIndex : 0u;\n\t\t\t\t\t}\n\t\t\t\t\tbool IsKeyFrame() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->isKeyFrame;\n\t\t\t\t\t}\n\n\t\t\t\tprivate:\n\t\t\t\t\tstd::unique_ptr<VP8::PayloadDescriptor> payloadDescriptor;\n\t\t\t\t};\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Codecs/VP9.hpp",
    "content": "#ifndef MS_RTC_RTP_CODECS_VP9_HPP\n#define MS_RTC_RTP_CODECS_VP9_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/SeqManager.hpp\"\n\n/* https://tools.ietf.org/html/draft-ietf-payload-vp9-06\n * VP9 Payload Descriptor\n\n   Flexible mode (with the F bit below set to 1)\n   =============================================\n\n      0 1 2 3 4 5 6 7\n     +-+-+-+-+-+-+-+-+\n     |I|P|L|F|B|E|V|-| (REQUIRED)\n     +-+-+-+-+-+-+-+-+\nI:   |M| PICTURE ID  | (REQUIRED)\n     +-+-+-+-+-+-+-+-+\nM:   | EXTENDED PID  | (RECOMMENDED)\n     +-+-+-+-+-+-+-+-+\nL:   | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)\n     +-+-+-+-+-+-+-+-+                             -\\\nP,F: | P_DIFF      |N| (CONDITIONALLY REQUIRED)    - up to 3 times\n     +-+-+-+-+-+-+-+-+                             -/\nV:   | SS            |\n     | ..            |\n     +-+-+-+-+-+-+-+-+\n\n   Non-flexible mode (with the F bit below set to 0)\n   =================================================\n\n      0 1 2 3 4 5 6 7\n     +-+-+-+-+-+-+-+-+\n     |I|P|L|F|B|E|V|-| (REQUIRED)\n     +-+-+-+-+-+-+-+-+\nI:   |M| PICTURE ID  | (RECOMMENDED)\n     +-+-+-+-+-+-+-+-+\nM:   | EXTENDED PID  | (RECOMMENDED)\n     +-+-+-+-+-+-+-+-+\nL:   | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)\n     +-+-+-+-+-+-+-+-+\n     |   TL0PICIDX   | (CONDITIONALLY REQUIRED)\n     +-+-+-+-+-+-+-+-+\nV:   | SS            |\n     | ..            |\n     +-+-+-+-+-+-+-+-+\n */\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\tclass VP9\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tstruct PayloadDescriptor : public Codecs::PayloadDescriptor\n\t\t\t\t{\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::PayloadDescriptor. */\n\t\t\t\t\t~PayloadDescriptor() override = default;\n\n\t\t\t\t\tvoid Dump(int indentation = 0) const override;\n\n\t\t\t\t\t// Header.\n\t\t\t\t\tuint8_t i : 1; // I: Picture ID (PID) present.\n\t\t\t\t\tuint8_t p : 1; // P: Inter-picture predicted layer frame.\n\t\t\t\t\tuint8_t l : 1; // L: Layer indices present.\n\t\t\t\t\tuint8_t f : 1; // F: Flexible mode.\n\t\t\t\t\tuint8_t b : 1; // B: Start of a layer frame.\n\t\t\t\t\tuint8_t e : 1; // E: End of a layer frame.\n\t\t\t\t\tuint8_t v : 1; // V: Scalability structure (SS) data present.\n\t\t\t\t\t// Extension fields.\n\t\t\t\t\tuint16_t pictureId{ 0 };\n\t\t\t\t\tuint8_t slIndex{ 0 };\n\t\t\t\t\tuint8_t tlIndex{ 0 };\n\t\t\t\t\tuint8_t tl0PictureIndex;\n\t\t\t\t\tuint8_t switchingUpPoint : 1;\n\t\t\t\t\tuint8_t interLayerDependency : 1;\n\t\t\t\t\t// Parsed values.\n\t\t\t\t\tbool isKeyFrame{ false };\n\t\t\t\t\tbool hasPictureId{ false };\n\t\t\t\t\tbool hasOneBytePictureId{ false };\n\t\t\t\t\tbool hasTwoBytesPictureId{ false };\n\t\t\t\t\tbool hasSlIndex{ false };\n\t\t\t\t\tbool hasTl0PictureIndex{ false };\n\t\t\t\t\tbool hasTlIndex{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tstatic VP9::PayloadDescriptor* Parse(const uint8_t* data, size_t len);\n\t\t\t\tstatic void ProcessRtpPacket(RTP::Packet* packet);\n\n\t\t\tpublic:\n\t\t\t\tclass EncodingContext : public Codecs::EncodingContext\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit EncodingContext(Codecs::EncodingContext::Params& params)\n\t\t\t\t\t  : Codecs::EncodingContext(params)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\t~EncodingContext() override = default;\n\n\t\t\t\t\t/* Pure virtual methods inherited from Codecs::EncodingContext. */\n\t\t\t\tpublic:\n\t\t\t\t\tvoid SyncRequired() override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->syncRequired = true;\n\t\t\t\t\t}\n\n\t\t\t\tpublic:\n\t\t\t\t\tRTC::SeqManager<uint16_t, 15> pictureIdManager;\n\t\t\t\t\tbool syncRequired{ false };\n\t\t\t\t};\n\n\t\t\t\tclass PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler\n\t\t\t\t{\n\t\t\t\tpublic:\n\t\t\t\t\texplicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor);\n\t\t\t\t\t~PayloadDescriptorHandler() override = default;\n\n\t\t\t\tpublic:\n\t\t\t\t\tvoid Dump(int indentation = 0) const override\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->payloadDescriptor->Dump(indentation);\n\t\t\t\t\t}\n\t\t\t\t\tbool Process(\n\t\t\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override;\n\t\t\t\t\tvoid RtpPacketChanged(RTP::Packet* packet) override {};\n\t\t\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\t\t\t\t\tvoid Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tvoid Restore(RTP::Packet* packet) override\n\t\t\t\t\t{\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetSpatialLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->hasSlIndex ? this->payloadDescriptor->slIndex : 0u;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t GetTemporalLayer() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->hasTlIndex ? this->payloadDescriptor->tlIndex : 0u;\n\t\t\t\t\t}\n\t\t\t\t\tbool IsKeyFrame() const override\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->payloadDescriptor->isKeyFrame;\n\t\t\t\t\t}\n\n\t\t\t\tprivate:\n\t\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor;\n\t\t\t\t};\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/HeaderExtensionIds.hpp",
    "content": "#ifndef MS_RTC_RTP_HEADER_EXTENSION_IDS_HPP\n#define MS_RTC_RTP_HEADER_EXTENSION_IDS_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t// RTP header extension ids. Some of these are shared by all Producers using\n\t\t// the same Transport. Others are different for each Producer.\n\t\tstruct HeaderExtensionIds\n\t\t{\n\t\t\t// 0 means no id.\n\t\t\tuint8_t mid{ 0u };\n\t\t\tuint8_t rid{ 0u };\n\t\t\tuint8_t rrid{ 0u };\n\t\t\tuint8_t absSendTime{ 0u };\n\t\t\tuint8_t transportWideCc01{ 0u };\n\t\t\tuint8_t ssrcAudioLevel{ 0u };\n\t\t\tuint8_t dependencyDescriptor{ 0u };\n\t\t\tuint8_t videoOrientation{ 0u };\n\t\t\tuint8_t timeOffset{ 0u };\n\t\t\tuint8_t absCaptureTime{ 0u };\n\t\t\tuint8_t playoutDelay{ 0u };\n\t\t\tuint8_t mediasoupPacketId{ 0u };\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/Packet.hpp",
    "content": "#ifndef MS_RTC_RTP_PACKET_HPP\n#define MS_RTC_RTP_PACKET_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"FBS/rtpPacket.h\"\n#include \"RTC/RTP/Codecs/DependencyDescriptor.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include \"RTC/Serializable.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <flatbuffers/flatbuffers.h>\n#include <array>\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/**\n\t\t * RTP Packet.\n\t\t *\n\t\t * @see RFC 3550.\n\t\t */\n\t\tclass Packet : public Serializable, public Codecs::DependencyDescriptor::Listener\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * RTP Fixed Header.\n\t\t\t *\n\t\t\t * @see RFC 3550.\n\t\t\t *\n\t\t\t *  0                   1                   2                   3\n\t\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |V=2|P|X|  CC   |M|     PT      |       sequence number         |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |                           timestamp                           |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |           synchronization source (SSRC) identifier            |\n\t\t\t * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+\n\t\t\t * |            contributing source (CSRC) identifiers             |\n\t\t\t * |                             ....                              |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct FixedHeader\n\t\t\t{\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t csrcCount : 4;\n\t\t\t\tuint8_t extension : 1;\n\t\t\t\tuint8_t padding : 1;\n\t\t\t\tuint8_t version : 2;\n\t\t\t\tuint8_t payloadType : 7;\n\t\t\t\tuint8_t marker : 1;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t version : 2;\n\t\t\t\tuint8_t padding : 1;\n\t\t\t\tuint8_t extension : 1;\n\t\t\t\tuint8_t csrcCount : 4;\n\t\t\t\tuint8_t marker : 1;\n\t\t\t\tuint8_t payloadType : 7;\n#endif\n\t\t\t\tuint16_t sequenceNumber;\n\t\t\t\tuint32_t timestamp;\n\t\t\t\tuint32_t ssrc;\n\t\t\t};\n\n#ifdef MS_TEST\n\t\tpublic:\n#else\n\t\tprivate:\n#endif\n\t\t\t/**\n\t\t\t * RTP Header Extension.\n\t\t\t *\n\t\t\t * @see RFC 3350.\n\t\t\t *\n\t\t\t *  0                   1                   2                   3\n\t\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |      defined by profile       |           length              |\n\t\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t\t * |                        header extension                       |\n\t\t\t * |                             ....                              |\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct HeaderExtension\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * Defined by profile.\n\t\t\t\t */\n\t\t\t\tuint16_t id;\n\t\t\t\t/**\n\t\t\t\t * Number of 32-bit words in the extension, excluding the id & length\n\t\t\t\t * four-octet.\n\t\t\t\t */\n\t\t\t\tuint16_t len;\n\t\t\t\tuint8_t value[];\n\t\t\t};\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * One-Byte and Two-Bytes Extension types.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tenum class ExtensionsType : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * Auto means that One-Byte or Two-Bytes is choosen based on given\n\t\t\t\t * Extensions.\n\t\t\t\t */\n\t\t\t\tAuto     = 0,\n\t\t\t\tOneByte  = 1,\n\t\t\t\tTwoBytes = 2\n\t\t\t};\n\n#ifdef MS_TEST\n\t\tpublic:\n#else\n\t\tprivate:\n#endif\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte.\n\t\t\t */\n\t\t\tstruct OneByteExtension\n\t\t\t{\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t len : 4;\n\t\t\t\tuint8_t id : 4;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t id : 4;\n\t\t\t\tuint8_t len : 4;\n#endif\n\t\t\t\tuint8_t value[];\n\t\t\t};\n\n#ifdef MS_TEST\n\t\tpublic:\n#else\n\t\tprivate:\n#endif\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte.\n\t\t\t */\n\t\t\tstruct TwoBytesExtension\n\t\t\t{\n\t\t\t\tuint8_t id;\n\t\t\t\tuint8_t len;\n\t\t\t\tuint8_t value[];\n\t\t\t};\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct for setting and replacing Extensions.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is NOT guaranteed to be aligned to any fixed number of\n\t\t\t *   bytes because it contains a pointer, which is 4 or 8 bytes depending\n\t\t\t *   on the architecture. Anyway we never cast any buffer to this struct.\n\t\t\t */\n\t\t\tstruct Extension\n\t\t\t{\n\t\t\t\tExtension(RTC::RtpHeaderExtensionUri::Type type, uint8_t id, uint8_t len, uint8_t* value)\n\t\t\t\t  : type(type), id(id), len(len), value(value) {};\n\n\t\t\t\tRTC::RtpHeaderExtensionUri::Type type;\n\t\t\t\tuint8_t id;\n\t\t\t\tuint8_t len;\n\t\t\t\tuint8_t* value;\n\t\t\t};\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Minimum size (in bytes) of the RTP Fixed Header (without CSRC fields).\n\t\t\t */\n\t\t\tstatic const size_t FixedHeaderMinLength{ 12 };\n\n\t\t\t/**\n\t\t\t * Whether given buffer could be a valid RTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Before calling this static method, the caller should verify whether\n\t\t\t *   the given buffer is a RTCP packet.\n\t\t\t */\n\t\t\tstatic bool IsRtp(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Parse a RTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `packetLength` must be the exact length of the Packet.\n\t\t\t * - `bufferLength` must be >= `packetLength`.\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If `bufferLength` is lower than\n\t\t\t *   `packetLength`.\n\t\t\t */\n\t\t\tstatic Packet* Parse(const uint8_t* buffer, size_t packetLength, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Parse a RTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the Packet.\n\t\t\t */\n\t\t\tstatic Packet* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a RTP Packet.\n\t\t\t */\n\t\t\tstatic Packet* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Generate value for \"urn:mediasoup:params:rtp-hdrext:packet-id\"\n\t\t\t * mediasoup custom Extension.\n\t\t\t */\n\t\t\tstatic uint32_t GetNextMediasoupPacketId();\n\n\t\tprivate:\n\t\t\tstatic thread_local uint32_t nextMediasoupPacketId;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Constructor is private because we only want to create Packet instances\n\t\t\t * via Parse() and Factory().\n\t\t\t */\n\t\t\tPacket(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~Packet() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tPacket* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tflatbuffers::Offset<FBS::RtpPacket::Dump> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t\tuint8_t GetVersion() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer()->version;\n\t\t\t}\n\n\t\t\tuint8_t GetPayloadType() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer()->payloadType;\n\t\t\t}\n\n\t\t\tvoid SetPayloadType(uint8_t payloadType);\n\n\t\t\tbool HasMarker() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer()->marker;\n\t\t\t}\n\n\t\t\tvoid SetMarker(bool marker);\n\n\t\t\tuint16_t GetSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn ntohs(GetFixedHeaderPointer()->sequenceNumber);\n\t\t\t}\n\n\t\t\tvoid SetSequenceNumber(uint16_t seq);\n\n\t\t\tuint32_t GetTimestamp() const\n\t\t\t{\n\t\t\t\treturn ntohl(GetFixedHeaderPointer()->timestamp);\n\t\t\t}\n\n\t\t\tvoid SetTimestamp(uint32_t timestamp);\n\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn ntohl(GetFixedHeaderPointer()->ssrc);\n\t\t\t}\n\n\t\t\tvoid SetSsrc(uint32_t ssrc);\n\n\t\t\tbool HasCsrcs() const\n\t\t\t{\n\t\t\t\treturn (GetFixedHeaderPointer()->csrcCount != 0);\n\t\t\t}\n\n\t\t\tbool HasHeaderExtension() const\n\t\t\t{\n\t\t\t\treturn (GetFixedHeaderPointer()->extension);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Get the Extension Header id or 0 if there isn't.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether there is indeed space for the\n\t\t\t *   announced Header Extension.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tuint16_t GetHeaderExtensionId() const\n\t\t\t{\n\t\t\t\tif (!HasHeaderExtension())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn ntohs(GetHeaderExtensionPointer()->id);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the Header Extension value or `nullptr` if there is no\n\t\t\t * Header Extension or its has no value.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether there is indeed space for the\n\t\t\t *   announced Header Extension.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tuint8_t* GetHeaderExtensionValue() const\n\t\t\t{\n\t\t\t\tauto headerExtensionValueLength = GetHeaderExtensionValueLength();\n\n\t\t\t\tif (headerExtensionValueLength == 0)\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn GetHeaderExtensionPointer()->value;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Length of the Header Extension value (excluding the id & length\n\t\t\t * four-octet).\n\t\t\t */\n\t\t\tsize_t GetHeaderExtensionValueLength() const\n\t\t\t{\n\t\t\t\tif (!HasHeaderExtension())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn static_cast<size_t>(ntohs(GetHeaderExtensionPointer()->len) * 4);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Remove the Header Extension.\n\t\t\t */\n\t\t\tvoid RemoveHeaderExtension();\n\n\t\t\t/**\n\t\t\t * Whether the Packet has One-Byte or Two-Bytes Extensions.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tbool HasExtensions() const\n\t\t\t{\n\t\t\t\treturn HasOneByteExtensions() || HasTwoBytesExtensions();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether the Packet has One-Byte Extensions.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tbool HasOneByteExtensions() const\n\t\t\t{\n\t\t\t\treturn GetHeaderExtensionId() == 0xBEDE;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether the Packet has Two-Bytes Extensions.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tbool HasTwoBytesExtensions() const\n\t\t\t{\n\t\t\t\treturn (GetHeaderExtensionId() & 0b1111111111110000) == 0b0001000000000000;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether the One-Byte or Two-Bytes Extension with given `id` exists in\n\t\t\t * the Packet.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - If the length of the Extension value is 0 this method returns `true`.\n\t\t\t */\n\t\t\tbool HasExtension(uint8_t id) const\n\t\t\t{\n\t\t\t\tif (id == 0)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telse if (HasOneByteExtensions())\n\t\t\t\t{\n\t\t\t\t\tif (id > 14)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\t// `-1` because we have 14 elements total 0..13 and `id` is in the\n\t\t\t\t\t// range 1..14.\n\t\t\t\t\tconst auto offset = this->oneByteExtensions[id - 1];\n\n\t\t\t\t\treturn offset != -1;\n\t\t\t\t}\n\t\t\t\telse if (HasTwoBytesExtensions())\n\t\t\t\t{\n\t\t\t\t\treturn this->twoBytesExtensions.find(id) != this->twoBytesExtensions.end();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Get a pointer to the value of the the One-Byte or Two-Bytes Extension\n\t\t\t * with given `id` and set its value length into given `len`.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tuint8_t* GetExtensionValue(uint8_t id, uint8_t& len) const\n\t\t\t{\n\t\t\t\tlen = 0;\n\n\t\t\t\tif (id == 0)\n\t\t\t\t{\n\t\t\t\t\tlen = 0;\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\t\t\t\telse if (HasOneByteExtensions())\n\t\t\t\t{\n\t\t\t\t\tif (id > 14)\n\t\t\t\t\t{\n\t\t\t\t\t\tlen = 0;\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\t// `-1` because we have 14 elements total 0..13 and `id` is in the\n\t\t\t\t\t// range 1..14.\n\t\t\t\t\tconst auto offset = this->oneByteExtensions[id - 1];\n\n\t\t\t\t\tif (offset == -1)\n\t\t\t\t\t{\n\t\t\t\t\t\tlen = 0;\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto* extension = reinterpret_cast<OneByteExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\t\t// In One-Byte Extensions value length 0 means 1.\n\t\t\t\t\tlen = extension->len + 1;\n\n\t\t\t\t\treturn extension->value;\n\t\t\t\t}\n\t\t\t\telse if (HasTwoBytesExtensions())\n\t\t\t\t{\n\t\t\t\t\tconst auto it = this->twoBytesExtensions.find(id);\n\n\t\t\t\t\tif (it == this->twoBytesExtensions.end())\n\t\t\t\t\t{\n\t\t\t\t\t\tlen = 0;\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst auto offset = it->second;\n\n\t\t\t\t\tauto* extension = reinterpret_cast<TwoBytesExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\t\tlen = extension->len;\n\n\t\t\t\t\treturn extension->value;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tlen = 0;\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Add or replace Extensions.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If there is no space available for given\n\t\t\t *   Extensions or if given Extensions are invalid/wrong.\n\t\t\t */\n\t\t\tvoid SetExtensions(ExtensionsType type, const std::vector<Extension>& extensions);\n\n\t\t\t/**\n\t\t\t * Assign Extension ids.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tvoid AssignExtensionIds(RTP::HeaderExtensionIds& headerExtensionIds);\n\n\t\t\tbool ReadMid(std::string& mid) const;\n\n\t\t\tbool UpdateMid(const std::string& mid);\n\n\t\t\tbool ReadRid(std::string& rid) const;\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - `absSendTime` is set with the raw 3 bytes unsigned integer stored\n\t\t\t *   in the Extension value.\n\t\t\t */\n\t\t\tbool ReadAbsSendTime(uint32_t& absSendtime) const;\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Contrary to `ReadAbsSendTime()` method, given `ms` is internally\n\t\t\t *   converted to ABS Send Time.\n\t\t\t */\n\t\t\tbool UpdateAbsSendTime(uint64_t ms) const;\n\n\t\t\tbool ReadTransportWideCc01(uint16_t& wideSeqNumber) const;\n\n\t\t\tbool UpdateTransportWideCc01(uint16_t wideSeqNumber) const;\n\n\t\t\tbool ReadSsrcAudioLevel(uint8_t& volume, bool& voice) const;\n\n\t\t\tbool ReadDependencyDescriptor(\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor,\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t\t\t    templateDependencyStructure) const;\n\n\t\t\tbool UpdateDependencyDescriptor(const uint8_t* data, size_t len);\n\n\t\t\tbool ReadVideoOrientation(bool& camera, bool& flip, uint16_t& rotation) const;\n\n\t\t\tbool ReadAbsCaptureTime(uint64_t& absCaptureTimestamp, int64_t& estimatedCaptureClockOffset) const;\n\n\t\t\tbool ReadPlayoutDelay(uint16_t& minDelay, uint16_t& maxDelay) const;\n\n\t\t\tbool ReadMediasoupPacketId(uint32_t& mediasoupPacketId) const;\n\n\t\t\t/**\n\t\t\t * Whether this Packet has payload.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether the padding length announced in\n\t\t\t *   the last byte of the Packet is valid.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tbool HasPayload() const\n\t\t\t{\n\t\t\t\treturn GetPayloadLength() > 0;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the beginning of the payload (if any). Payload length is\n\t\t\t * set into given `length`.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether the padding length announced in\n\t\t\t *   the last byte of the Packet is valid.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t * - This method doens't take into account padding, so in a padding-only\n\t\t\t *   Packet this method returns `nullptr` with `len` 0.\n\t\t\t */\n\t\t\tuint8_t* GetPayload(size_t& len) const\n\t\t\t{\n\t\t\t\tauto* payloadPointer = GetPayloadPointer();\n\t\t\t\tconst size_t availablePayloadAndPaddingLength = GetLength() - (payloadPointer - GetBuffer());\n\t\t\t\tconst auto paddingLength = GetPaddingLength();\n\n\t\t\t\t// If there is announced padding, compute effective payload length\n\t\t\t\t// without padding.\n\t\t\t\tif (availablePayloadAndPaddingLength > paddingLength)\n\t\t\t\t{\n\t\t\t\t\tlen = availablePayloadAndPaddingLength - paddingLength;\n\n\t\t\t\t\treturn payloadPointer;\n\t\t\t\t}\n\t\t\t\t// If there are more announced padding bytes than the available length\n\t\t\t\t// for payload and padding, assume payload length 0.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tlen = 0;\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the beginning of the payload (if any).\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether the padding length announced in\n\t\t\t *   the last byte of the Packet is valid.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t * - This method doens't take into account padding, so in a padding-only\n\t\t\t *   Packet this method returns `nullptr`.\n\t\t\t */\n\t\t\tuint8_t* GetPayload() const\n\t\t\t{\n\t\t\t\tthread_local size_t len;\n\n\t\t\t\treturn GetPayload(len);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Length of the payload excluding padding bytes.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether the padding length announced in\n\t\t\t *   the last byte of the Packet is valid.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tsize_t GetPayloadLength() const\n\t\t\t{\n\t\t\t\tconst size_t availablePayloadAndPaddingLength =\n\t\t\t\t  GetLength() - (GetPayloadPointer() - GetBuffer());\n\t\t\t\tconst auto paddingLength = GetPaddingLength();\n\n\t\t\t\t// If there is announced padding, compute effective payload length\n\t\t\t\t// without padding.\n\t\t\t\tif (availablePayloadAndPaddingLength >= paddingLength)\n\t\t\t\t{\n\t\t\t\t\treturn availablePayloadAndPaddingLength - paddingLength;\n\t\t\t\t}\n\t\t\t\t// If there are more announced padding bytes than the available length\n\t\t\t\t// for payload and padding, return 0.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Set the payload. It copies the given `payload` into the Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If given `payloadLength` is higher than\n\t\t\t *   available length for the payload of if `nullptr` is given as\n\t\t\t *   `payload` with `payloadLength` higher than 0.\n\t\t\t */\n\t\t\tvoid SetPayload(const uint8_t* payload, size_t payloadLength);\n\n\t\t\t/**\n\t\t\t * Set the payload length.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If given `payloadLength` is higher than\n\t\t\t *   available length for the payload.\n\t\t\t */\n\t\t\tvoid SetPayloadLength(size_t payloadLength);\n\n\t\t\t/**\n\t\t\t * Remove the payload.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t */\n\t\t\tvoid RemovePayload()\n\t\t\t{\n\t\t\t\tSetPayload(nullptr, 0);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Shift or unshift the content of the payload `delta` bytes after\n\t\t\t * `payloadOffset`.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If wrong values are given.\n\t\t\t */\n\t\t\tvoid ShiftPayload(size_t payloadOffset, int32_t delta);\n\n\t\t\t/**\n\t\t\t * Whether this Packet has padding.\n\t\t\t */\n\t\t\tbool HasPadding() const\n\t\t\t{\n\t\t\t\treturn (GetFixedHeaderPointer()->padding);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Length of the padding.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether the padding length announced in\n\t\t\t *   the last byte of the Packet is valid.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tuint8_t GetPaddingLength() const\n\t\t\t{\n\t\t\t\tif (!HasPadding())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get1Byte(GetBuffer(), GetLength() - 1);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Set padding length.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If given `paddingLength` is higher than\n\t\t\t *   available length for the padding.\n\t\t\t */\n\t\t\tvoid SetPaddingLength(uint8_t paddingLength);\n\n\t\t\t/**\n\t\t\t * Whether Packet length is padded to 4 bytes.\n\t\t\t */\n\t\t\tbool IsPaddedTo4Bytes() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::IsPaddedTo4Bytes(GetLength());\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pad Packet length to 4 bytes by modifying padding bytes.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding and adds up to 3 bytes of\n\t\t\t *   padding bytes if needed.\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If needed padding length is higher than\n\t\t\t *   available length for the padding.\n\t\t\t */\n\t\t\tvoid PadTo4Bytes();\n\n\t\t\t/**\n\t\t\t * Encode the Packet into a RTX Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If there is no space for the new computed\n\t\t\t *   payload.\n\t\t\t */\n\t\t\tvoid RtxEncode(uint8_t payloadType, uint32_t ssrc, uint16_t seq);\n\n\t\t\t/**\n\t\t\t * Decode the RTX Packet into a regular RTP Packet.\n\t\t\t *\n\t\t\t * @return `true` if RTX decoding iwas done.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method removes existing padding (if any).\n\t\t\t */\n\t\t\tbool RtxDecode(uint8_t payloadType, uint32_t ssrc);\n\n\t\t\t/**\n\t\t\t * Set payload descritor handler.\n\t\t\t */\n\t\t\tvoid SetPayloadDescriptorHandler(Codecs::PayloadDescriptorHandler* payloadDescriptorHandler);\n\n\t\t\t/**\n\t\t\t * Process the payload.\n\t\t\t */\n\t\t\tbool ProcessPayload(Codecs::EncodingContext* context, bool& marker);\n\n\t\t\t/**\n\t\t\t * Get the payload encoder.\n\t\t\t */\n\t\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> GetPayloadEncoder() const;\n\n\t\t\t/**\n\t\t\t * Encode the payload.\n\t\t\t */\n\t\t\tvoid EncodePayload(Codecs::PayloadDescriptor::Encoder* encoder);\n\n\t\t\t/**\n\t\t\t * Restore the payload.\n\t\t\t */\n\t\t\tvoid RestorePayload();\n\n\t\t\t/**\n\t\t\t * Whether the payload contains a video keyframe.\n\t\t\t */\n\t\t\tbool IsKeyFrame() const\n\t\t\t{\n\t\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn this->payloadDescriptorHandler->IsKeyFrame();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Spatial layer of the video codec.\n\t\t\t */\n\t\t\tuint8_t GetSpatialLayer() const\n\t\t\t{\n\t\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t\t{\n\t\t\t\t\treturn 0u;\n\t\t\t\t}\n\n\t\t\t\treturn this->payloadDescriptorHandler->GetSpatialLayer();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Temporal layer of the video codec.\n\t\t\t */\n\t\t\tuint8_t GetTemporalLayer() const\n\t\t\t{\n\t\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t\t{\n\t\t\t\t\treturn 0u;\n\t\t\t\t}\n\n\t\t\t\treturn this->payloadDescriptorHandler->GetTemporalLayer();\n\t\t\t}\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Returns FixedHeader* instead of const FixedHeader* since we may want\n\t\t\t *   to modify its fields.\n\t\t\t */\n\t\t\tFixedHeader* GetFixedHeaderPointer() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<FixedHeader*>(const_cast<uint8_t*>(GetBuffer()));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the location where the CSRC list is supposed to begin.\n\t\t\t */\n\t\t\tuint8_t* GetCsrcsPointer() const\n\t\t\t{\n\t\t\t\treturn const_cast<uint8_t*>(GetBuffer()) + Packet::FixedHeaderMinLength;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Number of entries in the CSRC list.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether there is indeed space for the\n\t\t\t *   announced CSRC list.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tsize_t GetCsrcCount() const\n\t\t\t{\n\t\t\t\treturn GetFixedHeaderPointer()->csrcCount * sizeof(GetFixedHeaderPointer()->ssrc);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the location where Extension Header is supposed to begin.\n\t\t\t */\n\t\t\tHeaderExtension* GetHeaderExtensionPointer() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<HeaderExtension*>(GetCsrcsPointer() + GetCsrcCount());\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Length of the Header Extension including the id & length four-octet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method doesn't validate whether there is indeed space for the\n\t\t\t *   announced Header Extension.\n\t\t\t * - This method is guaranteed to return valid value once @ref Validate()\n\t\t\t *   was succesfully called.\n\t\t\t */\n\t\t\tsize_t GetHeaderExtensionLength() const\n\t\t\t{\n\t\t\t\tif (!HasHeaderExtension())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn 4 + static_cast<size_t>(ntohs(GetHeaderExtensionPointer()->len) * 4);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Pointer to the location where the payload is supposed to begin.\n\t\t\t */\n\t\t\tuint8_t* GetPayloadPointer() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<uint8_t*>(GetHeaderExtensionPointer()) + GetHeaderExtensionLength();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Validates whether the Packet is valid. It also stores internal\n\t\t\t * containers holding Extensions if `storeExtensions` is `true`.\n\t\t\t */\n#ifdef MS_TEST\n\t\tpublic:\n#endif\n\t\t\tbool Validate(bool storeExtensions);\n#ifdef MS_TEST\n\t\tprivate:\n#endif\n\n\t\t\t/**\n\t\t\t * Parses Extensions. Returns `true` if they are valid. It also stores\n\t\t\t * internal containers holding Extensions if `storeExtensions` is `true`.\n\t\t\t *\n\t\t\t * @see RFC 8285.\n\t\t\t */\n\t\t\tbool ParseExtensions(bool storeExtensions);\n\n\t\t\t/**\n\t\t\t * Set the value length of the Extension with given `id`.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The caller is responsible of not setting a length higher than the\n\t\t\t *   available one (taking into account existing padding bytes).\n\t\t\t * - If the Extension with `id` doesn't exist this methods terminates\n\t\t\t *   the process.\n\t\t\t */\n\t\t\tvoid SetExtensionLength(uint8_t id, uint8_t len);\n\n\t\t\t/* Pure virtual methods inherited from Codecs::DependencyDescriptor::Listener. */\n\t\tpublic:\n\t\t\tvoid OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override;\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpublic:\n\t\t\tRtcLogger::RtpPacket logger;\n#endif\n\n\t\tprivate:\n\t\t\t// Array of One Byte Extensions. Index is the id - 1 of the Extension,\n\t\t\t// each entry is the offset (in bytes) from the beginning of the Header\n\t\t\t// Extension value to the beginning of the Extension.\n\t\t\tstd::array<ssize_t, 14> oneByteExtensions{ -1, -1, -1, -1, -1, -1, -1,\n\t\t\t\t                                         -1, -1, -1, -1, -1, -1, -1 };\n\t\t\t// Ordered map of Two Bytes Extensions. Key is the id 1 of the Extension,\n\t\t\t// each entry is the offset (in bytes) from the beginning of the Header\n\t\t\t// Extension value to the beginning of the Extension.\n\t\t\tstd::map<uint8_t, ssize_t> twoBytesExtensions;\n\t\t\t// Extension ids.\n\t\t\tRTP::HeaderExtensionIds headerExtensionIds{};\n\t\t\t// Codec related.\n\t\t\tstd::shared_ptr<Codecs::PayloadDescriptorHandler> payloadDescriptorHandler;\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/ProbationGenerator.hpp",
    "content": "#ifndef MS_RTC_RTP_PROBATION_GENERATOR_HPP\n#define MS_RTC_RTP_PROBATION_GENERATOR_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass ProbationGenerator\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Maximum length of a probation RTP Packet.\n\t\t\t */\n\t\t\tstatic const size_t ProbationPacketMaxLength{ 1400 };\n\t\t\t/**\n\t\t\t * SSRC of the probation RTP stream.\n\t\t\t */\n\t\t\tstatic const uint32_t Ssrc{ 1234 };\n\t\t\t/**\n\t\t\t * Codec payload type of the probation RTP stream.\n\t\t\t */\n\t\t\tstatic const uint8_t PayloadType{ 127u };\n\n\t\tpublic:\n\t\t\texplicit ProbationGenerator();\n\t\t\t~ProbationGenerator();\n\n\t\tpublic:\n\t\t\tRTP::Packet* GetNextPacket(size_t len);\n\n\t\t\tsize_t GetProbationPacketMinLength() const\n\t\t\t{\n\t\t\t\treturn this->probationPacketMinLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\t// Allocated by this.\n\t\t\tstd::unique_ptr<RTP::Packet> probationPacket;\n\t\t\t// Others.\n\t\t\t// The length of the probation RTP Packet without payload or padding.\n\t\t\tsize_t probationPacketMinLength{ 0 };\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/RetransmissionBuffer.hpp",
    "content": "#ifndef MS_RTC_RTP_RETRANSMISSION_BUFFER_HPP\n#define MS_RTC_RTP_RETRANSMISSION_BUFFER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include <deque>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t// Special container that stores `Item`* elements addressable by their `uint16_t`\n\t\t// sequence number, while only taking as little memory as necessary to store\n\t\t// the range covering a maximum of `MaxRetransmissionDelayForVideoMs` or\n\t\t//  `MaxRetransmissionDelayForAudioMs` ms.\n\t\tclass RetransmissionBuffer\n\t\t{\n\t\tpublic:\n\t\t\tstruct Item\n\t\t\t{\n\t\t\t\tvoid Reset();\n\n\t\t\t\t// Original packet.\n\t\t\t\tRTP::SharedPacket sharedPacket{ nullptr };\n\t\t\t\t// Payload descriptor encoder.\n\t\t\t\tstd::unique_ptr<RTP::Codecs::PayloadDescriptor::Encoder> encoder{ nullptr };\n\t\t\t\t// Correct SSRC since original packet may not have the same.\n\t\t\t\tuint32_t ssrc{ 0u };\n\t\t\t\t// Correct sequence number since original packet may not have the same.\n\t\t\t\tuint16_t sequenceNumber{ 0u };\n\t\t\t\t// Correct timestamp since original packet may not have the same.\n\t\t\t\tuint32_t timestamp{ 0u };\n\t\t\t\t// Correct marker bit since original packet may not have the same.\n\t\t\t\tbool marker{ false };\n\t\t\t\t// Last time this packet was resent.\n\t\t\t\tuint64_t resentAtMs{ 0u };\n\t\t\t\t// Number of times this packet was resent.\n\t\t\t\tuint8_t sentTimes{ 0u };\n\t\t\t};\n\n\t\tprivate:\n\t\t\tstatic Item* FillItem(Item* item, RTP::Packet* packet, const RTP::SharedPacket& sharedPacket);\n\n\t\tpublic:\n\t\t\tRetransmissionBuffer(uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate);\n\t\t\t~RetransmissionBuffer();\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t\tItem* Get(uint16_t seq) const;\n\t\t\tbool Insert(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket);\n\t\t\tvoid Clear();\n\n\t\tprivate:\n\t\t\tItem* GetOldest() const;\n\t\t\tItem* GetNewest() const;\n\t\t\tvoid RemoveOldest();\n\t\t\tvoid RemoveOldest(uint16_t numItems);\n\t\t\tbool ClearTooOldByTimestamp(uint32_t newestTimestamp);\n\t\t\tbool IsTooOldTimestamp(uint32_t timestamp, uint32_t newestTimestamp) const;\n\n\t\tprotected:\n\t\t\t// Make buffer protected for testing purposes.\n\t\t\tstd::deque<Item*> buffer;\n\n\t\tprivate:\n\t\t\t// Given as argument.\n\t\t\tuint16_t maxItems;\n\t\t\tuint32_t maxRetransmissionDelayMs;\n\t\t\tuint32_t clockRate;\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/RtpStream.hpp",
    "content": "#ifndef MS_RTC_RTP_RTP_STREAM_HPP\n#define MS_RTC_RTP_RTP_STREAM_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"FBS/rtpStream.h\"\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n#include \"RTC/RTCP/FeedbackPsPli.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTCP/Sdes.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtxStream.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass RtpStream\n\t\t{\n\t\tprotected:\n\t\t\tclass Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~Listener() = default;\n\n\t\t\tpublic:\n\t\t\t\tvirtual void OnRtpStreamScore(\n\t\t\t\t  RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstruct Params\n\t\t\t{\n\t\t\t\tflatbuffers::Offset<FBS::RtpStream::Params> FillBuffer(\n\t\t\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t\t\tsize_t encodingIdx{ 0u };\n\t\t\t\tuint32_t ssrc{ 0u };\n\t\t\t\tuint8_t payloadType{ 0u };\n\t\t\t\tRTC::RtpCodecMimeType mimeType;\n\t\t\t\tuint32_t clockRate{ 0u };\n\t\t\t\tstd::string rid;\n\t\t\t\tstd::string cname;\n\t\t\t\tuint32_t rtxSsrc{ 0u };\n\t\t\t\tuint8_t rtxPayloadType{ 0u };\n\t\t\t\tbool useNack{ false };\n\t\t\t\tbool usePli{ false };\n\t\t\t\tbool useFir{ false };\n\t\t\t\tbool useInBandFec{ false };\n\t\t\t\tbool useDtx{ false };\n\t\t\t\tuint8_t spatialLayers{ 1u };\n\t\t\t\tuint8_t temporalLayers{ 1u };\n\t\t\t};\n\n\t\tpublic:\n\t\t\tRtpStream(\n\t\t\t  RTP::RtpStream::Listener* listener,\n\t\t\t  SharedInterface* shared,\n\t\t\t  RTP::RtpStream::Params& params,\n\t\t\t  uint8_t initialScore);\n\t\t\tvirtual ~RtpStream();\n\n\t\t\tflatbuffers::Offset<FBS::RtpStream::Dump> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\t\t\tvirtual flatbuffers::Offset<FBS::RtpStream::Stats> FillBufferStats(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\t\tuint32_t GetEncodingIdx() const\n\t\t\t{\n\t\t\t\treturn this->params.encodingIdx;\n\t\t\t}\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->params.ssrc;\n\t\t\t}\n\t\t\tuint8_t GetPayloadType() const\n\t\t\t{\n\t\t\t\treturn this->params.payloadType;\n\t\t\t}\n\t\t\tconst RTC::RtpCodecMimeType& GetMimeType() const\n\t\t\t{\n\t\t\t\treturn this->params.mimeType;\n\t\t\t}\n\t\t\tuint32_t GetClockRate() const\n\t\t\t{\n\t\t\t\treturn this->params.clockRate;\n\t\t\t}\n\t\t\tconst std::string& GetRid() const\n\t\t\t{\n\t\t\t\treturn this->params.rid;\n\t\t\t}\n\t\t\tconst std::string& GetCname() const\n\t\t\t{\n\t\t\t\treturn this->params.cname;\n\t\t\t}\n\t\t\tbool HasRtx() const\n\t\t\t{\n\t\t\t\treturn this->rtxStream != nullptr;\n\t\t\t}\n\t\t\tvirtual void SetRtx(uint8_t payloadType, uint32_t ssrc);\n\t\t\tuint32_t GetRtxSsrc() const\n\t\t\t{\n\t\t\t\treturn this->params.rtxSsrc;\n\t\t\t}\n\t\t\tuint8_t GetRtxPayloadType() const\n\t\t\t{\n\t\t\t\treturn this->params.rtxPayloadType;\n\t\t\t}\n\t\t\tuint8_t GetSpatialLayers() const\n\t\t\t{\n\t\t\t\treturn this->params.spatialLayers;\n\t\t\t}\n\t\t\tbool HasDtx() const\n\t\t\t{\n\t\t\t\treturn this->params.useDtx;\n\t\t\t}\n\t\t\tuint8_t GetTemporalLayers() const\n\t\t\t{\n\t\t\t\treturn this->params.temporalLayers;\n\t\t\t}\n\t\t\tvirtual bool ReceiveStreamPacket(const RTP::Packet* packet);\n\t\t\tvirtual void Pause()                                                                     = 0;\n\t\t\tvirtual void Resume()                                                                    = 0;\n\t\t\tvirtual uint32_t GetBitrate(uint64_t nowMs)                                              = 0;\n\t\t\tvirtual uint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) = 0;\n\t\t\tvirtual uint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer)            = 0;\n\t\t\tvirtual uint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) = 0;\n\t\t\tvoid ResetScore(uint8_t score, bool notify);\n\t\t\tuint8_t GetFractionLost() const\n\t\t\t{\n\t\t\t\treturn this->fractionLost;\n\t\t\t}\n\t\t\tfloat GetLossPercentage() const\n\t\t\t{\n\t\t\t\treturn static_cast<float>(this->fractionLost) * 100 / 256;\n\t\t\t}\n\t\t\tfloat GetRtt() const\n\t\t\t{\n\t\t\t\treturn this->rtt;\n\t\t\t}\n\t\t\tuint64_t GetMaxPacketMs() const\n\t\t\t{\n\t\t\t\treturn this->maxPacketMs;\n\t\t\t}\n\t\t\tuint32_t GetMaxPacketTs() const\n\t\t\t{\n\t\t\t\treturn this->maxPacketTs;\n\t\t\t}\n\t\t\tuint64_t GetSenderReportNtpMs() const\n\t\t\t{\n\t\t\t\treturn this->lastSenderReportNtpMs;\n\t\t\t}\n\t\t\tuint32_t GetSenderReportTs() const\n\t\t\t{\n\t\t\t\treturn this->lastSenderReportTs;\n\t\t\t}\n\t\t\tuint8_t GetScore() const\n\t\t\t{\n\t\t\t\treturn this->score;\n\t\t\t}\n\t\t\tuint64_t GetActiveMs() const\n\t\t\t{\n\t\t\t\treturn this->shared->GetTimeMs() - this->activeSinceMs;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tbool UpdateSeq(const RTP::Packet* packet);\n\t\t\tvoid UpdateScore(uint8_t score);\n\t\t\tvoid PacketRetransmitted(const RTP::Packet* packet);\n\t\t\tvoid PacketRepaired(const RTP::Packet* packet);\n\t\t\tuint32_t GetExpectedPackets() const\n\t\t\t{\n\t\t\t\treturn (this->cycles + this->maxSeq) - this->baseSeq + 1;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid InitSeq(uint16_t seq);\n\n\t\t\t/* Pure virtual method that must be implemented by the subclass. */\n\t\tprotected:\n\t\t\tvirtual void UserOnSequenceNumberReset() = 0;\n\n\t\tprotected:\n\t\t\t// Given as argument.\n\t\t\tRTP::RtpStream::Listener* listener{ nullptr };\n\t\t\tSharedInterface* shared{ nullptr };\n\t\t\tParams params;\n\t\t\t// Others.\n\t\t\t//   https://tools.ietf.org/html/rfc3550#appendix-A.1 stuff.\n\t\t\t// Highest seq. number seen.\n\t\t\tuint16_t maxSeq{ 0u };\n\t\t\t// Shifted count of seq. number cycles.\n\t\t\tuint32_t cycles{ 0u };\n\t\t\t// Base seq number.\n\t\t\tuint32_t baseSeq{ 0u };\n\t\t\t// Last 'bad' seq number + 1.\n\t\t\tuint32_t badSeq{ 0u };\n\t\t\t// Highest timestamp seen.\n\t\t\tuint32_t maxPacketTs{ 0u };\n\t\t\t// When the packet with highest timestammp was seen.\n\t\t\tuint64_t maxPacketMs{ 0u };\n\t\t\tint32_t packetsLost{ 0 };\n\t\t\tuint8_t fractionLost{ 0u };\n\t\t\t// Jitter in RTP timestamp units. As per spec it's kept as floating value\n\t\t\t// although it's exposed as integer in the stats.\n\t\t\tfloat jitter{ 0 };\n\t\t\tsize_t packetsDiscarded{ 0u };\n\t\t\tsize_t packetsRetransmitted{ 0u };\n\t\t\tsize_t packetsRepaired{ 0u };\n\t\t\tsize_t nackCount{ 0u };\n\t\t\tsize_t nackPacketCount{ 0u };\n\t\t\tsize_t pliCount{ 0u };\n\t\t\tsize_t firCount{ 0u };\n\t\t\t// Packets repaired at last interval for score calculation.\n\t\t\tsize_t repairedPriorScore{ 0u };\n\t\t\t// Packets retransmitted at last interval for score calculation.\n\t\t\tsize_t retransmittedPriorScore{ 0u };\n\t\t\t// NTP timestamp in last Sender Report (in ms).\n\t\t\tuint64_t lastSenderReportNtpMs{ 0u };\n\t\t\t// RTP timestamp in last Sender Report.\n\t\t\tuint32_t lastSenderReportTs{ 0u };\n\t\t\tfloat rtt{ 0.0f };\n\t\t\t// Instance of RtxStream.\n\t\t\tRTP::RtxStream* rtxStream{ nullptr };\n\n\t\tprivate:\n\t\t\t// Score related.\n\t\t\tuint8_t score{ 0u };\n\t\t\tstd::vector<uint8_t> scores;\n\t\t\t// Whether at least a RTP packet has been received.\n\t\t\tbool started{ false };\n\t\t\t// Last time since the stream is active.\n\t\t\tuint64_t activeSinceMs{ 0u };\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/RtpStreamRecv.hpp",
    "content": "#ifndef MS_RTC_RTP_RTP_STREAM_RECV_HPP\n#define MS_RTC_RTP_RTP_STREAM_RECV_HPP\n\n#include \"RTC/NackGenerator.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass RtpStreamRecv : public RTP::RtpStream,\n\t\t                      public RTC::NackGenerator::Listener,\n\t\t                      public TimerHandleInterface::Listener\n\t\t{\n\t\tpublic:\n\t\t\tclass Listener : public RTP::RtpStream::Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual void OnRtpStreamSendRtcpPacket(\n\t\t\t\t  RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) = 0;\n\t\t\t\tvirtual void OnRtpStreamNeedWorstRemoteFractionLost(\n\t\t\t\t  RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost) = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tclass TransmissionCounter\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tTransmissionCounter(\n\t\t\t\t  SharedInterface* shared, uint8_t spatialLayers, uint8_t temporalLayers, size_t windowSize);\n\t\t\t\tvoid Update(const RTP::Packet* packet);\n\t\t\t\tuint32_t GetBitrate(uint64_t nowMs);\n\t\t\t\tuint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer);\n\t\t\t\tuint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer);\n\t\t\t\tuint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer);\n\t\t\t\tsize_t GetPacketCount() const;\n\t\t\t\tsize_t GetBytes() const;\n\n\t\t\tprivate:\n\t\t\t\tstd::vector<std::vector<RTC::RtpDataCounter>> spatialLayerCounters;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tRtpStreamRecv(\n\t\t\t  RTP::RtpStreamRecv::Listener* listener,\n\t\t\t  SharedInterface* shared,\n\t\t\t  RTP::RtpStream::Params& params,\n\t\t\t  unsigned int sendNackDelayMs,\n\t\t\t  bool useRtpInactivityCheck);\n\t\t\t~RtpStreamRecv() override;\n\n\t\t\tflatbuffers::Offset<FBS::RtpStream::Stats> FillBufferStats(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\t\tbool ReceivePacket(RTP::Packet* packet);\n\t\t\tbool ReceiveRtxPacket(RTP::Packet* packet);\n\t\t\tRTC::RTCP::ReceiverReport* GetRtcpReceiverReport();\n\t\t\tRTC::RTCP::ReceiverReport* GetRtxRtcpReceiverReport();\n\t\t\tvoid ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report);\n\t\t\tvoid ReceiveRtxRtcpSenderReport(RTC::RTCP::SenderReport* report);\n\t\t\tvoid ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo);\n\t\t\tvoid RequestKeyFrame();\n\t\t\tvoid Pause() override;\n\t\t\tvoid Resume() override;\n\t\t\tuint32_t GetBitrate(uint64_t nowMs) override\n\t\t\t{\n\t\t\t\treturn this->transmissionCounter.GetBitrate(nowMs);\n\t\t\t}\n\t\t\tuint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override\n\t\t\t{\n\t\t\t\treturn this->transmissionCounter.GetBitrate(nowMs, spatialLayer, temporalLayer);\n\t\t\t}\n\t\t\tuint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer) override\n\t\t\t{\n\t\t\t\treturn this->transmissionCounter.GetSpatialLayerBitrate(nowMs, spatialLayer);\n\t\t\t}\n\t\t\tuint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override\n\t\t\t{\n\t\t\t\treturn this->transmissionCounter.GetLayerBitrate(nowMs, spatialLayer, temporalLayer);\n\t\t\t}\n\t\t\tbool HasRtpInactivityCheckEnabled() const\n\t\t\t{\n\t\t\t\treturn this->useRtpInactivityCheck;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid CalculateJitter(uint32_t rtpTimestamp);\n\t\t\tvoid UpdateScore();\n\n\t\t\t/* Pure virtual methods inherited from RTP::RtpStream. */\n\t\tpublic:\n\t\t\tvoid UserOnSequenceNumberReset() override;\n\n\t\t\t/* Pure virtual methods inherited from TimerHandleInterface. */\n\t\tprotected:\n\t\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\t\t\t/* Pure virtual methods inherited from RTC::NackGenerator. */\n\t\tprotected:\n\t\t\tvoid OnNackGeneratorNackRequired(const std::vector<uint16_t>& seqNumbers) override;\n\t\t\tvoid OnNackGeneratorKeyFrameRequired() override;\n\n\t\tprivate:\n\t\t\t// Passed by argument.\n\t\t\tunsigned int sendNackDelayMs{ 0u };\n\t\t\tbool useRtpInactivityCheck{ false };\n\t\t\t// Others.\n\t\t\t// Packets expected at last interval.\n\t\t\tuint32_t expectedPrior{ 0u };\n\t\t\t// Packets expected at last interval for score calculation.\n\t\t\tuint32_t expectedPriorScore{ 0u };\n\t\t\t// Packets received at last interval.\n\t\t\tuint32_t receivedPrior{ 0u };\n\t\t\t// Packets received at last interval for score calculation.\n\t\t\tuint32_t receivedPriorScore{ 0u };\n\t\t\t// The middle 32 bits out of 64 in the NTP timestamp received in the most\n\t\t\t// recent sender report.\n\t\t\tuint32_t lastSrTimestamp{ 0u };\n\t\t\t// Wallclock time representing the most recent sender report arrival.\n\t\t\tuint64_t lastSrReceived{ 0u };\n\t\t\t// Relative transit time for prev packet.\n\t\t\tint32_t transit{ 0u };\n\t\t\tuint8_t firSeqNumber{ 0u };\n\t\t\tint32_t reportedPacketsLost{ 0 };\n\t\t\tstd::unique_ptr<RTC::NackGenerator> nackGenerator;\n\t\t\tTimerHandleInterface* inactivityCheckPeriodicTimer{ nullptr };\n\t\t\tbool inactive{ false };\n\t\t\t// Valid media + valid RTX.\n\t\t\tTransmissionCounter transmissionCounter;\n\t\t\t// Just valid media.\n\t\t\tRTC::RtpDataCounter mediaTransmissionCounter;\n\t\t\t// Template dependency structure for Dependency Descriptor.\n\t\t\tstd::unique_ptr<RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t\t\t  templateDependencyStructure;\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/RtpStreamSend.hpp",
    "content": "#ifndef MS_RTC_RTP_RTP_STREAM_SEND_HPP\n#define MS_RTC_RTP_RTP_STREAM_SEND_HPP\n\n#include \"RTC/RTP/RetransmissionBuffer.hpp\"\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass RtpStreamSend : public RTP::RtpStream\n\t\t{\n\t\tpublic:\n\t\t\t// Maximum retransmission buffer size for video (ms).\n\t\t\tstatic const uint32_t MaxRetransmissionDelayForVideoMs;\n\t\t\t// Maximum retransmission buffer size for audio (ms).\n\t\t\tstatic const uint32_t MaxRetransmissionDelayForAudioMs;\n\n\t\tpublic:\n\t\t\tenum class ReceivePacketResult : uint8_t\n\t\t\t{\n\t\t\t\tDISCARDED,\n\t\t\t\tACCEPTED_AND_NOT_STORED,\n\t\t\t\tACCEPTED_AND_STORED\n\t\t\t};\n\n\t\tpublic:\n\t\t\tclass Listener : public RTP::RtpStream::Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual void OnRtpStreamRetransmitRtpPacket(\n\t\t\t\t  RTP::RtpStreamSend* rtpStream, RTP::Packet* packet) = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tRtpStreamSend(\n\t\t\t  RTP::RtpStreamSend::Listener* listener,\n\t\t\t  SharedInterface* shared,\n\t\t\t  RTP::RtpStream::Params& params,\n\t\t\t  std::string& mid);\n\t\t\t~RtpStreamSend() override;\n\n\t\t\tflatbuffers::Offset<FBS::RtpStream::Stats> FillBufferStats(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\t\tvoid SetRtx(uint8_t payloadType, uint32_t ssrc) override;\n\t\t\tReceivePacketResult ReceivePacket(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket);\n\t\t\tvoid ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket);\n\t\t\tvoid ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType);\n\t\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report);\n\t\t\tvoid ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report);\n\t\t\tRTC::RTCP::SenderReport* GetRtcpSenderReport(uint64_t nowMs);\n\t\t\tRTC::RTCP::DelaySinceLastRr::SsrcInfo* GetRtcpXrDelaySinceLastRrSsrcInfo(uint64_t nowMs);\n\t\t\tRTC::RTCP::SdesChunk* GetRtcpSdesChunk();\n\t\t\tvoid Pause() override;\n\t\t\tvoid Resume() override;\n\t\t\tuint32_t GetBitrate(uint64_t nowMs) override\n\t\t\t{\n\t\t\t\treturn this->transmissionCounter.GetBitrate(nowMs);\n\t\t\t}\n\t\t\tuint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override;\n\t\t\tuint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer) override;\n\t\t\tuint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override;\n\n\t\tprivate:\n\t\t\tvoid FillRetransmissionContainer(uint16_t seq, uint16_t bitmask);\n\t\t\tvoid UpdateScore(RTC::RTCP::ReceiverReport* report);\n\n\t\t\t/* Pure virtual methods inherited from RTP::RtpStream. */\n\t\tpublic:\n\t\t\tvoid UserOnSequenceNumberReset() override;\n\n\t\tprivate:\n\t\t\t// Packets lost at last interval for score calculation.\n\t\t\tint32_t lostPriorScore{ 0 };\n\t\t\t// Packets sent at last interval for score calculation.\n\t\t\tuint32_t sentPriorScore{ 0u };\n\t\t\tstd::string mid;\n\t\t\tuint16_t rtxSeq{ 0u };\n\t\t\tRTC::RtpDataCounter transmissionCounter;\n\t\t\tRTP::RetransmissionBuffer* retransmissionBuffer{ nullptr };\n\t\t\t// The middle 32 bits out of 64 in the NTP timestamp received in the most\n\t\t\t// recent receiver reference timestamp.\n\t\t\tuint32_t lastRrTimestamp{ 0u };\n\t\t\t// Wallclock time representing the most recent receiver reference timestamp\n\t\t\t// arrival.\n\t\t\tuint64_t lastRrReceivedMs{ 0u };\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/RtxStream.hpp",
    "content": "#ifndef MS_RTC_RTP_RTX_STREAM_HPP\n#define MS_RTC_RTP_RTX_STREAM_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"FBS/rtxStream.h\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass RtxStream\n\t\t{\n\t\tpublic:\n\t\t\tstruct Params\n\t\t\t{\n\t\t\t\tflatbuffers::Offset<FBS::RtxStream::Params> FillBuffer(\n\t\t\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t\t\tuint32_t ssrc{ 0 };\n\t\t\t\tuint8_t payloadType{ 0 };\n\t\t\t\tRTC::RtpCodecMimeType mimeType;\n\t\t\t\tuint32_t clockRate{ 0 };\n\t\t\t\tstd::string rrid;\n\t\t\t\tstd::string cname;\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit RtxStream(SharedInterface* shared, RTP::RtxStream::Params& params);\n\t\t\tvirtual ~RtxStream();\n\n\t\t\tflatbuffers::Offset<FBS::RtxStream::RtxDump> FillBuffer(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\t\tuint32_t GetSsrc() const\n\t\t\t{\n\t\t\t\treturn this->params.ssrc;\n\t\t\t}\n\t\t\tuint8_t GetPayloadType() const\n\t\t\t{\n\t\t\t\treturn this->params.payloadType;\n\t\t\t}\n\t\t\tconst RTC::RtpCodecMimeType& GetMimeType() const\n\t\t\t{\n\t\t\t\treturn this->params.mimeType;\n\t\t\t}\n\t\t\tuint32_t GetClockRate() const\n\t\t\t{\n\t\t\t\treturn this->params.clockRate;\n\t\t\t}\n\t\t\tconst std::string& GetRrid() const\n\t\t\t{\n\t\t\t\treturn this->params.rrid;\n\t\t\t}\n\t\t\tconst std::string& GetCname() const\n\t\t\t{\n\t\t\t\treturn this->params.cname;\n\t\t\t}\n\t\t\tuint8_t GetFractionLost() const\n\t\t\t{\n\t\t\t\treturn this->fractionLost;\n\t\t\t}\n\t\t\tfloat GetLossPercentage() const\n\t\t\t{\n\t\t\t\treturn static_cast<float>(this->fractionLost) * 100 / 256;\n\t\t\t}\n\t\t\tsize_t GetPacketsDiscarded() const\n\t\t\t{\n\t\t\t\treturn this->packetsDiscarded;\n\t\t\t}\n\t\t\tbool ReceivePacket(const RTP::Packet* packet);\n\t\t\tRTC::RTCP::ReceiverReport* GetRtcpReceiverReport();\n\t\t\tvoid ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report);\n\n\t\tprotected:\n\t\t\tbool UpdateSeq(const RTP::Packet* packet);\n\t\t\tuint32_t GetExpectedPackets() const\n\t\t\t{\n\t\t\t\treturn (this->cycles + this->maxSeq) - this->baseSeq + 1;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid InitSeq(uint16_t seq);\n\n\t\tprotected:\n\t\t\t// Given as argument.\n\t\t\tSharedInterface* shared{ nullptr };\n\t\t\tParams params;\n\t\t\t// Others.\n\t\t\t//   https://tools.ietf.org/html/rfc3550#appendix-A.1 stuff.\n\t\t\tuint16_t maxSeq{ 0u };      // Highest seq. number seen.\n\t\t\tuint32_t cycles{ 0u };      // Shifted count of seq. number cycles.\n\t\t\tuint32_t baseSeq{ 0u };     // Base seq number.\n\t\t\tuint32_t badSeq{ 0u };      // Last 'bad' seq number + 1.\n\t\t\tuint32_t maxPacketTs{ 0u }; // Highest timestamp seen.\n\t\t\tuint64_t maxPacketMs{ 0u }; // When the packet with highest timestammp was seen.\n\t\t\tint32_t packetsLost{ 0 };\n\t\t\tuint8_t fractionLost{ 0u };\n\t\t\tsize_t packetsDiscarded{ 0u };\n\t\t\tsize_t packetsCount{ 0u };\n\n\t\tprivate:\n\t\t\t// Whether at least a RTP packet has been received.\n\t\t\tbool started{ false };\n\t\t\t// Fields for generating Receiver Reports.\n\t\t\tuint32_t expectedPrior{ 0u };\n\t\t\tuint32_t receivedPrior{ 0u };\n\t\t\tuint32_t lastSrTimestamp{ 0u };\n\t\t\tuint64_t lastSrReceived{ 0u };\n\t\t\tint32_t reportedPacketsLost{ 0 };\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RTP/SharedPacket.hpp",
    "content": "#ifndef MS_RTC_RTP_SHARED_PACKET_HPP\n#define MS_RTC_RTP_SHARED_PACKET_HPP\n\n#include \"common.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tclass SharedPacket\n\t\t{\n\t\tpublic:\n#ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE\n\t\t\tstatic thread_local uint64_t allocatedMemory;\n#endif\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Empty constructor.\n\t\t\t */\n\t\t\tSharedPacket();\n\n\t\t\t/**\n\t\t\t * Constructor with RTP Packet pointer. If a packet is given it's internally\n\t\t\t * cloned.\n\t\t\t */\n\t\t\texplicit SharedPacket(RTP::Packet* packet);\n\n\t\t\t/**\n\t\t\t * Copy constructor.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * No need to declare it but let's be explicit.\n\t\t\t */\n\t\t\tSharedPacket(const SharedPacket&) = default;\n\n\t\t\t/**\n\t\t\t * Copy assignment operator.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - No need to declare it but let's be explicit.\n\t\t\t *\n\t\t\t * @throws MediasoupError if the Packet is too big.\n\t\t\t */\n\t\t\tSharedPacket& operator=(const SharedPacket&) = default;\n\n\t\t\t/**\n\t\t\t * Destructor.\n\t\t\t */\n\t\t\t~SharedPacket() = default;\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\tbool HasPacket() const\n\t\t\t{\n\t\t\t\treturn this->sharedPtr->get() != nullptr;\n\t\t\t}\n\n\t\t\tRTP::Packet* GetPacket() const\n\t\t\t{\n\t\t\t\treturn this->sharedPtr->get();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Assign given packet (could be nullptr). If packet is given it's internally\n\t\t\t * cloned.\n\t\t\t *\n\t\t\t * @throws MediasoupError if the Packet is too big.\n\t\t\t */\n\t\t\tvoid Assign(RTP::Packet* packet);\n\n\t\t\t/**\n\t\t\t * Resets the internal packet to nullptr.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * This affects to ALL copies of this SharedPacket object.\n\t\t\t */\n\t\t\tvoid Reset();\n\n\t\t\t/**\n\t\t\t * Assert that RTP Packet contained in this SharedPacket is a clone of the\n\t\t\t * given other packet (or there is no packet inside and no other packet has\n\t\t\t * been given).\n\t\t\t */\n\t\t\tvoid AssertSamePacket(const RTP::Packet* otherPacket) const;\n\n\t\tprivate:\n\t\t\tvoid StorePacket(RTP::Packet* packet);\n\n\t\tprivate:\n\t\t\t// NOTE: This needs to be a shared pointer that holds an unique pointer.\n\t\t\t// Otherwise, when copying/storing the shared pointer in other locations\n\t\t\t// (buffers, etc), reseting its internal value wouldn't affect other copies\n\t\t\t// of the shared pointer.\n\t\t\tstd::shared_ptr<std::unique_ptr<RTP::Packet>> sharedPtr;\n\t\t};\n\t} // namespace RTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RateCalculator.hpp",
    "content": "#ifndef MS_RTC_RATE_CALCULATOR_HPP\n#define MS_RTC_RATE_CALCULATOR_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\t// It is considered that the time source increases monotonically.\n\t// ie: the current timestamp can never be minor than a timestamp in the past.\n\tclass RateCalculator\n\t{\n\tpublic:\n\t\tstatic constexpr size_t DefaultWindowSize{ 1000u };\n\t\tstatic constexpr float DefaultBpsScale{ 8000.0f };\n\t\tstatic constexpr uint16_t DefaultWindowItems{ 100u };\n\n\tpublic:\n\t\texplicit RateCalculator(\n\t\t  size_t windowSizeMs  = DefaultWindowSize,\n\t\t  float scale          = DefaultBpsScale,\n\t\t  uint16_t windowItems = DefaultWindowItems);\n\n\t\tvoid Update(size_t size, uint64_t nowMs);\n\n\t\tuint32_t GetRate(uint64_t nowMs);\n\n\t\tsize_t GetBytes() const\n\t\t{\n\t\t\treturn this->bytes;\n\t\t}\n\n\t\tvoid Reset();\n\n\tprivate:\n\t\tvoid RemoveOldData(uint64_t nowMs);\n\n\tprivate:\n\t\tstruct BufferItem\n\t\t{\n\t\t\tsize_t count{ 0u };\n\t\t\tuint64_t time{ 0u };\n\t\t};\n\n\tprivate:\n\t\t// Window Size (in milliseconds).\n\t\tsize_t windowSizeMs{ DefaultWindowSize };\n\t\t// Scale in which the rate is represented.\n\t\tfloat scale{ DefaultBpsScale };\n\t\t// Window Size (number of items).\n\t\tuint16_t windowItems{ DefaultWindowItems };\n\t\t// Item Size (in milliseconds), calculated as: windowSizeMs / windowItems.\n\t\tsize_t itemSizeMs{ 0u };\n\t\t// Buffer to keep data.\n\t\tstd::vector<BufferItem> buffer;\n\t\t// Time (in milliseconds) for last item in the time window.\n\t\tstd::optional<uint64_t> newestItemStartTime{ std::nullopt };\n\t\t// Index for the last item in the time window.\n\t\tint32_t newestItemIndex{ -1 };\n\t\t// Time (in milliseconds) for oldest item in the time window.\n\t\tstd::optional<uint64_t> oldestItemStartTime{ std::nullopt };\n\t\t// Index for the oldest item in the time window.\n\t\tint32_t oldestItemIndex{ -1 };\n\t\t// Total count in the time window.\n\t\tsize_t totalCount{ 0u };\n\t\t// Total bytes transmitted.\n\t\tsize_t bytes{ 0u };\n\t\t// Last value calculated by GetRate().\n\t\tuint32_t lastRate{ 0u };\n\t\t// Last time GetRate() was called.\n\t\tstd::optional<uint64_t> lastTime{ std::nullopt };\n\t};\n\n\tclass RtpDataCounter\n\t{\n\tpublic:\n\t\texplicit RtpDataCounter(\n\t\t  SharedInterface* shared, bool ignorePaddingOnlyPackets, size_t windowSizeMs = 2500)\n\t\t  : shared(shared), ignorePaddingOnlyPackets(ignorePaddingOnlyPackets), rate(windowSizeMs)\n\t\t{\n\t\t}\n\n\tpublic:\n\t\tvoid Update(const RTC::RTP::Packet* packet);\n\n\t\tuint32_t GetBitrate(uint64_t nowMs)\n\t\t{\n\t\t\treturn this->rate.GetRate(nowMs);\n\t\t}\n\n\t\tsize_t GetPacketCount() const\n\t\t{\n\t\t\treturn this->packets;\n\t\t}\n\n\t\tsize_t GetBytes() const\n\t\t{\n\t\t\treturn this->rate.GetBytes();\n\t\t}\n\n\tprivate:\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Whether the size of padding only RTP packets should not be taken into\n\t\t// account\n\t\tbool ignorePaddingOnlyPackets{ false };\n\t\tRateCalculator rate;\n\t\tsize_t packets{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Router.hpp",
    "content": "#ifndef MS_RTC_ROUTER_HPP\n#define MS_RTC_ROUTER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/DataConsumer.hpp\"\n#include \"RTC/DataProducer.hpp\"\n#include \"RTC/Producer.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include \"RTC/RtpObserver.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"RTC/WebRtcServer.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <absl/container/flat_hash_set.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Router : public RTC::Transport::Listener,\n\t               public RTC::RtpObserver::Listener,\n\t               public Channel::ChannelSocket::RequestHandler\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual RTC::WebRtcServer* OnRouterNeedWebRtcServer(\n\t\t\t  RTC::Router* router, std::string& webRtcServerId) = 0;\n\t\t};\n\n\tpublic:\n\t\texplicit Router(SharedInterface* shared, const std::string& id, Listener* listener);\n\t\t~Router() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Router::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tRTC::Transport* GetTransportById(const std::string& transportId) const;\n\t\tRTC::RtpObserver* GetRtpObserverById(const std::string& rtpObserverId) const;\n\t\tvoid CheckNoTransport(const std::string& transportId) const;\n\t\tvoid CheckNoRtpObserver(const std::string& rtpObserverId) const;\n\n\t\t/* Pure virtual methods inherited from RTC::Transport::Listener. */\n\tpublic:\n\t\tvoid OnTransportNewProducer(RTC::Transport* transport, RTC::Producer* producer) override;\n\t\tvoid OnTransportProducerClosed(RTC::Transport* transport, RTC::Producer* producer) override;\n\t\tvoid OnTransportProducerPaused(RTC::Transport* transport, RTC::Producer* producer) override;\n\t\tvoid OnTransportProducerResumed(RTC::Transport* transport, RTC::Producer* producer) override;\n\t\tvoid OnTransportProducerNewRtpStream(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::Producer* producer,\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t  uint32_t mappedSsrc) override;\n\t\tvoid OnTransportProducerRtpStreamScore(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::Producer* producer,\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t  uint8_t score,\n\t\t  uint8_t previousScore) override;\n\t\tvoid OnTransportProducerRtcpSenderReport(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::Producer* producer,\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t  bool first) override;\n\t\tvoid OnTransportProducerRtpPacketReceived(\n\t\t  RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::Packet* packet) override;\n\t\tvoid OnTransportNeedWorstRemoteFractionLost(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::Producer* producer,\n\t\t  uint32_t mappedSsrc,\n\t\t  uint8_t& worstRemoteFractionLost) override;\n\t\tvoid OnTransportNewConsumer(\n\t\t  RTC::Transport* transport, RTC::Consumer* consumer, const std::string& producerId) override;\n\t\tvoid OnTransportConsumerClosed(RTC::Transport* transport, RTC::Consumer* consumer) override;\n\t\tvoid OnTransportConsumerProducerClosed(RTC::Transport* transport, RTC::Consumer* consumer) override;\n\t\tvoid OnTransportConsumerKeyFrameRequested(\n\t\t  RTC::Transport* transport, RTC::Consumer* consumer, uint32_t mappedSsrc) override;\n\t\tvoid OnTransportNewDataProducer(RTC::Transport* transport, RTC::DataProducer* dataProducer) override;\n\t\tvoid OnTransportDataProducerClosed(RTC::Transport* transport, RTC::DataProducer* dataProducer) override;\n\t\tvoid OnTransportDataProducerPaused(RTC::Transport* transport, RTC::DataProducer* dataProducer) override;\n\t\tvoid OnTransportDataProducerResumed(\n\t\t  RTC::Transport* transport, RTC::DataProducer* dataProducer) override;\n\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\tvoid OnTransportDataProducerMessageReceived(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::DataProducer* dataProducer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel) override;\n\t\tvoid OnTransportDataProducerMessageReceived(\n\t\t  RTC::Transport* transport,\n\t\t  RTC::DataProducer* dataProducer,\n\t\t  RTC::SCTP::Message message,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel) override;\n\t\tvoid OnTransportNewDataConsumer(\n\t\t  RTC::Transport* transport, RTC::DataConsumer* dataConsumer, std::string& dataProducerId) override;\n\t\tvoid OnTransportDataConsumerClosed(RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override;\n\t\tvoid OnTransportDataConsumerDataProducerClosed(\n\t\t  RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override;\n\t\tvoid OnTransportListenServerClosed(RTC::Transport* transport) override;\n\n\t\t/* Pure virtual methods inherited from RTC::RtpObserver::Listener. */\n\tpublic:\n\t\tRTC::Producer* RtpObserverGetProducer(RTC::RtpObserver* rtpObserver, const std::string& id) override;\n\t\tvoid OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) override;\n\t\tvoid OnRtpObserverRemoveProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) override;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\tListener* listener{ nullptr };\n\t\t// Allocated by this.\n\t\tabsl::flat_hash_map<std::string, RTC::Transport*> mapTransports;\n\t\tabsl::flat_hash_map<std::string, RTC::RtpObserver*> mapRtpObservers;\n\t\t// Others.\n\t\tabsl::flat_hash_map<RTC::Producer*, absl::flat_hash_set<RTC::Consumer*>> mapProducerConsumers;\n\t\tabsl::flat_hash_map<RTC::Consumer*, RTC::Producer*> mapConsumerProducer;\n\t\tabsl::flat_hash_map<RTC::Producer*, absl::flat_hash_set<RTC::RtpObserver*>> mapProducerRtpObservers;\n\t\tabsl::flat_hash_map<std::string, RTC::Producer*> mapProducers;\n\t\tabsl::flat_hash_map<RTC::DataProducer*, absl::flat_hash_set<RTC::DataConsumer*>>\n\t\t  mapDataProducerDataConsumers;\n\t\tabsl::flat_hash_map<RTC::DataConsumer*, RTC::DataProducer*> mapDataConsumerDataProducer;\n\t\tabsl::flat_hash_map<std::string, RTC::DataProducer*> mapDataProducers;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RtcLogger.hpp",
    "content": "#ifndef MS_RTC_RTC_LOGGER_HPP\n#define MS_RTC_RTC_LOGGER_HPP\n\n#include \"common.hpp\"\n#include <absl/container/flat_hash_map.h>\n\nnamespace RTC\n{\n\tnamespace RtcLogger\n\t{\n\t\tclass RtpPacket\n\t\t{\n\t\tpublic:\n\t\t\tenum class DiscardReason : uint8_t\n\t\t\t{\n\t\t\t\tNONE = 0,\n\t\t\t\tPRODUCER_NOT_FOUND,\n\t\t\t\tRECV_RTP_STREAM_NOT_FOUND,\n\t\t\t\tRECV_RTP_STREAM_DISCARDED,\n\t\t\t\tRECV_RTP_RTX_STREAM_DISCARDED,\n\t\t\t\tCONSUMER_INACTIVE,\n\t\t\t\tINVALID_TARGET_LAYER,\n\t\t\t\tUNSUPPORTED_PAYLOAD_TYPE,\n\t\t\t\tNOT_A_KEYFRAME,\n\t\t\t\tEMPTY_PAYLOAD,\n\t\t\t\tSPATIAL_LAYER_MISMATCH,\n\t\t\t\tPACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH,\n\t\t\t\tDROPPED_BY_CODEC,\n\t\t\t\tTOO_HIGH_TIMESTAMP_EXTRA_NEEDED,\n\t\t\t\tSEND_RTP_STREAM_DISCARDED\n\t\t\t};\n\n\t\t\tstatic const absl::flat_hash_map<DiscardReason, std::string> DiscardReason2String;\n\n\t\t\tRtpPacket()  = default;\n\t\t\t~RtpPacket() = default;\n\t\t\tvoid Sent();\n\t\t\tvoid Discarded(DiscardReason discardReason);\n\n\t\tprivate:\n\t\t\tvoid Log() const;\n\t\t\tvoid Clear();\n\n\t\tpublic:\n\t\t\tuint64_t timestamp{};\n\t\t\tstd::string recvTransportId;\n\t\t\tstd::string sendTransportId;\n\t\t\tstd::string routerId;\n\t\t\tstd::string producerId;\n\t\t\tstd::string consumerId;\n\t\t\tuint32_t recvRtpTimestamp{};\n\t\t\tuint32_t sendRtpTimestamp{};\n\t\t\tuint16_t recvSeqNumber{};\n\t\t\tuint16_t sendSeqNumber{};\n\t\t\tbool discarded{};\n\t\t\tDiscardReason discardReason{ DiscardReason::NONE };\n\t\t};\n\t}; // namespace RtcLogger\n} // namespace RTC\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RtpDictionaries.hpp",
    "content": "#ifndef MS_RTC_RTP_DICTIONARIES_HPP\n#define MS_RTC_RTP_DICTIONARIES_HPP\n\n#include \"common.hpp\"\n#include \"FBS/rtpParameters.h\"\n#include \"RTC/Parameters.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Media\n\t{\n\tpublic:\n\t\tenum class Kind : uint8_t\n\t\t{\n\t\t\tAUDIO,\n\t\t\tVIDEO\n\t\t};\n\t};\n\n\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\n\tclass RtpCodecMimeType\n\t{\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tAUDIO,\n\t\t\tVIDEO\n\t\t};\n\n\tpublic:\n\t\tenum class Subtype\n\t\t{\n\t\t\t// Audio codecs:\n\t\t\tOPUS = 100,\n\t\t\t// Multi-channel Opus.\n\t\t\tMULTIOPUS,\n\t\t\tPCMA,\n\t\t\tPCMU,\n\t\t\tISAC,\n\t\t\tG722,\n\t\t\tILBC,\n\t\t\tSILK,\n\t\t\t// Video codecs:\n\t\t\tVP8 = 200,\n\t\t\tVP9,\n\t\t\tH264,\n\t\t\tAV1,\n\t\t\t// Complementary codecs:\n\t\t\tCN = 300,\n\t\t\tTELEPHONE_EVENT,\n\t\t\t// Feature codecs:\n\t\t\tRTX = 400,\n\t\t\tULPFEC,\n\t\t\tX_ULPFECUC,\n\t\t\tFLEXFEC,\n\t\t\tRED\n\t\t};\n\n\tpublic:\n\t\tstatic const absl::flat_hash_map<std::string, Type> String2Type;\n\t\tstatic const absl::flat_hash_map<Type, std::string> Type2String;\n\t\tstatic const absl::flat_hash_map<std::string, Subtype> String2Subtype;\n\t\tstatic const absl::flat_hash_map<Subtype, std::string> Subtype2String;\n\n\tpublic:\n\t\tRtpCodecMimeType() = default;\n\n\t\tbool operator==(const RtpCodecMimeType& other) const\n\t\t{\n\t\t\treturn this->type == other.type && this->subtype == other.subtype;\n\t\t}\n\n\t\tbool operator!=(const RtpCodecMimeType& other) const\n\t\t{\n\t\t\treturn !(*this == other);\n\t\t}\n\n\t\tvoid SetMimeType(const std::string& mimeType);\n\n\t\tvoid UpdateMimeType();\n\n\t\tconst std::string& ToString() const\n\t\t{\n\t\t\treturn this->mimeType;\n\t\t}\n\n\t\tbool IsMediaCodec() const\n\t\t{\n\t\t\treturn this->subtype >= Subtype(100) && this->subtype < (Subtype)300;\n\t\t}\n\n\t\tbool IsComplementaryCodec() const\n\t\t{\n\t\t\treturn this->subtype >= Subtype(300) && this->subtype < (Subtype)400;\n\t\t}\n\n\t\tbool IsFeatureCodec() const\n\t\t{\n\t\t\treturn this->subtype >= Subtype(400);\n\t\t}\n\n\tpublic:\n\t\tType type;\n\t\tSubtype subtype;\n\n\tprivate:\n\t\tstd::string mimeType;\n\t};\n\n\tclass RtpHeaderExtensionUri\n\t{\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tMID                    = 1,\n\t\t\tRTP_STREAM_ID          = 2,\n\t\t\tREPAIRED_RTP_STREAM_ID = 3,\n\t\t\tABS_SEND_TIME          = 4,\n\t\t\tTRANSPORT_WIDE_CC_01   = 5,\n\t\t\tSSRC_AUDIO_LEVEL       = 6,\n\t\t\tDEPENDENCY_DESCRIPTOR  = 7,\n\t\t\tVIDEO_ORIENTATION      = 8,\n\t\t\tTIME_OFFSET            = 9,\n\t\t\tABS_CAPTURE_TIME       = 10,\n\t\t\tPLAYOUT_DELAY          = 11,\n\t\t\tMEDIASOUP_PACKET_ID    = 12\n\t\t};\n\n\tpublic:\n\t\tstatic RtpHeaderExtensionUri::Type TypeFromFbs(FBS::RtpParameters::RtpHeaderExtensionUri uri);\n\t\tstatic FBS::RtpParameters::RtpHeaderExtensionUri TypeToFbs(RtpHeaderExtensionUri::Type uri);\n\t};\n\n\tclass RtcpFeedback\n\t{\n\tpublic:\n\t\tRtcpFeedback() = default;\n\t\texplicit RtcpFeedback(const FBS::RtpParameters::RtcpFeedback* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtcpFeedback> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tstd::string type;\n\t\tstd::string parameter;\n\t};\n\n\tclass RtpCodecParameters\n\t{\n\tpublic:\n\t\tRtpCodecParameters() = default;\n\t\texplicit RtpCodecParameters(const FBS::RtpParameters::RtpCodecParameters* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtpCodecParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tprivate:\n\t\tvoid CheckCodec() const;\n\n\tpublic:\n\t\tRtpCodecMimeType mimeType;\n\t\tuint8_t payloadType{ 0u };\n\t\tuint32_t clockRate{ 0u };\n\t\tuint8_t channels{ 1u };\n\t\tRTC::Parameters parameters;\n\t\tstd::vector<RtcpFeedback> rtcpFeedback;\n\t};\n\n\tclass RtpRtxParameters\n\t{\n\tpublic:\n\t\tRtpRtxParameters() = default;\n\t\texplicit RtpRtxParameters(const FBS::RtpParameters::Rtx* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::Rtx> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tuint32_t ssrc{ 0u };\n\t};\n\n\tclass RtpEncodingParameters\n\t{\n\tpublic:\n\t\tRtpEncodingParameters() = default;\n\t\texplicit RtpEncodingParameters(const FBS::RtpParameters::RtpEncodingParameters* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tuint32_t ssrc{ 0u };\n\t\tstd::string rid;\n\t\tuint8_t codecPayloadType{ 0u };\n\t\tbool hasCodecPayloadType{ false };\n\t\tRtpRtxParameters rtx;\n\t\tbool hasRtx{ false };\n\t\tuint32_t maxBitrate{ 0u };\n\t\tdouble maxFramerate{ 0 };\n\t\tbool dtx{ false };\n\t\tstd::string scalabilityMode{ \"S1T1\" };\n\t\tuint8_t spatialLayers{ 1u };\n\t\tuint8_t temporalLayers{ 1u };\n\t\tbool ksvc{ false };\n\t};\n\n\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\n\tclass RtpHeaderExtensionParameters\n\t{\n\tpublic:\n\t\tRtpHeaderExtensionParameters() = default;\n\t\texplicit RtpHeaderExtensionParameters(const FBS::RtpParameters::RtpHeaderExtensionParameters* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtpHeaderExtensionParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tRtpHeaderExtensionUri::Type type;\n\t\tuint8_t id{ 0u };\n\t\tbool encrypt{ false };\n\t\tRTC::Parameters parameters;\n\t};\n\n\tclass RtcpParameters\n\t{\n\tpublic:\n\t\tRtcpParameters() = default;\n\t\texplicit RtcpParameters(const FBS::RtpParameters::RtcpParameters* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtcpParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tstd::string cname;\n\t\tbool reducedSize{ true };\n\t};\n\n\tclass RtpParameters\n\t{\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tSIMPLE,\n\t\t\tSIMULCAST,\n\t\t\tSVC,\n\t\t\tPIPE\n\t\t};\n\n\tpublic:\n\t\tstatic std::optional<Type> GetType(const RtpParameters& rtpParameters);\n\t\tstatic const std::string& GetTypeString(Type type);\n\t\tstatic FBS::RtpParameters::Type TypeToFbs(Type type);\n\n\tprivate:\n\t\tstatic const absl::flat_hash_map<Type, std::string> Type2String;\n\n\tpublic:\n\t\tRtpParameters() = default;\n\t\texplicit RtpParameters(const FBS::RtpParameters::RtpParameters* data);\n\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtpParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tconst RTC::RtpCodecParameters* GetCodecForEncoding(RtpEncodingParameters& encoding) const;\n\t\tconst RTC::RtpCodecParameters* GetRtxCodecForEncoding(RtpEncodingParameters& encoding) const;\n\n\tprivate:\n\t\tvoid ValidateCodecs();\n\t\tvoid ValidateEncodings();\n\t\tvoid SetType();\n\n\tpublic:\n\t\tstd::string mid;\n\t\tstd::vector<RtpCodecParameters> codecs;\n\t\tstd::vector<RtpEncodingParameters> encodings;\n\t\tstd::vector<RtpHeaderExtensionParameters> headerExtensions;\n\t\tRtcpParameters rtcp;\n\t\tstd::string msid;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RtpListener.hpp",
    "content": "#ifndef MS_RTC_RTP_LISTENER_HPP\n#define MS_RTC_RTP_LISTENER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/Producer.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tclass RtpListener\n\t{\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Transport::RtpListener> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid AddProducer(RTC::Producer* producer);\n\t\tvoid RemoveProducer(RTC::Producer* producer);\n\t\tRTC::Producer* GetProducer(const RTC::RTP::Packet* packet);\n\t\tRTC::Producer* GetProducer(uint32_t ssrc) const;\n\n\tpublic:\n\t\t// Table of SSRC / Producer pairs.\n\t\tstd::unordered_map<uint32_t, RTC::Producer*> ssrcTable;\n\t\t//  Table of MID / Producer pairs.\n\t\tstd::unordered_map<std::string, RTC::Producer*> midTable;\n\t\t//  Table of RID / Producer pairs.\n\t\tstd::unordered_map<std::string, RTC::Producer*> ridTable;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/RtpObserver.hpp",
    "content": "#ifndef MS_RTC_RTP_OBSERVER_HPP\n#define MS_RTC_RTP_OBSERVER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"RTC/Producer.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tclass RtpObserver : public Channel::ChannelSocket::RequestHandler\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual RTC::Producer* RtpObserverGetProducer(\n\t\t\t  RTC::RtpObserver* rtpObserver, const std::string& id) = 0;\n\t\t\tvirtual void OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnRtpObserverRemoveProducer(\n\t\t\t  RTC::RtpObserver* rtpObserver, RTC::Producer* producer) = 0;\n\t\t};\n\n\tpublic:\n\t\tRtpObserver(SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener);\n\t\t~RtpObserver() override;\n\n\tpublic:\n\t\tvoid Pause();\n\t\tvoid Resume();\n\t\tbool IsPaused() const\n\t\t{\n\t\t\treturn this->paused;\n\t\t}\n\t\tvirtual void AddProducer(RTC::Producer* producer)                                = 0;\n\t\tvirtual void RemoveProducer(RTC::Producer* producer)                             = 0;\n\t\tvirtual void ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0;\n\t\tvirtual void ProducerPaused(RTC::Producer* producer)                             = 0;\n\t\tvirtual void ProducerResumed(RTC::Producer* producer)                            = 0;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprotected:\n\t\tvirtual void Paused()  = 0;\n\t\tvirtual void Resumed() = 0;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\n\tprotected:\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tRTC::RtpObserver::Listener* listener{ nullptr };\n\t\t// Others.\n\t\tbool paused{ false };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/TODO_SCTP.md",
    "content": "# TODO STCP\n\n## Related to mediasoup SCTP implementation\n\n- dcsctp uses µs (webrtc::Timestamp::Micros()) internally, while mediasoup uses ms (`DepLibUV::GetTimeMs()`). When porting dcsctp timeout/duration logic, make sure to convert accordingly. Do not mix units in the same field.\n\n- `Association`: When transitioning to CLOSED (due to failure while connecting or closure) we should emit a new event \"stcpclosed\" in all `DataProducers/Consumers`.\n\n- When receiving SCTP RE-CONFIG, we should emit \"streamclosed\" in those `DataProducers/DataConsumers` whose stream ID have been closed.\n\n- `OnAssociationFailed()` and `OnAssociationClosed()` should report an error (if present) to JS.\n\n- Rename all \"Packet\", \"Chunk\", \"Parameter\", \"Error Cause\", \"Association\", etc to lowcase everywhere (in code and comments).\n\n- Rename all \"I_DATA\" etc to `I-DATA\" everywhere (in code and comments).\n\n- Probably add many more fields in `SctpOptions` given to the `Association` in `Transport.cpp`.\n\n- When running `test-PipeTransport.ts` and `test-werift-sctp.ts` with `useBuiltInSctpStack: true`, tests pass but those errors show up:\n\n  ```\n  mediasoup:ERROR:Worker (stderr) UnixStreamSocketHandle::Write() | uv_try_write() failed, trying uv_write(): broken pipe\n  ```\n\n- We must remove `numSctpStreams` option given to `router.createXxxTransport()` and `NumSctpStreams` type. `OS` and `MIS` in `numSctpStreams` are just the max announced number of outbound and incoming SCTP streams, but in the new SCTP stack those should always be 65535. The max number of incoming and outgoing streams will be negotiated later with the SCTP INIT and INIT_ACK and will be the minimum of our values (65535) and the OS and MIS that the peer announces in its INIT or INIT_ACK.\n  - This is a breaking change.\n  - Remove it from `sctpParameters.fbs` and other FBS types (look for `MIS` or `mis`, etc).\n  - Remove it in Rust layer.\n  - We must also remove `device.sctpCapabilities` getter from mediasoup-client because anyway we are making up those values!\n  - Also must update the website documentation.\n\n- When we invoke `close()` on a `DataProducer/Consumer` in server, we must end calling `sctpAssociation->ResetStream([streamId])` so it sends `ReConfig` to peer.\n\n- In `transport.dump()` (maybe also in `getStats()`) we must properly obtain `OS` and `MIS` according to the number of SCTP streams negotiated via INIT + INIT_ACK. And if SCTP is not yet established, then... not sure.\n  - In `Association::FillBuffer()` we should not pass `this->sctpOptions.negotiatedMaxOutboundStreams/negotiatedMaxInboundStreams` but the current values.\n\n- We need to pass `isDataChannel` to `SCTP::Association` constructor as we do in former `SctpAssociation`. Also use it in `Association::FillBuffer()`.\n  - Well, let's see. If it's only for when changing number of OS/MIS... then the new SCTP stack doesn't support it so...\n\n- Fix `dataConsumer.getBufferedAmount()` which in usrsctp returns the data buffered for all data consumers in the transport but now it will be per `DataConsumer` (SCTP stream).\n  - In `DataConsumer` class rename `SetAssociationBufferedAmount()` to `SetBufferedAmount()`.\n  - In `DataConsumer` class revisit `SctpAssociationSendBufferFull()` method.\n  - Fix the documentation in the website which says: \"The underlaying SCTP association uses a common send buffer for all data consumers, hence the value given by this method indicates the data buffered for all data consumers in the transport.\"\n\n- Look for \"TODO: SCTP\" everywhere (also in `worker/test/`).\n\n- Test Chrome/Canary with I-DATA (message interleaving):\n\n  ```bash\n  /Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary \\\n    --force-fieldtrials=\"WebRTC-DataChannelMessageInterleaving/Enabled/\" \\\n    --enable-logging=stderr \\\n    --v=1 \\\n  ```\n\n  ### Problem in ReassemblyQueue\n\n  In dsctp there is an `absl::AnyInvocable`, which is a move-only callable, unlike `std::function` which requires the callable to be copyable. The standard equivalent is `std::move_only_function`, introduced in C++23.\n\n  If you use C++23:\n\n  ```cpp\n  std::vector<std::move_only_function<void()>> deferredActions;\n  ```\n\n  In C++20 there is no `std::move_only_function` (that is C++23). The problem is that `absl::AnyInvocable` accepts move-only callables, while `std::function` requires them to be copyable.\n\n  This is relevant because in dcsctp the lambda captures `data = std::move(data)`, and `UserData` has its copy constructor deleted, so the resulting lambda is not copyable and `std::function` will reject it.\n\n  The solution for C++20 is to move the `UserData` into a `shared_ptr` so that the lambda becomes copyable:\n\n  ```cpp\n  std::vector<std::function<void()>> deferredActions;\n  ```\n\n  And when adding the action, instead of:\n\n  ```cpp\n  // dcsctp - It works because AnyInvocable accepts move-only callables\n  deferred_actions.push_back(\n      [this, tsn, data = std::move(data)]() mutable {\n          queued_bytes_ -= data.size();\n          Add(tsn, std::move(data));\n      });\n  ```\n\n  In your code:\n\n  ```cpp\n  // C++20 - UserData is not copyable, so it is wrapped in shared_ptr\n  auto sharedData = std::make_shared<UserData>(std::move(data));\n\n  this->deferredResetStreams->deferredActions.push_back(\n    [this, tsn, sharedData]() mutable\n    {\n        this->queuedBytes -= sharedData->GetPayloadLength();\n        this->Add(tsn, std::move(*sharedData));\n    });\n  ```\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/Association.hpp",
    "content": "#ifndef MS_RTC_SCTP_ASSOCIATION_HPP\n#define MS_RTC_SCTP_ASSOCIATION_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/SCTP/association/AssociationListenerDeferrer.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/association/PacketSender.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include \"RTC/SCTP/association/TransmissionControlBlock.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyDataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyInitChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieEchoChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/OperationErrorChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ReConfigChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/UnknownChunk.hpp\"\n#include \"RTC/SCTP/public/AssociationInterface.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/AssociationMetrics.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/SCTP/tx/RoundRobinSendQueue.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <FBS/sctpParameters.h>\n#include <span>\n#include <string_view>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * This is the implementation of the AssociationInterface.\n\t\t */\n\t\tclass Association : public AssociationInterface,\n\t\t                    public PacketSender::Listener,\n\t\t                    public BackoffTimerHandleInterface::Listener\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Internal SCTP association state. This is different from the public SCTP\n\t\t\t * Association state (`SCTP::Types::AssociationState`).\n\t\t\t */\n\t\t\tenum class State : uint8_t\n\t\t\t{\n\t\t\t\tNEW,\n\t\t\t\tCLOSED,\n\t\t\t\tCOOKIE_WAIT,\n\t\t\t\t// NOTE: TCB is valid in these states:\n\t\t\t\tCOOKIE_ECHOED,\n\t\t\t\tESTABLISHED,\n\t\t\t\tSHUTDOWN_PENDING,\n\t\t\t\tSHUTDOWN_SENT,\n\t\t\t\tSHUTDOWN_RECEIVED,\n\t\t\t\tSHUTDOWN_ACK_SENT\n\t\t\t};\n\n\t\t\tstatic constexpr std::string_view StateToString(State state)\n\t\t\t{\n\t\t\t\t// NOTE: We cannot use MS_TRACE() here because clang in Linux will\n\t\t\t\t// complain about \"read of non-constexpr variable 'configuration' is not\n\t\t\t\t// allowed in a constant expression\".\n\n\t\t\t\tswitch (state)\n\t\t\t\t{\n\t\t\t\t\tcase State::NEW:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"NEW\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::CLOSED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"CLOSED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::COOKIE_WAIT:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"COOKIE_WAIT\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::COOKIE_ECHOED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"COOKIE_ECHOED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::ESTABLISHED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"ESTABLISHED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::SHUTDOWN_PENDING:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SHUTDOWN_PENDING\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::SHUTDOWN_SENT:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SHUTDOWN_SENT\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::SHUTDOWN_RECEIVED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SHUTDOWN_RECEIVED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase State::SHUTDOWN_ACK_SENT:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SHUTDOWN_ACK_SENT\";\n\t\t\t\t\t}\n\n\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Struct holding local verification tag and initial TSN between having\n\t\t\t * sent the INIT Chunk until the connection is established (there is no\n\t\t\t * TCB in between).\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This is how dcSCTP does, despite RFC 9260 states that the TCB should\n\t\t\t *   also be created when an INIT Chunk is sent.\n\t\t\t */\n\t\t\tstruct PreTransmissionControlBlock\n\t\t\t{\n\t\t\t\tuint32_t localVerificationTag{ 0 };\n\t\t\t\tuint32_t localInitialTsn{ 0 };\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Metrics that are directly filled by the Association class.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is a subset of the public AssociationMetrics struct.\n\t\t\t */\n\t\t\tstruct AssociationPrivateMetrics\n\t\t\t{\n\t\t\t\tuint64_t txPacketsCount{ 0 };\n\t\t\t\tuint64_t txMessagesCount{ 0 };\n\t\t\t\tuint64_t rxPacketsCount{ 0 };\n\t\t\t\tuint64_t rxMessagesCount{ 0 };\n\t\t\t\tTypes::SctpImplementation peerImplementation{ Types::SctpImplementation::UNKNOWN };\n\t\t\t\tuint16_t negotiatedMaxOutboundStreams{ 0 };\n\t\t\t\tuint16_t negotiatedMaxInboundStreams{ 0 };\n\t\t\t\tbool usesPartialReliability{ false };\n\t\t\t\tbool usesMessageInterleaving{ false };\n\t\t\t\tbool usesReConfig{ false };\n\t\t\t\tbool usesZeroChecksum{ false };\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit Association(\n\t\t\t  const SctpOptions& sctpOptions,\n\t\t\t  AssociationListenerInterface* listener,\n\t\t\t  SharedInterface* shared);\n\n\t\t\t~Association() override;\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const override;\n\n\t\t\tflatbuffers::Offset<FBS::SctpParameters::SctpParameters> FillBuffer(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) const override;\n\n\t\t\tTypes::AssociationState GetAssociationState() const override;\n\n\t\t\t/**\n\t\t\t * May invoke `Connect()` but only if the parent transport is ready for\n\t\t\t * SCTP transmission (e.g. the WebRtcTransport has ICE and DTLS connected).\n\t\t\t */\n\t\t\tvoid MayConnect() override;\n\n\t\t\t/**\n\t\t\t * Initiate the SCTP association with the remote peer. It sends an INIT\n\t\t\t * Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The SCTP association must be in New state.\n\t\t\t * - Despite this method is public, it's never invoked since `MayConnect()`\n\t\t\t *   method is invoked instead.\n\t\t\t */\n\t\t\tvoid Connect() override;\n\n\t\t\t/**\n\t\t\t * Gracefully shutdowns the Association and sends all outstanding data.\n\t\t\t * This is an asynchronous operation and `OnAssociationClosed()` will be\n\t\t\t * called on success.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - libwebrtc never calls the corresponding DcSctpSocket::Shutdown()\n\t\t\t *   method due to a bug and hence we shouldn't either.\n\t\t\t *\n\t\t\t * @see https://issues.webrtc.org/issues/42222897\n\t\t\t */\n\t\t\tvoid Shutdown() override;\n\n\t\t\t/**\n\t\t\t * Closes the Association non-gracefully. Will send ABORT if the connection\n\t\t\t * is not already closed. No callbacks will be made after Close() has\n\t\t\t * returned. However, before Close() returns, it may have called\n\t\t\t * `OnAssociationClosed()` or `OnAssociationAborted()` callbacks.\n\t\t\t */\n\t\t\tvoid Close() override;\n\n\t\t\t/**\n\t\t\t * Retrieves the latest metrics. If the Association is not fully connected,\n\t\t\t * `std::nullopt` will be returned.\n\t\t\t */\n\t\t\tstd::optional<AssociationMetrics> GetMetrics() const override;\n\n\t\t\t/**\n\t\t\t * Returns the currently set priority for an outgoing stream. The initial\n\t\t\t * value, when not set, is `SctpOptions::defaultStreamPriority`.\n\t\t\t */\n\t\t\tuint16_t GetStreamPriority(uint16_t streamId) const override;\n\n\t\t\t/**\n\t\t\t * Sets the priority of an outgoing stream. The initial value, when not\n\t\t\t * set, is `SctpOptions::defaultStreamPriority`.\n\t\t\t */\n\t\t\tvoid SetStreamPriority(uint16_t streamId, uint16_t priority) override;\n\n\t\t\t/**\n\t\t\t * Sets the maximum size of sent messages. The initial value, when not\n\t\t\t * set, is `SctpOptions::maxSendMessageSize`.\n\t\t\t */\n\t\t\tvoid SetMaxSendMessageSize(size_t maxMessageSize) override;\n\n\t\t\t/**\n\t\t\t * Returns the number of bytes of data currently queued to be sent on a\n\t\t\t * given stream.\n\t\t\t */\n\t\t\tsize_t GetStreamBufferedAmount(uint16_t streamId) const override;\n\n\t\t\t/**\n\t\t\t * Returns the number of buffered outgoing bytes that is considered \"low\"\n\t\t\t * for a given stream. See `SetStreamBufferedAmountLowThreshold()`.\n\t\t\t */\n\t\t\tsize_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const override;\n\n\t\t\t/**\n\t\t\t * Specifies the number of bytes of buffered outgoing data that is\n\t\t\t * considered \"low\" for a given stream, which will trigger\n\t\t\t * `OnAssociationStreamBufferedAmountLow()` event. The default value is 0.\n\t\t\t */\n\t\t\tvoid SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) override;\n\n\t\t\t/**\n\t\t\t * Resetting streams is an asynchronous operation and the results will be\n\t\t\t * notified using `OnAssociationStreamsResetPerformed()` on success and\n\t\t\t * `OnAssociationStreamsResetFailed()` on failure.\n\t\t\t *\n\t\t\t * When it's known that the peer has reset its own outgoing streams,\n\t\t\t * `OnAssociationInboundStreamsReset()` is called.\n\t\t\t *\n\t\t\t * Resetting streams can only be done on an established association that\n\t\t\t * supports stream resetting. Calling this method on e.g. a closed SCTP\n\t\t\t * association or streams that don't support resetting will not perform\n\t\t\t * any operation.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Only outbound streams can be reset.\n\t\t\t * - Resetting a stream will also remove all queued messages on those\n\t\t\t *   streams, but will ensure that the currently sent message (if any) is\n\t\t\t *   fully sent before closing the stream.\n\t\t\t */\n\t\t\tTypes::ResetStreamsStatus ResetStreams(std::span<const uint16_t> outboundStreamIds) override;\n\n\t\t\t/**\n\t\t\t * Sends an SCTP message using the provided send options. Sending a message\n\t\t\t * is an asynchronous operation, and the `OnAssociationError()` callback\n\t\t\t * may be invoked to indicate any errors in sending the message.\n\t\t\t *\n\t\t\t * The association does not have to be established before calling this\n\t\t\t * method. If it's called before there is an established association, the\n\t\t\t * message will be queued.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Copy constructor is disabled and there is move constructor. That's why\n\t\t\t *   we don't pass a reference here. We could pass `Message&&` but that's\n\t\t\t *   worse opens the door to bugs.\n\t\t\t */\n\t\t\tTypes::SendMessageStatus SendMessage(\n\t\t\t  Message message, const SendMessageOptions& sendMessageOptions) override;\n\n\t\t\t/**\n\t\t\t * Sends SCTP messages using the provided send options. Sending a message\n\t\t\t * is an asynchronous operation, and the `OnAssociationError()` callback\n\t\t\t * may be invoked to indicate any errors in sending a message.\n\t\t\t *\n\t\t\t * The association does not have to be established before calling this\n\t\t\t * method. If it's called before there is an established association, the\n\t\t\t * message will be queued.\n\t\t\t *\n\t\t\t * This has identical semantics to `SendMessage()', except that it may\n\t\t\t * coalesce many messages into a single SCTP Packet if they would fit.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Same as in `SendMessage()`.\n\t\t\t */\n\t\t\tstd::vector<Types::SendMessageStatus> SendManyMessages(\n\t\t\t  std::span<Message> messages, const SendMessageOptions& sendMessageOptions) override;\n\n\t\t\t/**\n\t\t\t * Receives SCTP data (hopefully an SCTP Packet) from the remote peer.\n\t\t\t */\n\t\t\tvoid ReceiveSctpData(const uint8_t* data, size_t len) override;\n\n\t\tprivate:\n\t\t\tvoid InternalClose(Types::ErrorKind errorKind, const std::string_view& message);\n\n\t\t\tvoid SetState(State state, const std::string_view& message);\n\n\t\t\tvoid AddCapabilitiesParametersToInitOrInitAckChunk(AnyInitChunk* chunk) const;\n\n\t\t\tvoid CreateTransmissionControlBlock(\n\t\t\t  uint32_t localVerificationTag,\n\t\t\t  uint32_t remoteVerificationTag,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  uint64_t tieTag,\n\t\t\t  const NegotiatedCapabilities& negotiatedCapabilities);\n\n\t\t\tstd::unique_ptr<Packet> CreatePacket() const;\n\n\t\t\tstd::unique_ptr<Packet> CreatePacketWithVerificationTag(uint32_t verificationTag) const;\n\n\t\t\tvoid SendInitChunk();\n\n\t\t\tvoid SendShutdownChunk();\n\n\t\t\tvoid SendShutdownAckChunk();\n\n\t\t\t/**\n\t\t\t * Sends SHUTDOWN or SHUTDOWN-ACK if the Association is shutting down and\n\t\t\t * if all outstanding data has been acknowledged.\n\t\t\t */\n\t\t\tvoid MaySendShutdownOrShutdownAckChunk();\n\n\t\t\t/**\n\t\t\t * If the Association is shutting down, responds SHUTDOWN to any incoming\n\t\t\t * DATA.\n\t\t\t */\n\t\t\tvoid MaySendShutdownOnPacketReceived(const Packet* receivedPacket);\n\n\t\t\t/**\n\t\t\t * If there are streams pending to be reset, send a request to reset them.\n\t\t\t */\n\t\t\tvoid MaySendResetStreamsRequest();\n\n\t\t\t/**\n\t\t\t * Called whenever data has been received, or the cumulative acknowledgment\n\t\t\t * TSN has moved, that may result in delivering messages.\n\t\t\t */\n\t\t\tvoid MayDeliverMessages();\n\n\t\t\tTypes::SendMessageStatus InternalSendMessageCheck(\n\t\t\t  const Message& message, const SendMessageOptions& sendMessageOptions);\n\n\t\t\tbool ValidateReceivedPacket(const Packet* receivedPacket);\n\n\t\t\tbool HandleReceivedChunk(const Packet* receivedPacket, const Chunk* receivedChunk);\n\n\t\t\tvoid HandleReceivedInitChunk(const Packet* receivedPacket, const InitChunk* receivedInitChunk);\n\n\t\t\tvoid HandleReceivedInitAckChunk(\n\t\t\t  const Packet* receivedPacket, const InitAckChunk* receivedInitAckChunk);\n\n\t\t\tvoid HandleReceivedCookieEchoChunk(\n\t\t\t  const Packet* receivedPacket, const CookieEchoChunk* receivedCookieEchoChunk);\n\n\t\t\tbool HandleReceivedCookieEchoChunkWithTcb(const Packet* receivedPacket, const StateCookie* cookie);\n\n\t\t\tvoid HandleReceivedCookieAckChunk(\n\t\t\t  const Packet* receivedPacket, const CookieAckChunk* receivedCookieAckChunk);\n\n\t\t\tvoid HandleReceivedShutdownChunk(\n\t\t\t  const Packet* receivedPacket, const ShutdownChunk* receivedShutdownChunk);\n\n\t\t\tvoid HandleReceivedShutdownAckChunk(\n\t\t\t  const Packet* receivedPacket, const ShutdownAckChunk* receivedShutdownAckChunk);\n\n\t\t\tvoid HandleReceivedShutdownCompleteChunk(\n\t\t\t  const Packet* receivedPacket, const ShutdownCompleteChunk* receivedShutdownCompleteChunk);\n\n\t\t\tvoid HandleReceivedOperationErrorChunk(\n\t\t\t  const Packet* receivedPacket, const OperationErrorChunk* receivedOperationErrorChunk);\n\n\t\t\tvoid HandleReceivedAbortAssociationChunk(\n\t\t\t  const Packet* receivedPacket, const AbortAssociationChunk* receivedAbortAssociationChunk);\n\n\t\t\tvoid HandleReceivedHeartbeatRequestChunk(\n\t\t\t  const Packet* receivedPacket, const HeartbeatRequestChunk* receivedHeartbeatRequestChunk);\n\n\t\t\tvoid HandleReceivedHeartbeatAckChunk(\n\t\t\t  const Packet* receivedPacket, const HeartbeatAckChunk* receivedHeartbeatAckChunk);\n\n\t\t\tvoid HandleReceivedReConfigChunk(\n\t\t\t  const Packet* receivedPacket, const ReConfigChunk* receivedReConfigChunk);\n\n\t\t\tvoid HandleReceivedForwardTsnChunk(\n\t\t\t  const Packet* receivedPacket, const ForwardTsnChunk* receivedForwardTsnChunk);\n\n\t\t\tvoid HandleReceivedIForwardTsnChunk(\n\t\t\t  const Packet* receivedPacket, const IForwardTsnChunk* receivedIForwardTsnChunk);\n\n\t\t\tvoid HandleReceivedAnyForwardTsnChunk(\n\t\t\t  const Packet* receivedPacket, const AnyForwardTsnChunk* receivedAnyForwardTsnChunk);\n\n\t\t\tvoid HandleReceivedDataChunk(const Packet* receivedPacket, const DataChunk* receivedDataChunk);\n\n\t\t\tvoid HandleReceivedIDataChunk(const Packet* receivedPacket, const IDataChunk* receivedIDataChunk);\n\n\t\t\tvoid HandleReceivedAnyDataChunk(\n\t\t\t  const Packet* receivedPacket, const AnyDataChunk* receivedAnyDataChunk);\n\n\t\t\tvoid HandleReceivedSackChunk(const Packet* receivedPacket, const SackChunk* receivedSackChunk);\n\n\t\t\tbool HandleReceivedUnknownChunk(\n\t\t\t  const Packet* receivedPacket, const UnknownChunk* receivedUnknownChunk);\n\n\t\t\tvoid OnT1InitTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\tvoid OnT1CookieTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\tvoid OnT2ShutdownTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\ttemplate<typename... States>\n\t\t\tvoid AssertState(States... expectedStates) const;\n\n\t\t\ttemplate<typename... States>\n\t\t\tvoid AssertNotState(States... unexpectedStates) const;\n\n\t\t\t/**\n\t\t\t * Returns true if there is a TCB, and false otherwise (and reports an\n\t\t\t * error).\n\t\t\t */\n\t\t\tbool ValidateHasTcb();\n\n\t\t\tvoid AssertHasTcb() const;\n\n\t\t\tvoid AssertIsConsistent() const;\n\n\t\t\t/* Pure virtual methods inherited from PacketSender::Listener. */\n\t\tpublic:\n\t\t\tvoid OnPacketSenderPacketSent(PacketSender* packetSender, const Packet* packet, bool sent) override;\n\n\t\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\t\tpublic:\n\t\t\tvoid OnBackoffTimer(\n\t\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;\n\n\t\tprivate:\n\t\t\t// SCTP options given in the constructor.\n\t\t\tSctpOptions sctpOptions;\n\t\t\t// Listener. It's a `AssociationListenerDeferrer` which implements\n\t\t\t// `AssociationListenerInterface`.\n\t\t\tAssociationListenerDeferrer associationListenerDeferrer;\n\t\t\tSharedInterface* shared;\n\t\t\t// SCTP association internal state.\n\t\t\tState state{ State::NEW };\n\t\t\t// Packet sender.\n\t\t\tPacketSender packetSender;\n\t\t\t// The actual send queue implementation. As data can be sent before the\n\t\t\t// connection is established, this component is not in the TCB.\n\t\t\tRoundRobinSendQueue sendQueue;\n\t\t\t// To keep settings between sending of INIT Chunk and establishment of\n\t\t\t// the connection.\n\t\t\tPreTransmissionControlBlock preTcb;\n\t\t\t// Once the SCTP association is established a Transmission Control Block\n\t\t\t// is created.\n\t\t\tstd::unique_ptr<TransmissionControlBlock> tcb;\n\t\t\t// Private metrics.\n\t\t\tAssociationPrivateMetrics privateMetrics;\n\t\t\t// T1-init timer.\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> t1InitTimer;\n\t\t\t// T1-cookie timer.\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> t1CookieTimer;\n\t\t\t// T2-shutdown timer.\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> t2ShutdownTimer;\n\t\t\t// Max SCTP Packet length.\n\t\t\tconst size_t maxPacketLength;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/AssociationListenerDeferrer.hpp",
    "content": "#ifndef MS_RTC_SCTP_ASSOCIATION_LISTENER_DEFERRED_HPP\n#define MS_RTC_SCTP_ASSOCIATION_LISTENER_DEFERRED_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <span>\n#include <string>\n#include <string_view>\n#include <variant>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tclass AssociationListenerDeferrer : public AssociationListenerInterface\n\t\t{\n\t\tpublic:\n\t\t\tclass ScopedDeferrer\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit ScopedDeferrer(AssociationListenerDeferrer& listenerDeferrer);\n\n\t\t\t\t~ScopedDeferrer();\n\n\t\t\tprivate:\n\t\t\t\tAssociationListenerDeferrer& listenerDeferrer;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tstruct Error\n\t\t\t{\n\t\t\t\tTypes::ErrorKind errorKind;\n\t\t\t\tstd::string message;\n\t\t\t};\n\n\t\t\tstruct StreamReset\n\t\t\t{\n\t\t\t\tstd::vector<uint16_t> streamIds;\n\t\t\t\tstd::string errorMessage;\n\t\t\t};\n\n\t\t\t// Use a pre-sized variant for storage to avoid double heap allocation. This\n\t\t\t// variant can hold all cases of stored data.\n\t\t\tusing CallbackData = std::variant<std::monostate, Message, Error, StreamReset, uint16_t>;\n\n\t\t\tusing Callback = std::function<void(CallbackData, AssociationListenerInterface*)>;\n\n\t\tpublic:\n\t\t\texplicit AssociationListenerDeferrer(AssociationListenerInterface* innerListener);\n\n\t\tprivate:\n\t\t\tvoid SetReady();\n\n\t\t\tvoid TriggerDeferredCallbacks();\n\n\t\tpublic:\n\t\t\t/* Pure virtual methods inherited from RTC::STCP::AssociationListenerInterface. */\n\t\t\tbool OnAssociationSendData(const uint8_t* data, size_t len) override;\n\n\t\t\tvoid OnAssociationConnecting() override;\n\n\t\t\tvoid OnAssociationConnected() override;\n\n\t\t\tvoid OnAssociationFailed(Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\n\t\t\tvoid OnAssociationClosed(Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\n\t\t\tvoid OnAssociationRestarted() override;\n\n\t\t\tvoid OnAssociationError(Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\n\t\t\tvoid OnAssociationMessageReceived(Message message) override;\n\n\t\t\tvoid OnAssociationStreamsResetPerformed(std::span<const uint16_t> outboundStreamIds) override;\n\n\t\t\tvoid OnAssociationStreamsResetFailed(\n\t\t\t  std::span<const uint16_t> outboundStreamIds, std::string_view errorMessage) override;\n\n\t\t\tvoid OnAssociationInboundStreamsReset(std::span<const uint16_t> inboundStreamIds) override;\n\n\t\t\tvoid OnAssociationStreamBufferedAmountLow(uint16_t streamId) override;\n\n\t\t\tvoid OnAssociationTotalBufferedAmountLow() override;\n\n\t\t\tbool OnAssociationIsTransportReadyForSctp() override;\n\n\t\t\tvoid OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) override;\n\n\t\t\tvoid OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered) override;\n\n\t\t\tvoid OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) override;\n\n\t\t\tvoid OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) override;\n\n\t\tprivate:\n\t\t\tAssociationListenerInterface* innerListener;\n\t\t\tbool ready{ false };\n\t\t\tstd::vector<std::pair<Callback, CallbackData>> deferredCallbacks;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/HeartbeatHandler.hpp",
    "content": "#ifndef MS_RTC_SCTP_HEARTBEAT_HANDLER_HPP\n#define MS_RTC_SCTP_HEARTBEAT_HANDLER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * HeartbeatHandler handles all logic around sending heartbeats and receiving\n\t\t * the responses, as well as receiving incoming heartbeat requests.\n\t\t *\n\t\t * Heartbeats are sent on idle connections to ensure that the connection is\n\t\t * still healthy and to measure the RTT. If a number of heartbeats time out,\n\t\t * the connection will eventually be closed.\n\t\t */\n\t\tclass HeartbeatHandler : public BackoffTimerHandleInterface::Listener\n\t\t{\n\t\tpublic:\n\t\t\tHeartbeatHandler(\n\t\t\t  AssociationListenerInterface& associationListener,\n\t\t\t  const SctpOptions& sctpOptions,\n\t\t\t  SharedInterface* shared,\n\t\t\t  TransmissionControlBlockContextInterface* tcbContext);\n\n\t\t\t~HeartbeatHandler() override;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Called when the heartbeat interval timer should be restarted. This is\n\t\t\t * generally done every time data is sent, which makes the timer expire\n\t\t\t * when the connection is idle.\n\t\t\t */\n\t\t\tvoid RestartTimer();\n\n\t\t\t/**\n\t\t\t * Called on received HEARTBEAT_REQUEST Chunk.\n\t\t\t */\n\t\t\tvoid HandleReceivedHeartbeatRequestChunk(\n\t\t\t  const HeartbeatRequestChunk* receivedHeartbeatRequestChunk);\n\n\t\t\t/**\n\t\t\t * Called on received HEARTBEAT_ACK Chunk.\n\t\t\t */\n\t\t\tvoid HandleReceivedHeartbeatAckChunk(const HeartbeatAckChunk* receivedHeartbeatAckChunk);\n\n\t\tprivate:\n\t\t\tvoid OnIntervalTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\tvoid OnTimeoutTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\t\tpublic:\n\t\t\tvoid OnBackoffTimer(\n\t\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;\n\n\t\tprivate:\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t\tconst SctpOptions sctpOptions;\n\t\t\tSharedInterface* shared;\n\t\t\tTransmissionControlBlockContextInterface* tcbContext;\n\t\t\t// The time for a connection to be idle before a heartbeat is sent.\n\t\t\tconst uint64_t intervalDurationMs;\n\t\t\t// Adding RTT to the duration will add some jitter, which is good in\n\t\t\t// production, but less good in unit tests, which is why it can be disabled.\n\t\t\tconst bool intervalDurationShouldIncludeRtt;\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> intervalTimer;\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> timeoutTimer;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/NegotiatedCapabilities.hpp",
    "content": "#ifndef MS_RTC_SCTP_NEGOTIATED_CAPABILITIES_HPP\n#define MS_RTC_SCTP_NEGOTIATED_CAPABILITIES_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyInitChunk.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Those are the SCTP association capabilities negotiated during the\n\t\t * handshake, meaning that both endpoints support them and have agreed on\n\t\t * them.\n\t\t */\n\t\tstruct NegotiatedCapabilities\n\t\t{\n\t\t\t/**\n\t\t\t * Create a NegotiatedCapabilities struct. Intended to be used during\n\t\t\t * the SCTP association handshake flow.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Given `remoteChunk` must be an INIT or an INIT_ACK Chunk.\n\t\t\t */\n\t\t\tstatic NegotiatedCapabilities Factory(\n\t\t\t  const SctpOptions& sctpOptions, const AnyInitChunk* remoteChunk);\n\n\t\t\t/**\n\t\t\t * Negotiated maximum number of outbound streams (OS).\n\t\t\t */\n\t\t\tuint16_t negotiatedMaxOutboundStreams{ 0 };\n\n\t\t\t/**\n\t\t\t * Negotiated maximum number of inbound streams (MIS).\n\t\t\t */\n\t\t\tuint16_t negotiatedMaxInboundStreams{ 0 };\n\n\t\t\t/**\n\t\t\t * Partial Reliability Extension.\n\t\t\t *\n\t\t\t * @see RFC 3758.\n\t\t\t */\n\t\t\tbool partialReliability{ false };\n\n\t\t\t/**\n\t\t\t * Stream Schedulers and User Message Interleaving (I-DATA Chunks).\n\t\t\t *\n\t\t\t * @see RFC 8260.\n\t\t\t */\n\t\t\tbool messageInterleaving{ false };\n\n\t\t\t/**\n\t\t\t * Stream Re-Configuration.\n\t\t\t *\n\t\t\t * @see RFC 6525.\n\t\t\t */\n\t\t\tbool reConfig{ false };\n\n\t\t\t/**\n\t\t\t * Zero Checksum.\n\t\t\t *\n\t\t\t * @see RFC 9653.\n\t\t\t */\n\t\t\tbool zeroChecksum{ false };\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/PacketSender.hpp",
    "content": "#ifndef MS_RTC_SCTP_PACKET_SENDER_HPP\n#define MS_RTC_SCTP_PACKET_SENDER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tclass PacketSender\n\t\t{\n\t\tpublic:\n\t\t\tclass Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~Listener() = default;\n\n\t\t\tpublic:\n\t\t\t\tvirtual void OnPacketSenderPacketSent(\n\t\t\t\t  PacketSender* packetSender, const Packet* packet, bool sent) = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tPacketSender(Listener* listener, AssociationListenerInterface& associationListener);\n\n\t\t\t~PacketSender();\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Notifies the parent about a Packet to be sent to the peer and returns a\n\t\t\t * boolean indicating whether the Packet was sent or not.\n\t\t\t *\n\t\t\t * This method also writes the Packet checksum field depending on the value\n\t\t\t * of `writeChecksum`.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method does not delete the given `packet`. The caller must do it\n\t\t\t *   after invoking this method.\n\t\t\t */\n\t\t\tbool SendPacket(Packet* packet, bool writeChecksum = true);\n\n\t\tprivate:\n\t\t\tListener* listener;\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/StateCookie.hpp",
    "content": "#ifndef MS_RTC_SCTP_STATE_COOKIE_HPP\n#define MS_RTC_SCTP_STATE_COOKIE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/Serializable.hpp\"\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * This is the State Cookie we generate and put into a State Cookie\n\t\t * Parameter when we send INIT_ACK Chunk to the remote peer.\n\t\t *\n\t\t * The syntax we use is as follows. Note that we use a fixed length of\n\t\t * StateCookieLength bytes.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                            Magic 1                            |\n\t\t * |                                                               |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                    Local Verification Tag                     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                    Remote Verification Tag                    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      Local Initial TSN                        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      Remote Initial TSN                       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |       Remote Advertised Receiver Window Credit (a_rwnd)       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                            Tie-Tag                            |\n\t\t * |                                                               |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                     Negotiated Capabilities                   /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * Negotiated Capabilities are serialized as follows:\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   (Reserved)  |       |D|C|B|A|            Magic 2            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |      Max Outbound Streams     |       Max Inbound Streams     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Flag A (partialReliability): Partial Reliability Extension.\n\t\t * - Flag B (messageInterleaving): Stream Schedulers and User Message\n\t\t *   Interleaving (I-DATA).\n\t\t * - Flag C (reconfig): Stream Reconfiguration.\n\t\t * - Flag D (zeroChecksum): Zero Checksum.\n\t\t */\n\t\tclass StateCookie : public Serializable\n\t\t{\n\t\tprivate:\n\t\t\tstruct NegotiatedCapabilitiesField\n\t\t\t{\n\t\t\t\tuint8_t reserved;\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t bitA : 1;\n\t\t\t\tuint8_t bitB : 1;\n\t\t\t\tuint8_t bitC : 1;\n\t\t\t\tuint8_t bitD : 1;\n\t\t\t\tuint8_t unusedBits : 4;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t unusedBits : 4;\n\t\t\t\tuint8_t bitD : 1;\n\t\t\t\tuint8_t bitC : 1;\n\t\t\t\tuint8_t bitB : 1;\n\t\t\t\tuint8_t bitA : 1;\n#endif\n\t\t\t\tuint16_t magic2;\n\t\t\t\tuint16_t negotiatedMaxOutboundStreams;\n\t\t\t\tuint16_t negotiatedMaxInboundStreams;\n\t\t\t};\n\n\t\tpublic:\n\t\t\t// Fixed total length of our generated State Cookies.\n\t\t\tstatic constexpr size_t StateCookieLength{ 44 };\n\t\t\t// Offset in the State Cookie where the Negotiated Capabilitied are\n\t\t\t// located.\n\t\t\tstatic constexpr size_t NegotiatedCapabilitiesOffset{ 36 };\n\t\t\t// Magic value we prefix the State Cookie with. Note that it is\n\t\t\t// \"msworker\" in ASCII bytes.\n\t\t\tstatic constexpr uint64_t Magic1{ 0x6D73776F726B6572 };\n\t\t\tstatic constexpr size_t Magic1Length{ 8 };\n\t\t\t// Magic value used within the Negotiated Capabilities block.\n\t\t\tstatic constexpr uint16_t Magic2{ 0xAD81 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Whether the given buffer is a StateCookie generated by mediasoup.\n\t\t\t */\n\t\t\tstatic bool IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Parse a StateCookie supposely generated by mediasoup.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` must be the exact length of the State Cookie.\n\t\t\t */\n\t\t\tstatic StateCookie* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a StateCookie.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the real length of the State\n\t\t\t * Cookie.\n\t\t\t */\n\t\t\tstatic StateCookie* Factory(\n\t\t\t  uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  uint32_t localVerificationTag,\n\t\t\t  uint32_t remoteVerificationTag,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  uint64_t tieTag,\n\t\t\t  const NegotiatedCapabilities& negotiatedCapabilities);\n\n\t\t\t/**\n\t\t\t * Serialize a StateCookie (based on given arguments) in the given buffer.\n\t\t\t */\n\t\t\tstatic void Write(\n\t\t\t  uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  uint32_t localVerificationTag,\n\t\t\t  uint32_t remoteVerificationTag,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  uint64_t tieTag,\n\t\t\t  const NegotiatedCapabilities& negotiatedCapabilities);\n\n\t\t\t/**\n\t\t\t * Determine the SCTP implementation of the generator of State Cookie\n\t\t\t * given in the buffer.\n\t\t\t */\n\t\t\tstatic Types::SctpImplementation DetermineSctpImplementation(\n\t\t\t  const uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\tStateCookie(uint8_t* buffer, size_t bufferLength);\n\n\t\t\t~StateCookie() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tStateCookie* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\t/**\n\t\t\t * The value of the Initiate Tag field we put in our INIT or INIT_ACK\n\t\t\t * Chunk. Packets sent by the remote peer must include this value in\n\t\t\t * their Verification Tag field.\n\t\t\t */\n\t\t\tuint32_t GetLocalVerificationTag() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initiate Tag field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk. Packets sent by us to the peer must include this value\n\t\t\t * in their Verification Tag field.\n\t\t\t */\n\t\t\tuint32_t GetRemoteVerificationTag() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initial TSN field we put in our INIT or INIT_ACK\n\t\t\t * Chunk.\n\t\t\t */\n\t\t\tuint32_t GetLocalInitialTsn() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initial TSN field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk.\n\t\t\t */\n\t\t\tuint32_t GetRemoteInitialTsn() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 20);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Advertised Receiver Window Credit field we put in our\n\t\t\t * INIT or INIT_ACK Chunk.\n\t\t\t */\n\t\t\tuint32_t GetRemoteAdvertisedReceiverWindowCredit() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 24);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Tie-Tag used as a nonce when connecting.\n\t\t\t */\n\t\t\tuint64_t GetTieTag() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get8Bytes(GetBuffer(), 28);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Negotiated association capabilities.\n\t\t\t */\n\t\t\tNegotiatedCapabilities GetNegotiatedCapabilities() const;\n\n\t\tprivate:\n\t\t\tNegotiatedCapabilitiesField* GetNegotiatedCapabilitiesField() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<NegotiatedCapabilitiesField*>(\n\t\t\t\t  const_cast<uint8_t*>(GetBuffer()) + StateCookie::NegotiatedCapabilitiesOffset);\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/StreamResetHandler.hpp",
    "content": "#ifndef MS_RTC_SCTP_STREAM_RESET_HANDLER_HPP\n#define MS_RTC_SCTP_STREAM_RESET_HANDLER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/chunks/ReConfigChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/rx/DataTracker.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyQueue.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionQueue.hpp\"\n#include \"Utils/UnwrappedSequenceNumber.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <span>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * StreamResetHandler handles sending outgoing stream reset requests (to\n\t\t * close an SCTP stream, which translates to closing a data channel in\n\t\t * WebRTC).\n\t\t *\n\t\t * It also handles incoming \"outgoing stream reset requests\", when the peer\n\t\t * wants to close its streams.\n\t\t *\n\t\t * Resetting streams is an asynchronous operation where the client will\n\t\t * request a request a stream to be reset, but then it might not be\n\t\t * performed exactly at this point. First, the sender might need to discard\n\t\t * all messages that have been enqueued for this stream, or it may select to\n\t\t * wait until all have been sent. At least, it must wait for the currently\n\t\t * sending fragmented message to be fully sent, because a stream can't be\n\t\t * reset while having received half a message. In the stream reset request,\n\t\t * the \"sender's last assigned TSN\" is provided, which is simply the TSN for\n\t\t * which the receiver should've received all messages before this value,\n\t\t * before the stream can be reset. Since fragments can get lost or sent\n\t\t * out-of-order, the receiver of a request may not have received all the\n\t\t * data just yet, and then it will respond to the sender: \"In progress\". In\n\t\t * other words, try again. The sender will then need to start a timer and\n\t\t * try the very same request again (but with a new sequence number) until\n\t\t * the receiver successfully performs the operation.\n\t\t *\n\t\t * All this can take some time, and may be driven by timers, so the client\n\t\t * will ultimately be notified using callbacks.\n\t\t *\n\t\t * In this implementation, when a stream is reset, the queued but\n\t\t * not-yet-sent messages will be discarded, but that may change in the future.\n\t\t * RFC8831 allows both behaviors.\n\t\t */\n\t\tclass StreamResetHandler : public BackoffTimerHandleInterface::Listener\n\t\t{\n\t\tprivate:\n\t\t\tenum class ReqSeqNbrValidationResult : uint8_t\n\t\t\t{\n\t\t\t\tVALID,\n\t\t\t\tRETRANSMISSION,\n\t\t\t\tBAD_SEQUENCE_NUMBER,\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Represents a stream request operation. There can only be one ongoing at\n\t\t\t * any time, and a sent request may either succeed, fail or result in the\n\t\t\t * receiver signaling that it can't process it right now, and then it will\n\t\t\t * be retried.\n\t\t\t */\n\t\t\tclass CurrentRequest\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tCurrentRequest(uint32_t senderLastAssignedTsn, std::vector<uint16_t> streamIds)\n\t\t\t\t  : reqSeqNbr(std::nullopt),\n\t\t\t\t    senderLastAssignedTsn(senderLastAssignedTsn),\n\t\t\t\t    streamIds(std::move(streamIds))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Returns the current request sequence number, if this request has been\n\t\t\t\t * sent (check `HasBeenFirst()` first). Will return 0 if the request is\n\t\t\t\t * just prepared (or scheduled for retransmission) but not yet sent.\n\t\t\t\t */\n\t\t\t\tuint32_t GetReqSeqNbr() const\n\t\t\t\t{\n\t\t\t\t\treturn this->reqSeqNbr.value_or(0);\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * The sender's last assigned TSN, from the retransmission queue. The\n\t\t\t\t * receiver uses this to know when all data up to this TSN has been\n\t\t\t\t * received, to know when to safely reset the stream.\n\t\t\t\t */\n\t\t\t\tuint32_t GetSenderLastAssignedTsn() const\n\t\t\t\t{\n\t\t\t\t\treturn senderLastAssignedTsn;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * The streams that are to be reset.\n\t\t\t\t */\n\t\t\t\tconst std::vector<uint16_t>& GetStreamIds() const\n\t\t\t\t{\n\t\t\t\t\treturn this->streamIds;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * If this request has been sent yet. If not, then it's either because\n\t\t\t\t * it has only been prepared and not yet sent, or because the received\n\t\t\t\t * couldn't apply the request, and then the exact same request will be\n\t\t\t\t * retried, but with a new sequence number.\n\t\t\t\t */\n\t\t\t\tbool HasBeenSent() const\n\t\t\t\t{\n\t\t\t\t\treturn this->reqSeqNbr.has_value();\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * If the receiver can't apply the request yet (and answered \"In\n\t\t\t\t * Progress\"), this will be called to prepare the request to be\n\t\t\t\t * retransmitted at a later time.\n\t\t\t\t */\n\t\t\t\tvoid PrepareRetransmission()\n\t\t\t\t{\n\t\t\t\t\tthis->reqSeqNbr = std::nullopt;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * If the request hasn't been sent yet, this assigns it a request\n\t\t\t\t * number.\n\t\t\t\t */\n\t\t\t\tvoid PrepareToSend(uint32_t newReqSeqNbr)\n\t\t\t\t{\n\t\t\t\t\tthis->reqSeqNbr = newReqSeqNbr;\n\t\t\t\t}\n\n\t\t\t\tvoid SetDeferred(bool isDeferred)\n\t\t\t\t{\n\t\t\t\t\tthis->isDeferred = isDeferred;\n\t\t\t\t}\n\n\t\t\t\tbool IsDeferred() const\n\t\t\t\t{\n\t\t\t\t\treturn this->isDeferred;\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// If this is set, this request has been sent. If it's not set, the\n\t\t\t\t// request has been prepared, but has not yet been sent. This is\n\t\t\t\t// typically used when the peer responded \"in progress\" and the same\n\t\t\t\t// request (but a different request number) must be sent again.\n\t\t\t\tstd::optional<uint32_t> reqSeqNbr{ 0 };\n\t\t\t\t// The sender's (that's us) last assigned TSN, from the retransmission\n\t\t\t\t// queue.\n\t\t\t\tuint32_t senderLastAssignedTsn{ 0 };\n\t\t\t\t// The streams that are to be reset in this request.\n\t\t\t\tstd::vector<uint16_t> streamIds;\n\t\t\t\t// If the request is deferred (received \"In Progress\"), the next timeout\n\t\t\t\t// should not be treated as a timeout.\n\t\t\t\tbool isDeferred{ false };\n\t\t\t};\n\n\t\tprivate:\n\t\t\tusing UnwrappedReConfigRequestSn = Utils::UnwrappedSequenceNumber<uint32_t>;\n\n\t\tpublic:\n\t\t\tStreamResetHandler(\n\t\t\t  AssociationListenerInterface& associationListener,\n\t\t\t  SharedInterface* shared,\n\t\t\t  TransmissionControlBlockContextInterface* tcbContext,\n\t\t\t  DataTracker* dataTracker,\n\t\t\t  ReassemblyQueue* reassemblyQueue,\n\t\t\t  RetransmissionQueue* retransmissionQueue);\n\n\t\t\t~StreamResetHandler() override;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Initiates reset of the provided streams. While there can only be one\n\t\t\t * ongoing stream reset request at any time, this method can be called at\n\t\t\t * any time and also multiple times. It will enqueue requests that can't\n\t\t\t * be directly fulfilled, and will asynchronously process them when any\n\t\t\t * ongoing request has completed.\n\t\t\t */\n\t\t\tvoid ResetStreams(std::span<const uint16_t> outgoingStreamIds);\n\n\t\t\t/**\n\t\t\t * Whether a Reset Streams request should be send. Will return `false` if\n\t\t\t * there is no need to create a request (no streams to reset) or if there\n\t\t\t * already is an ongoing stream reset request that hasn't completed yet.\n\t\t\t */\n\t\t\tbool ShouldSendStreamResetRequest() const;\n\n\t\t\t/**\n\t\t\t * Adds a Reset Streams request to the given Packet. Will start the\n\t\t\t * reconfig timer.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The caller must check `ShouldCreateStreamResetRequest()` first and\n\t\t\t *   only invoke this method if the former returns `true`.\n\t\t\t */\n\t\t\tvoid AddStreamResetRequest(Packet* packet);\n\n\t\t\t/**\n\t\t\t * Called when handling and incoming RE-CONFIG chunk. Processes a stream\n\t\t\t * reconfiguration chunk and may send a RE-CONFIG back to the peer with\n\t\t\t * either 1 or 2 responses.\n\t\t\t */\n\t\t\tvoid HandleReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Called to validate a received RE-CONFIG chunk.\n\t\t\t */\n\t\t\tbool ValidateReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk);\n\n\t\t\t/**\n\t\t\t * Adds the actual RE-CONFIG chunk to the given Packet. A request (which\n\t\t\t * set `this->currentRequest`) must have been created prior.\n\t\t\t */\n\t\t\tvoid AddReConfigChunk(Packet* packet);\n\n\t\t\t/**\n\t\t\t * Called to validate the `reqSeqNbr`, that it's the next in sequence.\n\t\t\t */\n\t\t\tReqSeqNbrValidationResult ValidateReqSeqNbr(UnwrappedReConfigRequestSn reqSeqNbr);\n\n\t\t\t/**\n\t\t\t * Called when this Association receives an outgoing stream reset request.\n\t\t\t * It might either be performed straight away, or have to be deferred, and\n\t\t\t * the result of that will be put in `responses`.\n\t\t\t */\n\t\t\tvoid HandleReceivedOutgoingSsnResetRequestParameter(\n\t\t\t  const OutgoingSsnResetRequestParameter* receivedOutgoingSsnResetRequestParameter,\n\t\t\t  ReConfigChunk* reConfigChunk);\n\n\t\t\t/**\n\t\t\t * Called when this Association receives an incoming stream reset request.\n\t\t\t * This isn't really supported, but a successful response is put in\n\t\t\t * `responses`.\n\t\t\t */\n\t\t\tvoid HandleReceivedIncomingSsnResetRequestParameter(\n\t\t\t  const IncomingSsnResetRequestParameter* receivedIncomingSsnResetRequestParameter,\n\t\t\t  ReConfigChunk* reConfigChunk);\n\n\t\t\t/**\n\t\t\t * Called when receiving a response to an outgoing stream reset request.\n\t\t\t * It will either commit the stream resetting, if the operation was\n\t\t\t * successful, or will schedule a retry if it was deferred. And if it\n\t\t\t * failed, the operation will be rolled back.\n\t\t\t */\n\t\t\tvoid HandleReceivedReconfigurationResponseParameter(\n\t\t\t  const ReconfigurationResponseParameter* receivedReconfigurationResponseParameter);\n\n\t\t\tvoid OnReConfigTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\t\tpublic:\n\t\t\tvoid OnBackoffTimer(\n\t\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;\n\n\t\tprivate:\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t\tSharedInterface* shared;\n\t\t\tTransmissionControlBlockContextInterface* tcbContext;\n\t\t\tDataTracker* dataTracker;\n\t\t\tReassemblyQueue* reassemblyQueue;\n\t\t\tRetransmissionQueue* retransmissionQueue;\n\t\t\tUnwrappedReConfigRequestSn::Unwrapper incomingReConfigRequestSnUnwrapper;\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> reConfigTimer;\n\t\t\t// The next sequence number for outgoing stream requests.\n\t\t\tuint32_t nextOutgoingReqSeqNbr;\n\t\t\t// For incoming requests. Last processed request sequence number.\n\t\t\tUnwrappedReConfigRequestSn lastProcessedReqSeqNbr;\n\t\t\t// The result from last processed incoming request.\n\t\t\tReconfigurationResponseParameter::Result lastProcessedReqResult;\n\t\t\t// The current stream request operation.\n\t\t\tstd::optional<CurrentRequest> currentRequest;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/TransmissionControlBlock.hpp",
    "content": "#ifndef MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_HPP\n#define MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/SCTP/association/HeartbeatHandler.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/association/PacketSender.hpp\"\n#include \"RTC/SCTP/association/StreamResetHandler.hpp\"\n#include \"RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/rx/DataTracker.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyQueue.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionErrorCounter.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionQueue.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionTimeout.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <string_view>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * The Transmission Control Block (TCB) represents an SCTP connection with\n\t\t * a peer and holds all its state.\n\t\t *\n\t\t * @see https://datatracker.ietf.org/doc/html/rfc9260#section-14\n\t\t */\n\t\tclass TransmissionControlBlock : public TransmissionControlBlockContextInterface,\n\t\t                                 public RetransmissionQueue::Listener,\n\t\t                                 public BackoffTimerHandleInterface::Listener\n\t\t{\n\t\tpublic:\n\t\t\tTransmissionControlBlock(\n\t\t\t  AssociationListenerInterface& associationListener,\n\t\t\t  const SctpOptions& sctpOptions,\n\t\t\t  SharedInterface* shared,\n\t\t\t  SendQueueInterface& sendQueue,\n\t\t\t  PacketSender& packetSender,\n\t\t\t  uint32_t localVerificationTag,\n\t\t\t  uint32_t remoteVerificationTag,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  uint64_t tieTag,\n\t\t\t  const NegotiatedCapabilities& negotiatedCapabilities,\n\t\t\t  size_t maxPacketLength,\n\t\t\t  std::function<bool()> isAssociationEstablished);\n\n\t\t\t~TransmissionControlBlock() override;\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tbool IsAssociationEstablished() const override\n\t\t\t{\n\t\t\t\treturn this->isAssociationEstablished();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initiate Tag field we put in our INIT or INIT_ACK\n\t\t\t * Chunk. Packets sent by the remote peer must include this value in\n\t\t\t * their Verification Tag field.\n\t\t\t */\n\t\t\tuint32_t GetLocalVerificationTag() const\n\t\t\t{\n\t\t\t\treturn this->localVerificationTag;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initiate Tag field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk. Packets sent by us to the peer must include this value\n\t\t\t * in their Verification Tag field.\n\t\t\t */\n\t\t\tuint32_t GetRemoteVerificationTag() const\n\t\t\t{\n\t\t\t\treturn this->remoteVerificationTag;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initial TSN field we put in our INIT or INIT_ACK\n\t\t\t * Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tuint32_t GetLocalInitialTsn() const override\n\t\t\t{\n\t\t\t\treturn this->localInitialTsn;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Initial TSN field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tuint32_t GetRemoteInitialTsn() const override\n\t\t\t{\n\t\t\t\treturn this->remoteInitialTsn;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Advertised Receiver Window Credit field we put in our\n\t\t\t * INIT or INIT_ACK Chunk.\n\t\t\t */\n\t\t\tuint32_t GetRemoteAdvertisedReceiverWindowCredit() const\n\t\t\t{\n\t\t\t\treturn this->remoteAdvertisedReceiverWindowCredit;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Tie-Tag used as a nonce when connecting.\n\t\t\t */\n\t\t\tuint64_t GetTieTag() const\n\t\t\t{\n\t\t\t\treturn this->tieTag;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Negotiated association capabilities.\n\t\t\t */\n\t\t\tconst NegotiatedCapabilities& GetNegotiatedCapabilities() const\n\t\t\t{\n\t\t\t\treturn this->negotiatedCapabilities;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tvoid ObserveRttMs(uint64_t rttMs) override;\n\n\t\t\tsize_t GetCwnd() const\n\t\t\t{\n\t\t\t\treturn this->retransmissionQueue.GetCwnd();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tuint64_t GetCurrentRtoMs() const override\n\t\t\t{\n\t\t\t\treturn this->rto.GetRtoMs();\n\t\t\t}\n\n\t\t\tuint64_t GetCurrentSrttMs() const\n\t\t\t{\n\t\t\t\treturn this->rto.GetSrttMs();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tstd::unique_ptr<Packet> CreatePacket() const override;\n\n\t\t\tstd::unique_ptr<Packet> CreatePacketWithVerificationTag(uint32_t verificationTag) const;\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tbool SendPacket(Packet* packet) override;\n\n\t\t\tDataTracker& GetDataTracker()\n\t\t\t{\n\t\t\t\treturn this->dataTracker;\n\t\t\t}\n\n\t\t\tReassemblyQueue& GetReassemblyQueue()\n\t\t\t{\n\t\t\t\treturn this->reassemblyQueue;\n\t\t\t}\n\n\t\t\tRetransmissionQueue& GetRetransmissionQueue()\n\t\t\t{\n\t\t\t\treturn this->retransmissionQueue;\n\t\t\t}\n\n\t\t\tStreamResetHandler& GetStreamResetHandler()\n\t\t\t{\n\t\t\t\treturn this->streamResetHandler;\n\t\t\t}\n\n\t\t\tHeartbeatHandler& GetHeartbeatHandler()\n\t\t\t{\n\t\t\t\treturn this->heartbeatHandler;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Will be set while the Association is in COOKIE_ECHOED state. In this\n\t\t\t * state, there can only be a single Packet outstanding, and it must\n\t\t\t * contain the COOKIE_ECHO Chunk as the first Chunk in that Packet, until\n\t\t\t * the COOKIE_ACK has been received, which will make the socket call\n\t\t\t * `ClearRemoteStateCookie()`.\n\t\t\t */\n\t\t\tvoid SetRemoteStateCookie(std::vector<uint8_t> remoteStateCookie);\n\n\t\t\t/**\n\t\t\t * Called when the COOKIE_ACK Chunk has been received, to allow further\n\t\t\t * Packets to be sent.\n\t\t\t */\n\t\t\tvoid ClearRemoteStateCookie();\n\n\t\t\tbool HasRemoteStateCookie() const\n\t\t\t{\n\t\t\t\treturn this->remoteStateCookie.has_value();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Sends a SACK Chunk, if there is a need to.\n\t\t\t */\n\t\t\tvoid MaySendSackChunk();\n\n\t\t\t/**\n\t\t\t * May add a FORWARD-TSN or I-FORWARD-TSN Chunk to the given Packet if it\n\t\t\t * is needed and allowed (rate-limited).\n\t\t\t */\n\t\t\tvoid MayAddForwardTsnChunk(Packet* packet, uint64_t nowMs);\n\n\t\t\tvoid MaySendFastRetransmit();\n\n\t\t\t/**\n\t\t\t * Create and fill Packets with control and DATA/I-DATA Chunks, and sends\n\t\t\t * them as much as can be allowed by the congestion control algorithm.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - If `this->remoteStateCookie` is present, then only one Packet will be\n\t\t\t *   sent, with this Chunk as the first Chunk.\n\t\t\t * - Cannot pass `addCookieAckChunk=true` if `this->remoteStateCookie` is\n\t\t\t *   present (will throw).\n\t\t\t */\n\t\t\tvoid SendBufferedPackets(uint64_t nowMs, bool addCookieAckChunk = false);\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tbool IncrementTxErrorCounter(std::string_view reason) override\n\t\t\t{\n\t\t\t\treturn this->txErrorCounter.Increment(reason);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tvoid ClearTxErrorCounter() override\n\t\t\t{\n\t\t\t\treturn this->txErrorCounter.Clear();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks\n\t\t\t * - Implements TransmissionControlBlockContextInterface.\n\t\t\t */\n\t\t\tbool HasTooManyTxErrors() const override\n\t\t\t{\n\t\t\t\treturn this->txErrorCounter.IsExhausted();\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid OnT3RtxTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\tvoid OnDelayedAckTimer(uint64_t& baseTimeoutMs, bool& stop);\n\n\t\t\t/* Pure virtual methods inherited from RetransmissionQueue::Listener. */\n\t\tpublic:\n\t\t\tvoid OnRetransmissionQueueNewRttMs(uint64_t newRttMs) override;\n\t\t\tvoid OnRetransmissionQueueClearRetransmissionCounter() override;\n\t\t\t;\n\n\t\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\t\tpublic:\n\t\t\tvoid OnBackoffTimer(\n\t\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override;\n\n\t\tprivate:\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t\tconst SctpOptions sctpOptions;\n\t\t\tSharedInterface* shared;\n\t\t\tPacketSender& packetSender;\n\t\t\tuint32_t localVerificationTag;\n\t\t\tuint32_t remoteVerificationTag;\n\t\t\tuint32_t localInitialTsn;\n\t\t\tuint32_t remoteInitialTsn;\n\t\t\tuint32_t remoteAdvertisedReceiverWindowCredit;\n\t\t\t// Nonce, used to detect reconnections.\n\t\t\tuint64_t tieTag;\n\t\t\tNegotiatedCapabilities negotiatedCapabilities;\n\t\t\t// Max SCTP Packet length.\n\t\t\tconst size_t maxPacketLength;\n\t\t\tstd::function<bool()> isAssociationEstablished;\n\t\t\t// The data retransmission timer.\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> t3RtxTimer;\n\t\t\t// Delayed ack timer, which triggers when acks should be sent (when\n\t\t\t// delayed).\n\t\t\tconst std::unique_ptr<BackoffTimerHandleInterface> delayedAckTimer;\n\t\t\tRetransmissionTimeout rto;\n\t\t\tRetransmissionErrorCounter txErrorCounter;\n\t\t\tDataTracker dataTracker;\n\t\t\tReassemblyQueue reassemblyQueue;\n\t\t\tRetransmissionQueue retransmissionQueue;\n\t\t\tStreamResetHandler streamResetHandler;\n\t\t\tHeartbeatHandler heartbeatHandler;\n\t\t\t// Rate limiting of FORWARD_TSN. Next can be sent at or after this\n\t\t\t// timestamp.\n\t\t\tuint64_t limitForwardTsnUntilMs{ 0 };\n\t\t\t// Only valid when state is State::COOKIE_ECHOED. In this state, the\n\t\t\t// Association must wait for COOKIE_ACK to continue sending any packets (not\n\t\t\t// including a COOKIE_ECHO). So if this state cookie is present, the\n\t\t\t// `SendBufferedChunks()` method will always only send one Packet, with\n\t\t\t// a CookieEchoChunk containing this cookie as the first Chunk in the Packet.\n\t\t\tstd::optional<std::vector<uint8_t>> remoteStateCookie;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp",
    "content": "#ifndef MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_CONTEXT_INTERFACE_HPP\n#define MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_CONTEXT_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tclass TransmissionControlBlockContextInterface\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~TransmissionControlBlockContextInterface() = default;\n\n\t\t\t/**\n\t\t\t * Indicates if the SCTP Association has been established.\n\t\t\t */\n\t\t\tvirtual bool IsAssociationEstablished() const = 0;\n\n\t\t\t/**\n\t\t\t * The value of the Initiate Tag field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk.\n\t\t\t */\n\t\t\tvirtual uint32_t GetLocalInitialTsn() const = 0;\n\n\t\t\t/**\n\t\t\t * The value of the Initial TSN field the peer put in its INIT or\n\t\t\t * INIT_ACK Chunk.\n\t\t\t */\n\t\t\tvirtual uint32_t GetRemoteInitialTsn() const = 0;\n\n\t\t\t/**\n\t\t\t * To be called when a RTT (ms) has been measured, to update the RTO\n\t\t\t * value.\n\t\t\t */\n\t\t\tvirtual void ObserveRttMs(uint64_t rttMs) = 0;\n\n\t\t\t/**\n\t\t\t * Returns the Retransmission Timeout (RTO) value.\n\t\t\t */\n\t\t\tvirtual uint64_t GetCurrentRtoMs() const = 0;\n\n\t\t\t/**\n\t\t\t * Increments the transmission error counter, given a human readable\n\t\t\t * reason. Returns `true` if the maximum error count has been reached,\n\t\t\t * `false` will be returned.\n\t\t\t */\n\t\t\tvirtual bool IncrementTxErrorCounter(std::string_view reason) = 0;\n\n\t\t\t/**\n\t\t\t * Clears the transmission error counter.\n\t\t\t */\n\t\t\tvirtual void ClearTxErrorCounter() = 0;\n\n\t\t\t/**\n\t\t\t * Returns true if there have been too many retransmission errors.\n\t\t\t */\n\t\t\tvirtual bool HasTooManyTxErrors() const = 0;\n\n\t\t\tvirtual std::unique_ptr<Packet> CreatePacket() const = 0;\n\n\t\t\t/**\n\t\t\t * Sends a Packet and returns a boolean indicating whether the Packet was\n\t\t\t * sent or not.\n\t\t\t */\n\t\t\tvirtual bool SendPacket(Packet* packet) = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/Chunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_CHUNK_HPP\n#define MS_RTC_SCTP_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/TLV.hpp\"\n#include <string>\n#include <unordered_map>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Chunk.\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Chunk Type   |  Chunk Flags  |         Chunk Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                          Chunk Value                          /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits).\n\t\t * - Chunk Flags (8 bits).\n\t\t * - Chunk Length (16 bits): Total length of the Chunk\n\t\t *   excluding padding bytes. Minimum value is 4 (if Chunk Value is 0\n\t\t *   bytes). Maximum value is 65535, which means 1 byte of padding.\n\t\t * - Chunk Value (variable length).\n\t\t * - Padding: Bytes of padding to make the Chunk total length be\n\t\t *   multiple of 4 bytes.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass Chunk : public TLV\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tusing ParametersIterator  = typename std::vector<Parameter*>::const_iterator;\n\t\t\tusing ErrorCausesIterator = typename std::vector<ErrorCause*>::const_iterator;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Chunk Type.\n\t\t\t */\n\t\t\tenum class ChunkType : uint8_t\n\t\t\t{\n\t\t\t\tDATA              = 0x00,\n\t\t\t\tINIT              = 0x01,\n\t\t\t\tINIT_ACK          = 0x02,\n\t\t\t\tSACK              = 0x03,\n\t\t\t\tHEARTBEAT_REQUEST = 0x04,\n\t\t\t\tHEARTBEAT_ACK     = 0x05,\n\t\t\t\tABORT             = 0x06,\n\t\t\t\tSHUTDOWN          = 0x07,\n\t\t\t\tSHUTDOWN_ACK      = 0x08,\n\t\t\t\tOPERATION_ERROR   = 0x09, // NOTE: Cannot use ERROR (MSVC complains).\n\t\t\t\tCOOKIE_ECHO       = 0x0A,\n\t\t\t\tCOOKIE_ACK        = 0x0B,\n\t\t\t\tECNE              = 0x0C, // NOTE: Not implemented.\n\t\t\t\tCWR               = 0x0D, // NOTE: Not implemented.\n\t\t\t\tSHUTDOWN_COMPLETE = 0x0E,\n\t\t\t\tFORWARD_TSN       = 0xC0, // Type: 192, RFC 3758\n\t\t\t\tRE_CONFIG         = 0x82, // Type 130, RFC 6525\n\t\t\t\tI_DATA            = 0x40, // Type: 64, RFC 8260\n\t\t\t\tI_FORWARD_TSN     = 0xC2, // Type: 194, RFC 8260\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Action that is taken if the processing endpoint does not recognize the\n\t\t\t * Chunk Type.\n\t\t\t */\n\t\t\tenum class ActionForUnknownChunkType : uint8_t\n\t\t\t{\n\t\t\t\tSTOP            = 0b00,\n\t\t\t\tSTOP_AND_REPORT = 0b01,\n\t\t\t\tSKIP            = 0b10,\n\t\t\t\tSKIP_AND_REPORT = 0b11\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Struct of an SCTP Chunk Header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct ChunkHeader\n\t\t\t{\n\t\t\t\tChunkType type;\n\t\t\t\tuint8_t flags;\n\t\t\t\t/**\n\t\t\t\t * The value of the Chunk Length field, which represents the total\n\t\t\t\t * length of the Chunk in bytes, including the Chunk Type, Chunk Flags,\n\t\t\t\t * Chunk Length and Chunk Value fields. So if the Chunk Value field is\n\t\t\t\t * zero-length, the Length field must be 4. The Chunk Length field does\n\t\t\t\t * not count any padding.\n\t\t\t\t */\n\t\t\t\tuint16_t length;\n\t\t\t};\n\n#ifdef MS_TEST\n\t\tpublic:\n#else\n\t\tprivate:\n#endif\n\t\t\t/**\n\t\t\t * Access to individual bit in the Chunk Flags field. bit0 corresponds\n\t\t\t * to the least significant bit.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 1 byte.\n\t\t\t */\n\t\t\tstruct ChunkFlags\n\t\t\t{\n#if defined(MS_LITTLE_ENDIAN)\n\t\t\t\tuint8_t bit0 : 1;\n\t\t\t\tuint8_t bit1 : 1;\n\t\t\t\tuint8_t bit2 : 1;\n\t\t\t\tuint8_t bit3 : 1;\n\t\t\t\tuint8_t bit4 : 1;\n\t\t\t\tuint8_t bit5 : 1;\n\t\t\t\tuint8_t bit6 : 1;\n\t\t\t\tuint8_t bit7 : 1;\n#elif defined(MS_BIG_ENDIAN)\n\t\t\t\tuint8_t bit7 : 1;\n\t\t\t\tuint8_t bit6 : 1;\n\t\t\t\tuint8_t bit5 : 1;\n\t\t\t\tuint8_t bit4 : 1;\n\t\t\t\tuint8_t bit3 : 1;\n\t\t\t\tuint8_t bit2 : 1;\n\t\t\t\tuint8_t bit1 : 1;\n\t\t\t\tuint8_t bit0 : 1;\n#endif\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t ChunkHeaderLength{ 4 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Whether given buffer could be a a valid Chunk.\n\t\t\t *\n\t\t\t * @param buffer\n\t\t\t * @param bufferLength - Can be greater than real Chunk length.\n\t\t\t * @param chunkType - If given buffer is a valid Chunk then `chunkType`\n\t\t\t *   is rewritten to parsed ChunkType.\n\t\t\t * @param chunkLength - If given buffer is a valid Chunk then\n\t\t\t *   `chunkLength` is rewritten to the value of the Chunk Length field.\n\t\t\t * @param padding - If given buffer is a valid Chunk then `padding` is\n\t\t\t *   rewritten to the number of padding bytes in the Chunk (only the\n\t\t\t *   necessary ones to make total length multiple of 4).\n\t\t\t */\n\t\t\tstatic bool IsChunk(\n\t\t\t  const uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  ChunkType& chunkType,\n\t\t\t  uint16_t& chunkLength,\n\t\t\t  uint8_t& padding);\n\n\t\t\tstatic const std::string& ChunkTypeToString(ChunkType chunkType);\n\n\t\tprivate:\n\t\t\tstatic const std::unordered_map<ChunkType, std::string> ChunkType2String;\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Constructor is protected because we only want to create Chunk\n\t\t\t * instances via Parse() and Factory() in subclasses.\n\t\t\t */\n\t\t\tChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~Chunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const override = 0;\n\n\t\t\tvoid Serialize(uint8_t* buffer, size_t bufferLength) final;\n\n\t\t\t/**\n\t\t\t * Can be overridden by each subclass.\n\t\t\t */\n\t\t\tChunk* Clone(uint8_t* buffer, size_t bufferLength) const override = 0;\n\n\t\t\tvirtual ChunkType GetType() const final\n\t\t\t{\n\t\t\t\treturn GetHeaderPointer()->type;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * False by default. UnknownChunk class overrides this method to return\n\t\t\t * true instead.\n\t\t\t */\n\t\t\tvirtual bool HasUnknownType() const\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvirtual ActionForUnknownChunkType GetActionForUnknownChunkType() const final\n\t\t\t{\n\t\t\t\treturn static_cast<ActionForUnknownChunkType>(GetBuffer()[0] >> 6);\n\t\t\t}\n\n\t\t\tvirtual uint8_t GetFlags() const final\n\t\t\t{\n\t\t\t\treturn GetHeaderPointer()->flags;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether this type of Chunk can have Parameters. Subclasses must\n\t\t\t * override this method.\n\t\t\t */\n\t\t\tvirtual bool CanHaveParameters() const = 0;\n\n\t\t\tvirtual bool HasParameters() const final\n\t\t\t{\n\t\t\t\treturn this->parameters.size() > 0;\n\t\t\t}\n\n\t\t\tvirtual size_t GetParametersCount() const final\n\t\t\t{\n\t\t\t\treturn this->parameters.size();\n\t\t\t}\n\n\t\t\tvirtual ParametersIterator ParametersBegin() const final\n\t\t\t{\n\t\t\t\treturn this->parameters.begin();\n\t\t\t}\n\n\t\t\tvirtual ParametersIterator ParametersEnd() const final\n\t\t\t{\n\t\t\t\treturn this->parameters.end();\n\t\t\t}\n\n\t\t\tvirtual const Parameter* GetParameterAt(size_t idx) const final\n\t\t\t{\n\t\t\t\tif (idx >= this->parameters.size())\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn this->parameters[idx];\n\t\t\t}\n\n\t\t\ttemplate<typename T>\n\t\t\tconst T* GetFirstParameterOfType() const\n\t\t\t{\n\t\t\t\tfor (const auto* parameter : this->parameters)\n\t\t\t\t{\n\t\t\t\t\tif (typeid(*parameter) == typeid(T))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn static_cast<const T*>(parameter);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Clone given Parameter into Chunk's buffer.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Once this method is called, the caller may want to free the original\n\t\t\t *   given Parameter (otherwise it will leak since the Chunk manages a clone\n\t\t\t *   of it).\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Parameters.\n\t\t\t * - MediaSoupError - If `BuildParameterInPlace()` or\n\t\t\t *   `BuildErrorCauseInPlace()` was called before and the caller didn't\n\t\t\t *   invoke `Consolidate()` on the returned Parameter or Error Cause yet.\n\t\t\t */\n\t\t\tvirtual void AddParameter(const Parameter* parameter) final;\n\n\t\t\t/**\n\t\t\t * Build a Parameter within the Chunk's buffer and append it to the list\n\t\t\t * of Parameters. The caller can perform modifications in that Parameter\n\t\t\t * and those will affect the Chunk body where the Parameter is serialized.\n\t\t\t * The desired Parameter class type is given via template argument.\n\t\t\t *\n\t\t\t * @returns Pointer of the created Parameter specific class.\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Parameters.\n\t\t\t * - MediaSoupError - If `BuildParameterInPlace()` or\n\t\t\t *   `BuildErrorCauseInPlace()` was called before and the caller didn't\n\t\t\t *   invoke `Consolidate()` on the returned Parameter or Error Cause yet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The caller MUST invoke `Consolidate()` once the Parameter is\n\t\t\t *   completed.\n\t\t\t * - The caller MUST NOT free the obtained Parameter pointer since it's\n\t\t\t *   now part of the Chunk.\n\t\t\t * - The caller MUST free the obtained Parameter only in case the\n\t\t\t *   `Consolidate()` method on the Parameter throws.\n\t\t\t * - Method implemented in header file due to C++ template usage.\n\t\t\t *\n\t\t\t * @example\n\t\t\t * ```c++\n\t\t\t * auto* ipv4Parameter =\n\t\t\t *   chunk->BuildParameterInPlace<IPv4AddressParameter>();\n\t\t\t * ```\n\t\t\t */\n\t\t\ttemplate<typename T>\n\t\t\tT* BuildParameterInPlace()\n\t\t\t{\n\t\t\t\tAssertCanHaveParameters();\n\t\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\t\t// The new Parameter will be added after other Parameters in the Chunk,\n\t\t\t\t// this is, at the end of the Chunk, whose length we know it's padded to\n\t\t\t\t// 4 bytes, and each Parameter total length is also multiple of 4 bytes.\n\t\t\t\tauto* ptr = const_cast<uint8_t*>(GetBuffer()) + GetLength();\n\t\t\t\t// The remaining length in the buffer is the potential buffer length\n\t\t\t\t// of the Parameter.\n\t\t\t\tsize_t parameterMaxBufferLength = GetBufferLength() - (ptr - GetBuffer());\n\n\t\t\t\tauto* parameter = T::Factory(ptr, parameterMaxBufferLength);\n\n\t\t\t\t// NOTE: Do not fix/update the Parameter buffer length since the caller\n\t\t\t\t// probably wants to modify the Parameter.\n\n\t\t\t\tHandleInPlaceParameter(parameter);\n\n\t\t\t\treturn parameter;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether this type of Chunk can have Error Causes. Subclasses must\n\t\t\t * override this method.\n\t\t\t */\n\t\t\tvirtual bool CanHaveErrorCauses() const = 0;\n\n\t\t\tvirtual bool HasErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn this->errorCauses.size() > 0;\n\t\t\t}\n\n\t\t\tvirtual size_t GetErrorCausesCount() const final\n\t\t\t{\n\t\t\t\treturn this->errorCauses.size();\n\t\t\t}\n\n\t\t\tvirtual ErrorCausesIterator ErrorCausesBegin() const final\n\t\t\t{\n\t\t\t\treturn this->errorCauses.begin();\n\t\t\t}\n\n\t\t\tvirtual ErrorCausesIterator ErrorCausesEnd() const final\n\t\t\t{\n\t\t\t\treturn this->errorCauses.end();\n\t\t\t}\n\n\t\t\tvirtual const ErrorCause* GetErrorCauseAt(size_t idx) const final\n\t\t\t{\n\t\t\t\tif (idx >= this->errorCauses.size())\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn this->errorCauses[idx];\n\t\t\t}\n\n\t\t\ttemplate<typename T>\n\t\t\tconst T* GetFirstErrorCauseOfCode() const\n\t\t\t{\n\t\t\t\tfor (const auto* errorCause : this->errorCauses)\n\t\t\t\t{\n\t\t\t\t\tif (typeid(*errorCause) == typeid(T))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn static_cast<const T*>(errorCause);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Clone given Error Cause into Chunk's buffer.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Once this method is called, the caller may want to free the original\n\t\t\t *   given Error Cause (otherwise it will leak since the Chunk manages a\n\t\t\t *   clone of it).\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Error Causes.\n\t\t\t * - MediaSoupError - If `BuildParameterInPlace()` or\n\t\t\t *   `BuildErrorCauseInPlace()` was called before and the caller didn't\n\t\t\t *   invoke `Consolidate()` on the returned Parameter or Error Cause yet.\n\t\t\t */\n\t\t\tvirtual void AddErrorCause(const ErrorCause* errorCause) final;\n\n\t\t\t/**\n\t\t\t * Build a Error Cause within the Chunk's buffer and append it to the\n\t\t\t * list of Error Causes. The caller can perform modifications in that\n\t\t\t * Error Cause and those will affect the Chunk body where the Error Cause\n\t\t\t * is serialzed. The desired Error Cause class type is given via template\n\t\t\t * argument.\n\t\t\t *\n\t\t\t * @returns Pointer of the created Error Cause specific class.\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Error Causes.\n\t\t\t * - MediaSoupError - If `BuildParameterInPlace()` or\n\t\t\t *   `BuildErrorCauseInPlace()` was called before and the caller didn't\n\t\t\t *   invoke `Consolidate()` on the returned Parameter or Error Cause yet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The caller MUST invoke `Consolidate()` once the Error Cause is\n\t\t\t *   completed.\n\t\t\t * - The caller MUST NOT free the obtained Error Cause pointer since it's\n\t\t\t *   now part of the Chunk.\n\t\t\t * - The caller MUST free the obtained Error Cause only in case the\n\t\t\t *   `Consolidate()` method on the Error Cause throws.\n\t\t\t * - Method implemented in header file due to C++ template usage.\n\t\t\t *\n\t\t\t * @example\n\t\t\t * ```c++\n\t\t\t * auto* noUserDataErrorCause =\n\t\t\t *   chunk->BuildErrorCauseInPlace<NoUserDataErrorCause>();\n\t\t\t * ```\n\t\t\t */\n\t\t\ttemplate<typename T>\n\t\t\tT* BuildErrorCauseInPlace()\n\t\t\t{\n\t\t\t\tAssertCanHaveErrorCauses();\n\t\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\t\t// The new Error Cause will be added after other Error Causes in the\n\t\t\t\t// Chunk, this is, at the end of the Chunk, whose length we know it's\n\t\t\t\t// padded to 4 bytes, and each Error Cause total length is also\n\t\t\t\t// multiple of 4 bytes.\n\t\t\t\tauto* ptr = const_cast<uint8_t*>(GetBuffer()) + GetLength();\n\t\t\t\t// The remaining length in the buffer is the potential buffer length\n\t\t\t\t// of the Error Cause.\n\t\t\t\tsize_t errorCauseMaxBufferLength = GetBufferLength() - (ptr - GetBuffer());\n\n\t\t\t\tauto* errorCause = T::Factory(ptr, errorCauseMaxBufferLength);\n\n\t\t\t\t// NOTE: Do not fix/update the Error Cause buffer length since the\n\t\t\t\t// caller probably wants to modify the Error Cause.\n\n\t\t\t\tHandleInPlaceErrorCause(errorCause);\n\n\t\t\t\treturn errorCause;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether `BuildParameterInPlace()` or `BuildErrorCauseInPlace()` was\n\t\t\t * called before and the caller didn't invoke `Consolidate()` on the\n\t\t\t * returned Parameter or Error Cause yet.\n\t\t\t */\n\t\t\tvirtual bool NeedsConsolidation() const final\n\t\t\t{\n\t\t\t\treturn this->needsConsolidation;\n\t\t\t}\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvoid DumpCommon(int indentation) const final;\n\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvirtual void DumpParameters(int indentation) const final;\n\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvirtual void DumpErrorCauses(int indentation) const final;\n\n\t\t\tvirtual void SoftSerialize(const uint8_t* buffer) final;\n\n\t\t\t/**\n\t\t\t * Can be overridden by each subclass.\n\t\t\t */\n\t\t\tvirtual Chunk* SoftClone(const uint8_t* buffer) const = 0;\n\n\t\t\tvirtual void SoftCloneInto(Chunk* chunk) const final;\n\n\t\t\tvirtual void InitializeHeader(ChunkType chunkType, uint8_t flags, uint16_t lengthFieldValue) final;\n\n\t\t\tvirtual bool GetBit0() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit0;\n\t\t\t}\n\n\t\t\tvirtual void SetBit0(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit0 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit1() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit1;\n\t\t\t}\n\n\t\t\tvirtual void SetBit1(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit1 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit2() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit2;\n\t\t\t}\n\n\t\t\tvirtual void SetBit2(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit2 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit3() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit3;\n\t\t\t}\n\n\t\t\tvirtual void SetBit3(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit3 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit4() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit4;\n\t\t\t}\n\n\t\t\tvirtual void SetBit4(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit4 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit5() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit5;\n\t\t\t}\n\n\t\t\tvirtual void SetBit5(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit5 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit6() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit6;\n\t\t\t}\n\n\t\t\tvirtual void SetBit6(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit6 = flag;\n\t\t\t}\n\n\t\t\tvirtual bool GetBit7() const final\n\t\t\t{\n\t\t\t\treturn GetFlagsPointer()->bit7;\n\t\t\t}\n\n\t\t\tvirtual void SetBit7(bool flag) final\n\t\t\t{\n\t\t\t\tGetFlagsPointer()->bit7 = flag;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Chunk subclasses with header bigger than default one (4 bytes) must\n\t\t\t * override this method and return their header length (excluding\n\t\t\t * variable-length field considered \"value\", Optional/Variable-Length\n\t\t\t * Parameters and Error Causes).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const override\n\t\t\t{\n\t\t\t\treturn Chunk::ChunkHeaderLength;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * To be called by each subclass of Chunk if Parameters parsing is\n\t\t\t * needed. It creates Parameter subclasses and adds them to the Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method assumes that the Chunk basic parsing has been made\n\t\t\t *   already so current length of the Chunk is the fixed length of the\n\t\t\t *   specific Chunk class.\n\t\t\t *\n\t\t\t * @return True if no error happened while parsing Parameters.\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Chunk Parameters.\n\t\t\t */\n\t\t\tvirtual bool ParseParameters() final;\n\n\t\t\t/**\n\t\t\t * To be called by each subclass of Chunk if Error Causes parsing is\n\t\t\t * needed. It creates ErrorCause subclasses and adds them to the Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method assumes that the Chunk basic parsing has been made\n\t\t\t *   already so current length of the Chunk is the fixed length of the\n\t\t\t *   specific Chunk class.\n\t\t\t *\n\t\t\t * @return True if no error happened while parsing Error Causes.\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If the Chunk subclass cannot have Chunk Parameters.\n\t\t\t */\n\t\t\tvirtual bool ParseErrorCauses() final;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * NOTE: Return ChunkHeader* instead of const ChunkHeader* since we may\n\t\t\t * want to modify its fields.\n\t\t\t */\n\t\t\tvirtual ChunkHeader* GetHeaderPointer() const final\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<ChunkHeader*>(const_cast<uint8_t*>(GetBuffer()));\n\t\t\t}\n\n\t\t\tvirtual void SetType(ChunkType chunkType) final\n\t\t\t{\n\t\t\t\tGetHeaderPointer()->type = chunkType;\n\t\t\t}\n\n\t\t\tvirtual void SetFlags(uint8_t flags) final\n\t\t\t{\n\t\t\t\tGetHeaderPointer()->flags = flags;\n\t\t\t}\n\n\t\t\tvirtual ChunkFlags* GetFlagsPointer() const final\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<ChunkFlags*>(const_cast<uint8_t*>(GetBuffer()) + 1);\n\t\t\t}\n\n\t\t\tvirtual void HandleInPlaceParameter(Parameter* parameter) final;\n\n\t\t\tvirtual void HandleInPlaceErrorCause(ErrorCause* errorCause) final;\n\n\t\t\tvirtual void AssertCanHaveParameters() const final;\n\n\t\t\tvirtual void AssertCanHaveErrorCauses() const final;\n\n\t\t\tvirtual void AssertDoesNotNeedConsolidation() const final;\n\n\t\tprivate:\n\t\t\t// Parameters.\n\t\t\tstd::vector<Parameter*> parameters;\n\t\t\t// Error Causes.\n\t\t\tstd::vector<ErrorCause*> errorCauses;\n\t\t\t// Whether `BuildParameterInPlace()` or `BuildErrorCauseInPlace()` was\n\t\t\t// called and the caller didn't invoke `Consolidate()` on the returned\n\t\t\t// Parameter or Error Cause yet.\n\t\t\tbool needsConsolidation{ false };\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/ErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/TLV.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Error Cause.\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Cause Code           |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                  Cause-Specific Information                   /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Cause Code (16 bits).\n\t\t * - Cause Length (16 bits): Set to the size of the Error Cause in bytes,\n\t\t *   including the Cause Code, Cause Length, and Cause-Specific Information\n\t\t *   fields (padding excluded).\n\t\t * - Cause-Specific Information (variable length): This field carries the\n\t\t *   details of the error condition.\n\t\t * - Padding: Bytes of padding to make the Error Cause total length be\n\t\t *   multiple of 4 bytes.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass ErrorCause : public TLV\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Error Cause Code.\n\t\t\t * NOTE: This field MUST be 2 bytes long.\n\t\t\t */\n\t\t\t// NOLINTNEXTLINE(performance-enum-size)\n\t\t\tenum class ErrorCauseCode : uint16_t\n\t\t\t{\n\t\t\t\tINVALID_STREAM_IDENTIFIER                    = 0x0001,\n\t\t\t\tMISSING_MANDATORY_PARAMETER                  = 0x0002,\n\t\t\t\tSTALE_COOKIE                                 = 0x0003,\n\t\t\t\tOUT_OF_RESOURCE                              = 0x0004,\n\t\t\t\tUNRESOLVABLE_ADDRESS                         = 0x0005,\n\t\t\t\tUNRECOGNIZED_CHUNK_TYPE                      = 0x0006,\n\t\t\t\tINVALID_MANDATORY_PARAMETER                  = 0x0007,\n\t\t\t\tUNRECOGNIZED_PARAMETERS                      = 0x0008,\n\t\t\t\tNO_USER_DATA                                 = 0x0009,\n\t\t\t\tCOOKIE_RECEIVED_WHILE_SHUTTING_DOWN          = 0x000A,\n\t\t\t\tRESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES = 0x000B,\n\t\t\t\tUSER_INITIATED_ABORT                         = 0x000C,\n\t\t\t\tPROTOCOL_VIOLATION                           = 0x000D,\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Struct of an SCTP Error Cause Header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct ErrorCauseHeader\n\t\t\t{\n\t\t\t\tErrorCauseCode code;\n\t\t\t\t/**\n\t\t\t\t * The value of the Error Cause Length field, which represents the\n\t\t\t\t * total length of the Error Cause in bytes, including the Cause Code,\n\t\t\t\t * Cause Length and Cause-Specific Information fields. So if the\n\t\t\t\t * Cause-Specific Information field is zero-length, the Length field\n\t\t\t\t * must be 4. The Cause Length field does not count any padding.\n\t\t\t\t */\n\t\t\t\tuint16_t length;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t ErrorCauseHeaderLength{ 4 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Whether given buffer could be a a valid Error Cause.\n\t\t\t *\n\t\t\t * @param buffer\n\t\t\t * @param bufferLength - Can be greater than real Error Cause length.\n\t\t\t * @param causeCode - If given buffer is a valid Error Cause then\n\t\t\t *   `causeCode` is rewritten to parsed ErrorCauseCode.\n\t\t\t * @param causeLength - If given buffer is a valid Error Cause then\n\t\t\t *   `causeLength` is rewritten to the value of the Cause Length field.\n\t\t\t * @param padding - If given buffer is a valid Error Cause then `padding`\n\t\t\t *   is rewritten to the number of padding bytes in the Error Cause (only\n\t\t\t *   the necessary ones to make total length multiple of 4).\n\t\t\t */\n\t\t\tstatic bool IsErrorCause(\n\t\t\t  const uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  ErrorCauseCode& causeCode,\n\t\t\t  uint16_t& causeLength,\n\t\t\t  uint8_t& padding);\n\n\t\t\tstatic const std::string& ErrorCauseCodeToString(ErrorCauseCode causeCode);\n\n\t\tprivate:\n\t\t\tstatic const std::unordered_map<ErrorCauseCode, std::string> ErrorCauseCode2String;\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Constructor is protected because we only want to create ErrorCause\n\t\t\t * instances via Parse() and Factory() in subclasses.\n\t\t\t */\n\t\t\tErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const override = 0;\n\n\t\t\tErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const override = 0;\n\n\t\t\tvirtual ErrorCauseCode GetCode() const final\n\t\t\t{\n\t\t\t\treturn static_cast<ErrorCauseCode>(ntohs(static_cast<uint16_t>(GetHeaderPointer()->code)));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * False by default. UnknownErrorCause class overrides this method to\n\t\t\t * return true instead.\n\t\t\t */\n\t\t\tvirtual bool HasUnknownCode() const\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvirtual const std::string ToString() const final\n\t\t\t{\n\t\t\t\t// Get the custom content from the subclass.\n\t\t\t\tconst auto contentToString = ContentToString();\n\n\t\t\t\tif (contentToString.size() > 0)\n\t\t\t\t{\n\t\t\t\t\treturn ErrorCause::ErrorCauseCodeToString(GetCode()) + \" (\" +\n\t\t\t\t\t       std::to_string(static_cast<uint16_t>(GetCode())) + \") \" + contentToString;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn ErrorCause::ErrorCauseCodeToString(GetCode()) + \" (\" +\n\t\t\t\t\t       std::to_string(static_cast<uint16_t>(GetCode())) + \")\";\n\t\t\t\t}\n\t\t\t}\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvoid DumpCommon(int indentation) const final;\n\n\t\t\tvirtual void SoftSerialize(const uint8_t* buffer) final;\n\n\t\t\tvirtual ErrorCause* SoftClone(const uint8_t* buffer) const = 0;\n\n\t\t\tvirtual void SoftCloneInto(ErrorCause* errorCause) const final;\n\n\t\t\tvirtual void InitializeHeader(ErrorCauseCode errorCauseCode, uint16_t lengthFieldValue) final;\n\n\t\t\t/**\n\t\t\t * Error Cause subclasses with header bigger than default one (4 bytes)\n\t\t\t * must override this method and return their header length (excluding\n\t\t\t * variable-length field considered \"value\").\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const override\n\t\t\t{\n\t\t\t\treturn ErrorCause::ErrorCauseHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * NOTE: Return ErrorCauseHeader* instead of const ErrorCauseHeader*\n\t\t\t * since we may want to modify its fields.\n\t\t\t */\n\t\t\tvirtual ErrorCauseHeader* GetHeaderPointer() const final\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<ErrorCauseHeader*>(const_cast<uint8_t*>(GetBuffer()));\n\t\t\t}\n\n\t\t\tvirtual void SetCode(ErrorCauseCode causeCode) final\n\t\t\t{\n\t\t\t\tGetHeaderPointer()->code =\n\t\t\t\t  static_cast<ErrorCauseCode>(htons(static_cast<uint16_t>(causeCode)));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Subclasses can override this method.\n\t\t\t */\n\t\t\tvirtual const std::string ContentToString() const\n\t\t\t{\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/Packet.hpp",
    "content": "#ifndef MS_RTC_SCTP_PACKET_HPP\n#define MS_RTC_SCTP_PACKET_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/Serializable.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Packet.\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                         Common Header                         |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                           Chunk #1                            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                              ...                              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                           Chunk #n                            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * It's mandatory that the Packet total length is multiple of 4 bytes.\n\t\t */\n\n\t\t/**\n\t\t * SCTP Common Header.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |      Source Port Number       |    Destination Port Number    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                       Verification Tag                        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                           Checksum                            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Source port (16 bits).\n\t\t * - Destination port (16 bits).\n\t\t * - Verification Tag (32 bits).\n\t\t * - Checksum (32 bits).\n\t\t */\n\n\t\tclass Packet : public Serializable\n\t\t{\n\t\tpublic:\n\t\t\tusing ChunksIterator = typename std::vector<Chunk*>::const_iterator;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Struct of an SCTP Packet Common Header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 4 bytes.\n\t\t\t */\n\t\t\tstruct CommonHeader\n\t\t\t{\n\t\t\t\tuint16_t sourcePort;\n\t\t\t\tuint16_t destinationPort;\n\t\t\t\tuint32_t verificationTag;\n\t\t\t\tuint32_t checksum;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t CommonHeaderLength{ 12 };\n\n\t\t\t/**\n\t\t\t * Whether given buffer could be a valid SCTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the Packet.\n\t\t\t * - This check is very lazy. It should NEVER be done before checking if\n\t\t\t *   given buffer is an RTP or RTCP packet.\n\t\t\t */\n\t\t\tstatic bool IsSctp(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Parse an SCTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the Packet.\n\t\t\t */\n\t\t\tstatic Packet* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create an SCTP Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` must be the exact length of the STUN Packet.\n\t\t\t * - If `transactionId` is not given then a random Transaction ID is\n\t\t\t *   generated.\n\t\t\t */\n\t\t\tstatic Packet* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Constructor is private because we only want to create Packet instances\n\t\t\t * via Parse() and Factory().\n\t\t\t */\n\t\t\tPacket(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~Packet() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tvoid Serialize(uint8_t* buffer, size_t bufferLength) final;\n\n\t\t\tPacket* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint16_t GetSourcePort() const\n\t\t\t{\n\t\t\t\treturn ntohs(GetHeaderPointer()->sourcePort);\n\t\t\t}\n\n\t\t\tvoid SetSourcePort(uint16_t sourcePort);\n\n\t\t\tuint16_t GetDestinationPort() const\n\t\t\t{\n\t\t\t\treturn ntohs(GetHeaderPointer()->destinationPort);\n\t\t\t}\n\n\t\t\tvoid SetDestinationPort(uint16_t destinationPort);\n\n\t\t\tuint32_t GetVerificationTag() const\n\t\t\t{\n\t\t\t\treturn ntohl(GetHeaderPointer()->verificationTag);\n\t\t\t}\n\n\t\t\tvoid SetVerificationTag(uint32_t verificationTag);\n\n\t\t\tuint32_t GetChecksum() const\n\t\t\t{\n\t\t\t\treturn ntohl(GetHeaderPointer()->checksum);\n\t\t\t}\n\n\t\t\tvoid SetChecksum(uint32_t checksum);\n\n\t\t\tbool HasChunks() const\n\t\t\t{\n\t\t\t\treturn this->chunks.size() > 0;\n\t\t\t}\n\n\t\t\tsize_t GetChunksCount() const\n\t\t\t{\n\t\t\t\treturn this->chunks.size();\n\t\t\t}\n\n\t\t\tChunksIterator ChunksBegin() const\n\t\t\t{\n\t\t\t\treturn this->chunks.begin();\n\t\t\t}\n\n\t\t\tChunksIterator ChunksEnd() const\n\t\t\t{\n\t\t\t\treturn this->chunks.end();\n\t\t\t}\n\n\t\t\tconst Chunk* GetChunkAt(size_t idx) const\n\t\t\t{\n\t\t\t\tif (idx >= this->chunks.size())\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn this->chunks[idx];\n\t\t\t}\n\n\t\t\ttemplate<typename T>\n\t\t\tconst T* GetFirstChunkOfType() const\n\t\t\t{\n\t\t\t\tfor (const auto* chunk : this->chunks)\n\t\t\t\t{\n\t\t\t\t\tif (typeid(*chunk) == typeid(T))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn static_cast<const T*>(chunk);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Clone given Chunk into Packet's buffer.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Once this method is called, the caller may want to free the original\n\t\t\t *   given Chunk (otherwise it will leak since the Packet manages a clone\n\t\t\t *   of it).\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If `BuildChunkInPlace()` was called before and the\n\t\t\t *   caller didn't invoke `Consolidate()` on the returned Chunk yet.\n\t\t\t */\n\t\t\tvoid AddChunk(const Chunk* chunk);\n\n\t\t\t/**\n\t\t\t * Build a Chunk within the Packet's buffer and append it to the list of\n\t\t\t * Chunks. The caller can perform modifications in that Chunk and those\n\t\t\t * will affect the Packet body where the Chunk is serialzed. The desired\n\t\t\t * Chunk class type is given via template argument.\n\t\t\t *\n\t\t\t * @returns Pointer of the created Chunk specific class.\n\t\t\t *\n\t\t\t * @throw\n\t\t\t * - MediaSoupError - If `BuildChunkInPlace()` was called before and the\n\t\t\t *   caller didn't invoke `Consolidate()` on the returned Chunk yet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The caller MUST invoke `Consolidate()` once the Chunk is completed.\n\t\t\t * - The caller MUST NOT call `BuildChunkInPlace()` while other Chunk is\n\t\t\t *   in progress.\n\t\t\t * - The caller MUST NOT free the obtained Chunk pointer since it's now\n\t\t\t *   part of the Packet.\n\t\t\t * - The caller MUST free the obtained Chunk only in case the\n\t\t\t *   `Consolidate()` method on the Chunk throws.\n\t\t\t * - Method implemented in header file due to C++ template usage.\n\t\t\t *\n\t\t\t * @example\n\t\t\t * ```c++\n\t\t\t * auto* initChunk = packet->BuildChunkInPlace<InitChunk>();\n\t\t\t * ```\n\t\t\t */\n\t\t\ttemplate<typename T>\n\t\t\tT* BuildChunkInPlace()\n\t\t\t{\n\t\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\t\t// The new Chunk will be added after other Chunks in the Packet, this is,\n\t\t\t\t// at the end of the Packet,  whose length we know it's padded to 4\n\t\t\t\t// bytes, and each Parameter total length is also multiple of 4 bytes.\n\t\t\t\tauto* ptr = const_cast<uint8_t*>(GetBuffer()) + GetLength();\n\t\t\t\t// The remaining length in the buffer is the potential buffer length\n\t\t\t\t// of the Chunk.\n\t\t\t\tsize_t chunkMaxBufferLength = GetBufferLength() - (ptr - GetBuffer());\n\n\t\t\t\tauto* chunk = T::Factory(ptr, chunkMaxBufferLength);\n\n\t\t\t\t// NOTE: Do not fix/update the Chunk buffer length since the caller\n\t\t\t\t// probably wants to modify the Chunk.\n\n\t\t\t\tHandleInPlaceChunk(chunk);\n\n\t\t\t\treturn chunk;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether `BuildChunkInPlace()` was called and the caller didn't invoke\n\t\t\t * `Consolidate()` on the returned Chunk yet.\n\t\t\t */\n\t\t\tbool NeedsConsolidation() const\n\t\t\t{\n\t\t\t\treturn this->needsConsolidation;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Calculate CRC32C value of the whole Packet and insert it into the\n\t\t\t * Checksum field.\n\t\t\t */\n\t\t\tvoid WriteCRC32cChecksum();\n\n\t\t\t/**\n\t\t\t * Validate CRC32C value in the Checksum field.\n\t\t\t */\n\t\t\tbool ValidateCRC32cChecksum() const;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * NOTE: Return CommonHeader* instead of const CommonHeader* since we may\n\t\t\t * want to modify its fields.\n\t\t\t */\n\t\t\tCommonHeader* GetHeaderPointer() const\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<CommonHeader*>(const_cast<uint8_t*>(GetBuffer()));\n\t\t\t}\n\n\t\t\tuint8_t* GetChunksPointer() const\n\t\t\t{\n\t\t\t\treturn const_cast<uint8_t*>(GetBuffer()) + Packet::CommonHeaderLength;\n\t\t\t}\n\n\t\t\tvirtual void HandleInPlaceChunk(Chunk* chunk) final;\n\n\t\t\tvirtual void AssertDoesNotNeedConsolidation() const final;\n\n\t\tprivate:\n\t\t\t// Chunks.\n\t\t\tstd::vector<Chunk*> chunks;\n\t\t\t// Whether `BuildChunkInPlace()` was called and the caller didn't invoke\n\t\t\t// `Consolidate()` on the returned Chunk yet.\n\t\t\tbool needsConsolidation{ false };\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/Parameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_PARAMETER_HPP\n#define MS_RTC_SCTP_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/TLV.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Parameter.\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Parameter Type         |       Parameter Length        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                        Parameter Value                        /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Parameter Type (16 bits).\n\t\t * - Parameter Length (16 bits): Total length of the Parameter, including\n\t\t *   the Parameter Type, Parameter Length and Parameter Value fields\n\t\t *   (padding is excluded). Thus, a Parameter with a zero-length Parameter\n\t\t *   Value field would have a Parameter Length field of 4.\n\t\t * - Parameter Value (variable length).\n\t\t * - Padding: Bytes of padding to make the Parameter total length be\n\t\t *   multiple of 4 bytes.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass Parameter : public TLV\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parameter Type.\n\t\t\t */\n\t\t\tenum class ParameterType : uint16_t\n\t\t\t{\n\t\t\t\tHEARTBEAT_INFO               = 0x0001,\n\t\t\t\tIPV4_ADDRESS                 = 0x0005,\n\t\t\t\tIPV6_ADDRESS                 = 0x0006,\n\t\t\t\tSTATE_COOKIE                 = 0x0007,\n\t\t\t\tUNRECOGNIZED_PARAMETER       = 0x0008,\n\t\t\t\tCOOKIE_PRESERVATIVE          = 0x0009,\n\t\t\t\tSUPPORTED_ADDRESS_TYPES      = 0x000C,\n\t\t\t\tFORWARD_TSN_SUPPORTED        = 0xC000, // Type 49152, RFC 3758\n\t\t\t\tSUPPORTED_EXTENSIONS         = 0x8008, // Type 32776, RFC 5061\n\t\t\t\tOUTGOING_SSN_RESET_REQUEST   = 0x000D, // Type 13, RFC 6525\n\t\t\t\tINCOMING_SSN_RESET_REQUEST   = 0x000E, // Type 14, RFC 6525\n\t\t\t\tSSN_TSN_RESET_REQUEST        = 0x000F, // Type 15, RFC 6525\n\t\t\t\tRECONFIGURATION_RESPONSE     = 0x0010, // Type 16, RFC 6525\n\t\t\t\tADD_OUTGOING_STREAMS_REQUEST = 0x0011, // Type 17, RFC 6525\n\t\t\t\tADD_INCOMING_STREAMS_REQUEST = 0x0012, // Type 18, RFC 6525\n\t\t\t\tZERO_CHECKSUM_ACCEPTABLE     = 0x8001, // Type 32769, RFC 9653\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Action that is taken if the processing endpoint does not recognize the\n\t\t\t * Parameter.\n\t\t\t */\n\t\t\tenum class ActionForUnknownParameterType : uint8_t\n\t\t\t{\n\t\t\t\tSTOP            = 0b00,\n\t\t\t\tSTOP_AND_REPORT = 0b01,\n\t\t\t\tSKIP            = 0b10,\n\t\t\t\tSKIP_AND_REPORT = 0b11\n\t\t\t};\n\n\t\t\t/**\n\t\t\t * Struct of an SCTP Parameter Header.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This struct is guaranteed to be aligned to 2 bytes.\n\t\t\t */\n\t\t\tstruct ParameterHeader\n\t\t\t{\n\t\t\t\tParameterType type;\n\t\t\t\t/**\n\t\t\t\t * The value of the Parameter Length field, which represents the total\n\t\t\t\t * length of the Parameter in bytes, including the Parameter Type,\n\t\t\t\t * Parameter Length and Parameter Value fields. So if the Parameter\n\t\t\t\t * Value field is zero-length, the Length field must be 4. The\n\t\t\t\t * Parameter Length field does not count any padding.\n\t\t\t\t */\n\t\t\t\tuint16_t length;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t ParameterHeaderLength{ 4 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Whether given buffer could be a a valid Parameter.\n\t\t\t *\n\t\t\t * @param buffer\n\t\t\t * @param bufferLength - Can be greater than real Parameter length.\n\t\t\t * @param parameterType - If given buffer is a valid Parameter then\n\t\t\t *   `parameterType` is rewritten to parsed ParameterType.\n\t\t\t * @param parameterLength - If given buffer is a valid Parameter then\n\t\t\t *   `parameterLength` is rewritten to the value of the Parameter Length\n\t\t\t *    field.\n\t\t\t * @param padding - If given buffer is a valid Parameter then `padding`\n\t\t\t *   is rewritten to the number of padding bytes in the Parameter (only\n\t\t\t *   the necessary ones to make total length multiple of 4).\n\t\t\t */\n\t\t\tstatic bool IsParameter(\n\t\t\t  const uint8_t* buffer,\n\t\t\t  size_t bufferLength,\n\t\t\t  ParameterType& parameterType,\n\t\t\t  uint16_t& parameterLength,\n\t\t\t  uint8_t& padding);\n\n\t\t\tstatic const std::string& ParameterTypeToString(ParameterType parameterType);\n\n\t\tprivate:\n\t\t\tstatic const std::unordered_map<ParameterType, std::string> ParameterType2String;\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Constructor is protected because we only want to create Parameter\n\t\t\t * instances via Parse() and Factory() in subclasses.\n\t\t\t */\n\t\t\tParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~Parameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const override = 0;\n\n\t\t\tParameter* Clone(uint8_t* buffer, size_t bufferLength) const override = 0;\n\n\t\t\tvirtual ParameterType GetType() const final\n\t\t\t{\n\t\t\t\treturn static_cast<ParameterType>(ntohs(static_cast<uint16_t>(GetHeaderPointer()->type)));\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * False by default. UnknownParameter class overrides this method to\n\t\t\t * return true instead.\n\t\t\t */\n\t\t\tvirtual bool HasUnknownType() const\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvirtual ActionForUnknownParameterType GetActionForUnknownParameterType() const final\n\t\t\t{\n\t\t\t\treturn static_cast<ActionForUnknownParameterType>(GetBuffer()[0] >> 6);\n\t\t\t}\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvoid DumpCommon(int indentation) const final;\n\n\t\t\tvirtual void SoftSerialize(const uint8_t* buffer) final;\n\n\t\t\tvirtual Parameter* SoftClone(const uint8_t* buffer) const = 0;\n\n\t\t\tvirtual void SoftCloneInto(Parameter* parameter) const final;\n\n\t\t\tvirtual void InitializeHeader(ParameterType parameterType, uint16_t lengthFieldValue) final;\n\n\t\t\t/**\n\t\t\t * Parameter subclasses with header bigger than default one (4 bytes)\n\t\t\t * must override this method and return their header length (excluding\n\t\t\t * variable-length field considered \"value\").\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const override\n\t\t\t{\n\t\t\t\treturn Parameter::ParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * NOTE: Return ParameterHeader* instead of const ParameterHeader* since\n\t\t\t * we may want to modify its fields.\n\t\t\t */\n\t\t\tvirtual ParameterHeader* GetHeaderPointer() const final\n\t\t\t{\n\t\t\t\treturn reinterpret_cast<ParameterHeader*>(const_cast<uint8_t*>(GetBuffer()));\n\t\t\t}\n\n\t\t\tvirtual void SetType(ParameterType parameterType) final\n\t\t\t{\n\t\t\t\tGetHeaderPointer()->type =\n\t\t\t\t  static_cast<ParameterType>(htons(static_cast<uint16_t>(parameterType)));\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/TLV.hpp",
    "content": "#ifndef MS_RTC_SCTP_TLV_HPP\n#define MS_RTC_SCTP_TLV_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/Serializable.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP TLV (Type-Length-Value).\n\t\t *\n\t\t * This is the base class of all items in an SCTP Packet, this is:\n\t\t * - SCTP Chunk,\n\t\t * - SCTP Parameter, and\n\t\t * - SCTP Error Cause.\n\t\t *\n\t\t * All those items have the same Length field and 4-byte padded length.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                               |             Length            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                             Value                             /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\tclass TLV : public Serializable\n\t\t{\n\t\tpublic:\n\t\t\tstatic const size_t TLVHeaderLength{ 4 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Whether given buffer could be a a valid SCTP TLV.\n\t\t\t */\n\t\t\tstatic bool IsTLV(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t& itemLength, uint8_t& padding);\n\n\t\tprotected:\n\t\t\tTLV(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~TLV() override;\n\n\t\tprotected:\n\t\t\t/**\n\t\t\t * Subclasses must invoke this method within their Dump() method.\n\t\t\t */\n\t\t\tvirtual void DumpCommon(int indentation) const;\n\n\t\t\tvirtual void InitializeTLVHeader(uint16_t lengthFieldValue) final;\n\n\t\t\t/**\n\t\t\t * Subclasses with header bigger than default one (4 bytes) must override\n\t\t\t * this method and return their header length (excluding variable-length\n\t\t\t * field considered \"value\", Optional/Variable-Length\n\t\t\t * Parameters and Error Causes).\n\t\t\t */\n\t\t\tvirtual size_t GetHeaderLength() const\n\t\t\t{\n\t\t\t\treturn TLV::TLVHeaderLength;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The value of the Length field, which includes the length of the header\n\t\t\t * and content (padding excluded).\n\t\t\t */\n\t\t\tvirtual uint16_t GetLengthField() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 2);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * A pointer to the position in the buffer where the variable-length value\n\t\t\t * (if any) starts or should start.\n\t\t\t */\n\t\t\tvirtual uint8_t* GetVariableLengthValuePointer() const final\n\t\t\t{\n\t\t\t\treturn const_cast<uint8_t*>(GetBuffer()) + GetHeaderLength();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Whether this item contains a variable-length value.\n\t\t\t *\n\t\t\t * @see GetVariableLengthValue()\n\t\t\t */\n\t\t\tvirtual bool HasVariableLengthValue() const final\n\t\t\t{\n\t\t\t\treturn GetLengthField() > GetHeaderLength();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Variable-length value of this item.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The variable-length value starts after the fixed header, which can be\n\t\t\t *   different and have different length in each item definition.\n\t\t\t * - In the case of SCTP Chunk class and subclasses (which implements this\n\t\t\t *   class) we assume that a Chunk having variable-length value does not\n\t\t\t *   have Parameters or Error Causes.\n\t\t\t */\n\t\t\tvirtual const uint8_t* GetVariableLengthValue() const final\n\t\t\t{\n\t\t\t\tif (!HasVariableLengthValue())\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn GetVariableLengthValuePointer();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Set the variable-length value. It copies the given value into the\n\t\t\t * the variable-length value of the item and updates both the length of\n\t\t\t * the Serializable and the Length field.\n\t\t\t *\n\t\t\t * @throw MediaSoupTypeError - If given `valueLength` is higher than\n\t\t\t *   available length.\n\t\t\t *\n\t\t\t * @see GetVariableLengthValue()\n\t\t\t */\n\t\t\tvirtual void SetVariableLengthValue(const uint8_t* value, size_t valueLength) final;\n\n\t\t\t/**\n\t\t\t * The length of the variable-length value.\n\t\t\t */\n\t\t\tvirtual uint16_t GetVariableLengthValueLength() const final\n\t\t\t{\n\t\t\t\tif (!HasVariableLengthValue())\n\t\t\t\t{\n\t\t\t\t\treturn 0u;\n\t\t\t\t}\n\n\t\t\t\treturn GetLengthField() - GetHeaderLength();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Set the length of the variable-length value. It doesn't copy any value\n\t\t\t * into the variable-length value. This method is used in items that have\n\t\t\t * variable-length value but it doesn't consist on a buffer + length, but\n\t\t\t * instead is an structure with fields (with variable length).\n\t\t\t *\n\t\t\t * @see GetVariableLengthValue()\n\t\t\t */\n\t\t\tvirtual void SetVariableLengthValueLength(size_t valueLength) final;\n\n\t\t\t/**\n\t\t\t * This method doesn't really add an item into the item (that must be done\n\t\t\t * by each subcass) but updates the length of the Serializable and the\n\t\t\t * value of the Length field by incrementing it with the length of the\n\t\t\t * given item.\n\t\t\t */\n\t\t\tvirtual void AddItem(const TLV* item) final;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * @throw MediaSoupTypeError - If given `length` is higher than maximum\n\t\t\t *   allowed one (65535).\n\t\t\t */\n\t\t\tvirtual void SetLengthField(size_t lengthField) final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/UserData.hpp",
    "content": "#ifndef MS_RTC_SCTP_USER_DATA_HPP\n#define MS_RTC_SCTP_USER_DATA_HPP\n\n#include \"common.hpp\"\n#include <ostream>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Represents user data extracted from a DATA or I_DATA Chunk.\n\t\t */\n\t\tclass UserData\n\t\t{\n\t\tpublic:\n\t\t\tUserData(\n\t\t\t  uint16_t streamId,\n\t\t\t  uint16_t ssn,\n\t\t\t  uint32_t mid,\n\t\t\t  uint32_t fsn,\n\t\t\t  uint32_t ppid,\n\t\t\t  std::vector<uint8_t> payload,\n\t\t\t  bool isBeginning,\n\t\t\t  bool isEnd,\n\t\t\t  bool isUnordered);\n\n\t\t\t/**\n\t\t\t * Move constructor. No need to do anything special since std::vector\n\t\t\t * already implements move.\n\t\t\t */\n\t\t\tUserData(UserData&& other) = default;\n\n\t\t\t/**\n\t\t\t * Move assignment. No need to do anything special since std::vector\n\t\t\t * already implements move.\n\t\t\t */\n\t\t\tUserData& operator=(UserData&& other) = default;\n\n\t\t\t/**\n\t\t\t * Copy constructor disabled.\n\t\t\t */\n\t\t\tUserData(const UserData&) = delete;\n\n\t\t\t/**\n\t\t\t * Copy assignment disabled.\n\t\t\t */\n\t\t\tUserData& operator=(const UserData&) = delete;\n\n\t\t\tbool operator==(const UserData& other) const\n\t\t\t{\n\t\t\t\treturn (\n\t\t\t\t  this->streamId == other.streamId && this->ssn == other.ssn && this->mid == other.mid &&\n\t\t\t\t  this->fsn == other.fsn && this->ppid == other.ppid && this->payload == other.payload &&\n\t\t\t\t  this->isBeginning == other.isBeginning && this->isEnd == other.isEnd &&\n\t\t\t\t  this->isUnordered == other.isUnordered);\n\t\t\t}\n\n\t\t\t~UserData();\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\t/**\n\t\t\t * Stream Identifier (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tuint16_t GetStreamId() const\n\t\t\t{\n\t\t\t\treturn this->streamId;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Stream Sequence Number (only in DATA chunks).\n\t\t\t */\n\t\t\tuint16_t GetStreamSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->ssn;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Message Identifier (MID) (only in I_DATA chunks).\n\t\t\t */\n\t\t\tuint32_t GetMessageId() const\n\t\t\t{\n\t\t\t\treturn this->mid;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Fragment Sequence Number (FSN) (only in I_DATA chunks).\n\t\t\t */\n\t\t\tuint32_t GetFragmentSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn this->fsn;\n\t\t\t}\n\n\t\t\tuint32_t GetPayloadProtocolId() const\n\t\t\t{\n\t\t\t\treturn this->ppid;\n\t\t\t}\n\n\t\t\tstd::vector<uint8_t>& GetPayload()\n\t\t\t{\n\t\t\t\treturn this->payload;\n\t\t\t}\n\n\t\t\tsize_t GetPayloadLength() const\n\t\t\t{\n\t\t\t\treturn this->payload.size();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Useful to extract the payload and its ownership when destructing the\n\t\t\t * UserData.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - && at the end means that it can only be called from a rvalue.\n\t\t\t *\n\t\t\t * @usage\n\t\t\t * ```c++\n\t\t\t * const auto payload = std::move(userData).ReleasePayload();\n\t\t\t * ```\n\t\t\t */\n\t\t\tstd::vector<uint8_t> ReleasePayload() &&\n\t\t\t{\n\t\t\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)\n\t\t\t\treturn std::move(this->payload);\n\t\t\t}\n\n\t\t\tUserData Clone() const\n\t\t\t{\n\t\t\t\treturn UserData(\n\t\t\t\t  this->streamId,\n\t\t\t\t  this->ssn,\n\t\t\t\t  this->mid,\n\t\t\t\t  this->fsn,\n\t\t\t\t  this->ppid,\n\t\t\t\t  this->payload,\n\t\t\t\t  this->isBeginning,\n\t\t\t\t  this->isEnd,\n\t\t\t\t  this->isUnordered);\n\t\t\t}\n\n\t\t\tbool IsBeginning() const\n\t\t\t{\n\t\t\t\treturn this->isBeginning;\n\t\t\t}\n\n\t\t\tbool IsEnd() const\n\t\t\t{\n\t\t\t\treturn this->isEnd;\n\t\t\t}\n\n\t\t\tbool IsUnordered() const\n\t\t\t{\n\t\t\t\treturn this->isUnordered;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t streamId;\n\t\t\tuint16_t ssn;\n\t\t\tuint32_t mid;\n\t\t\tuint32_t fsn;\n\t\t\tuint32_t ppid;\n\t\t\tstd::vector<uint8_t> payload;\n\t\t\tbool isBeginning;\n\t\t\tbool isEnd;\n\t\t\tbool isUnordered;\n\t\t};\n\n\t\t/**\n\t\t * For Catch2 to print it nicely.\n\t\t */\n\t\tinline std::ostream& operator<<(std::ostream& os, const UserData& d)\n\t\t{\n\t\t\treturn os << \"{streamId:\" << d.GetStreamId() << \", ssn:\" << d.GetStreamSequenceNumber()\n\t\t\t          << \", mid:\" << d.GetMessageId() << \", fsn:\" << d.GetFragmentSequenceNumber()\n\t\t\t          << \", ppid:\" << d.GetPayloadProtocolId() << \", payloadLen:\" << d.GetPayloadLength()\n\t\t\t          << \", B:\" << d.IsBeginning() << \", E:\" << d.IsEnd() << \", U:\" << d.IsUnordered()\n\t\t\t          << \"}\";\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_ABORT_ASSOCIATION_ERROR_CHUNK_HPP\n#define MS_RTC_SCTP_ABORT_ASSOCIATION_ERROR_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Abort Association Chunk (ABORT) (6).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 6    |  Reserved   |T|            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                   zero or more Error Causes                   /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 6.\n\t\t * - T bit (1 bit): The T bit is set to 0 if the sender filled in the\n\t\t *   Verification Tag expected by the peer. If the Verification Tag is\n\t\t *   reflected, the T bit MUST be set to 1. Reflecting means that the sent\n\t\t *   Verification Tag is the same as the received one.\n\t\t * - Length (16 bits).\n\t\t *\n\t\t * Optional Variable-Length Error Causes (anyone).\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass AbortAssociationChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a AbortAssociationChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic AbortAssociationChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a AbortAssociationChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic AbortAssociationChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a AbortAssociationChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic AbortAssociationChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tAbortAssociationChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~AbortAssociationChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tAbortAssociationChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool GetT() const\n\t\t\t{\n\t\t\t\treturn GetBit0();\n\t\t\t}\n\n\t\t\tvoid SetT(bool flag);\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tAbortAssociationChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/AnyDataChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_ANY_DATA_CHUNK_HPP\n#define MS_RTC_SCTP_ANY_DATA_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Base class for DataChunk and IDataChunk.\n\t\t */\n\n\t\tclass AnyDataChunk : public Chunk\n\t\t{\n\t\tprotected:\n\t\t\tAnyDataChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t\t{\n\t\t\t}\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * The (I)mmediate bit (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual bool GetI() const = 0;\n\n\t\t\t/**\n\t\t\t * The (U)nordered bit (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual bool GetU() const = 0;\n\n\t\t\t/**\n\t\t\t * The (B)eginning fragment bit (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual bool GetB() const = 0;\n\n\t\t\t/**\n\t\t\t * The (E)nding fragment bit (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual bool GetE() const = 0;\n\n\t\t\t/**\n\t\t\t * TSN (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint32_t GetTsn() const = 0;\n\n\t\t\t/**\n\t\t\t * Stream Identifier (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint16_t GetStreamId() const = 0;\n\n\t\t\t/**\n\t\t\t * Stream Sequence Number (only in DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint16_t GetStreamSequenceNumber() const = 0;\n\n\t\t\t/**\n\t\t\t * Message Identifier (MID) (only in I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint32_t GetMessageId() const = 0;\n\n\t\t\t/**\n\t\t\t * Fragment Sequence Number (FSN) (only in I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint32_t GetFragmentSequenceNumber() const = 0;\n\n\t\t\t/**\n\t\t\t * Payload Protocol Identifier (PPID) (in DATA and I_DATA chunks).\n\t\t\t */\n\t\t\tvirtual uint32_t GetPayloadProtocolId() const = 0;\n\n\t\t\tvirtual bool HasUserDataPayload() const = 0;\n\n\t\t\tvirtual const uint8_t* GetUserDataPayload() const = 0;\n\n\t\t\tvirtual uint16_t GetUserDataPayloadLength() const = 0;\n\n\t\t\tvirtual UserData MakeUserData() const = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_ANY_FORWARD_TSN_CHUNK_HPP\n#define MS_RTC_SCTP_ANY_FORWARD_TSN_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include <ostream>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Base class for ForwardTsnChunk and IForwardTsnChunk.\n\t\t */\n\n\t\tclass AnyForwardTsnChunk : public Chunk\n\t\t{\n\t\tpublic:\n\t\t\tstruct SkippedStream\n\t\t\t{\n\t\t\t\tSkippedStream(uint16_t streamId, uint16_t ssn)\n\t\t\t\t  : streamId(streamId), ssn(ssn), mid(0), unordered(false)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tSkippedStream(bool unordered, uint16_t streamId, uint32_t mid)\n\t\t\t\t  : streamId(streamId), ssn(0), mid(mid), unordered(unordered)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tuint16_t streamId;\n\n\t\t\t\t/**\n\t\t\t\t * Only set for FORWARD_TSN.\n\t\t\t\t */\n\t\t\t\tuint16_t ssn;\n\n\t\t\t\t/**\n\t\t\t\t * Only set for I_FORWARD_TSN.\n\t\t\t\t */\n\t\t\t\tuint32_t mid;\n\n\t\t\t\t/**\n\t\t\t\t * Only set for I_FORWARD_TSN.\n\t\t\t\t */\n\t\t\t\tbool unordered;\n\n\t\t\t\tbool operator==(const SkippedStream& other) const\n\t\t\t\t{\n\t\t\t\t\treturn streamId == other.streamId && ssn == other.ssn && mid == other.mid &&\n\t\t\t\t\t       unordered == other.unordered;\n\t\t\t\t}\n\t\t\t};\n\n\t\tprotected:\n\t\t\tAnyForwardTsnChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t\t{\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvirtual uint32_t GetNewCumulativeTsn() const = 0;\n\n\t\t\tvirtual uint16_t GetNumberOfSkippedStreams() const = 0;\n\n\t\t\tvirtual std::vector<AnyForwardTsnChunk::SkippedStream> GetSkippedStreams() const = 0;\n\t\t};\n\n\t\t/**\n\t\t * For logging purposes in Catch2 tests.\n\t\t */\n\t\tinline std::ostream& operator<<(std::ostream& os, const AnyForwardTsnChunk::SkippedStream& s)\n\t\t{\n\t\t\treturn os << \"{streamId:\" << s.streamId << \", ssn:\" << s.ssn << \", mid:\" << s.mid\n\t\t\t          << \", unordered:\" << s.unordered << \"}\";\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/AnyInitChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_ANY_INIT_CHUNK_HPP\n#define MS_RTC_SCTP_ANY_INIT_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Base class for InitChunk and InitAckChunk.\n\t\t */\n\n\t\tclass AnyInitChunk : public Chunk\n\t\t{\n\t\tprotected:\n\t\t\tAnyInitChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t\t{\n\t\t\t}\n\n\t\tpublic:\n\t\t\tvirtual uint32_t GetInitiateTag() const = 0;\n\n\t\t\tvirtual uint32_t GetAdvertisedReceiverWindowCredit() const = 0;\n\n\t\t\tvirtual uint16_t GetNumberOfOutboundStreams() const = 0;\n\n\t\t\tvirtual uint16_t GetNumberOfInboundStreams() const = 0;\n\n\t\t\tvirtual uint32_t GetInitialTsn() const = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/CookieAckChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_COOKIE_ACK_CHUNK_HPP\n#define MS_RTC_SCTP_COOKIE_ACK_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Cookie Acknowledgement Chunk (COOKIE_ACK) (11).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 11   |  Chunk Flags  |          Length = 4           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 11.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits): 4.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass CookieAckChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a CookieAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic CookieAckChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a CookieAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic CookieAckChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a CookieAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic CookieAckChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tCookieAckChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~CookieAckChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tCookieAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tCookieAckChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/CookieEchoChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_COOKIE_ECHO_CHUNK_HPP\n#define MS_RTC_SCTP_COOKIE_ECHO_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Cookie Echo Chunk (COOKIE_ECHO) (10).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 10   |  Chunk Flags  |            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                            Cookie                             /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 10.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - Cookie (variable length).\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass CookieEchoChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a CookieEchoChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic CookieEchoChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a CookieEchoChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic CookieEchoChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a CookieEchoChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic CookieEchoChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tCookieEchoChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~CookieEchoChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tCookieEchoChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool HasCookie() const\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetCookie() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetCookieLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetCookie(const uint8_t* cookie, uint16_t cookieLength);\n\n\t\tprotected:\n\t\t\tCookieEchoChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/DataChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_DATA_CHUNK_HPP\n#define MS_RTC_SCTP_DATA_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyDataChunk.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Payload Data Chunk (DATA) (0).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 0    |  Res  |I|U|B|E|       Length = Variable       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                              TSN                              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |      Stream Identifier S      |   Stream Sequence Number n    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                  Payload Protocol Identifier                  |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                 User Data (seq n of Stream S)                 /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 0.\n\t\t * - Res (4 bits): All set to 0.\n\t\t * - I bit (1 bit): The (I)mmediate bit MAY be set by the sender whenever\n\t\t *   the sender of a DATA chunk can benefit from the corresponding SACK\n\t\t *   chunk being sent back without delay.\n\t\t * - U bit (1 bit): The (U)nordered bit, if set to 1, indicates that this\n\t\t *   is an unordered DATA chunk, and there is no Stream Sequence Number\n\t\t *   assigned to this DATA chunk.\n\t\t * - B bit (1 bit): The (B)eginning fragment bit, if set, indicates the\n\t\t *   first fragment of a user message.\n\t\t * - E bit (1 bit): The (E)nding fragment bit, if set, indicates the last\n\t\t *   fragment of a user message.\n\t\t * - Length (16 bits): This field indicates the length of the DATA chunk in\n\t\t *   bytes from the beginning of the type field to the end of the User Data\n\t\t *   field excluding any padding. A DATA chunk with one byte of user data\n\t\t *   will have the Length field set to 17 (indicating 17 bytes). A DATA\n\t\t *   chunk with a User Data field of length L will have the Length field\n\t\t *   set to (16 + L) (indicating 16 + L bytes) where L MUST be greater than\n\t\t *   0.\n\t\t * - TSN (32 bits): This value represents the TSN for this DATA chunk. The\n\t\t *   valid range of TSN is from 0 to 4294967295 (232 - 1). TSN wraps back\n\t\t *   to 0 after reaching 4294967295.\n\t\t * - Stream Identifier S (16 bits): Identifies the stream to which the\n\t\t *   following user data belongs.\n\t\t * - Stream Sequence Number n (16 bits): This value represents the Stream\n\t\t *   Sequence Number of the following user data within the stream S. Valid\n\t\t *   range is 0 to 65535. When a user message is fragmented by SCTP for\n\t\t *   transport, the same Stream Sequence Number MUST be carried in each of\n\t\t *   the fragments of the message.\n\t\t * - Payload Protocol Identifier (PPID) (32 bits): This value represents an\n\t\t *   application (or upper layer) specified protocol identifier.\n\t\t * - User Data (variable length): This is the payload user data. The\n\t\t *   implementation MUST pad the end of the data to a 4-byte boundary with\n\t\t *   all zero bytes. Any padding MUST NOT be included in the Length field.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass DataChunk : public AnyDataChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t DataChunkHeaderLength{ 16 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a DataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic DataChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a DataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic DataChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a DataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic DataChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tDataChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~DataChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tDataChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool GetI() const final\n\t\t\t{\n\t\t\t\treturn GetBit3();\n\t\t\t}\n\n\t\t\tvoid SetI(bool flag);\n\n\t\t\tbool GetU() const final\n\t\t\t{\n\t\t\t\treturn GetBit2();\n\t\t\t}\n\n\t\t\tvoid SetU(bool flag);\n\n\t\t\tbool GetB() const final\n\t\t\t{\n\t\t\t\treturn GetBit1();\n\t\t\t}\n\n\t\t\tvoid SetB(bool flag);\n\n\t\t\tbool GetE() const final\n\t\t\t{\n\t\t\t\treturn GetBit0();\n\t\t\t}\n\n\t\t\tvoid SetE(bool flag);\n\n\t\t\tuint32_t GetTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetTsn(uint32_t value);\n\n\t\t\tuint16_t GetStreamId() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(const_cast<uint8_t*>(GetBuffer()), 8);\n\t\t\t}\n\n\t\t\tvoid SetStreamId(uint16_t value);\n\n\t\t\tuint16_t GetStreamSequenceNumber() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(const_cast<uint8_t*>(GetBuffer()), 10);\n\t\t\t}\n\n\t\t\tvoid SetStreamSequenceNumber(uint16_t value);\n\n\t\t\t/**\n\t\t\t * @remarks Only in I_DATA chunks.\n\t\t\t */\n\t\t\tuint32_t GetMessageId() const final\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tuint32_t GetPayloadProtocolId() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @remarks Only in I_DATA chunks.\n\t\t\t */\n\t\t\tuint32_t GetFragmentSequenceNumber() const final\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tvoid SetPayloadProtocolId(uint32_t value);\n\n\t\t\tbool HasUserDataPayload() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUserDataPayload() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUserDataPayloadLength() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength);\n\n\t\t\tUserData MakeUserData() const final\n\t\t\t{\n\t\t\t\tconst auto* userData       = GetUserDataPayload();\n\t\t\t\tconst uint16_t userDataLen = GetUserDataPayloadLength();\n\n\t\t\t\tstd::vector<uint8_t> payload(userData, userData + userDataLen);\n\n\t\t\t\treturn UserData(\n\t\t\t\t  GetStreamId(),\n\t\t\t\t  GetStreamSequenceNumber(),\n\t\t\t\t  GetMessageId(),\n\t\t\t\t  GetFragmentSequenceNumber(),\n\t\t\t\t  GetPayloadProtocolId(),\n\t\t\t\t  std::move(payload),\n\t\t\t\t  GetB(),\n\t\t\t\t  GetE(),\n\t\t\t\t  GetU());\n\t\t\t}\n\n\t\t\tvoid SetUserData(UserData userData);\n\n\t\tprotected:\n\t\t\tDataChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn DataChunk::DataChunkHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_FORWARD_TSN_CHUNK_HPP\n#define MS_RTC_SCTP_FORWARD_TSN_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Forward Cumulative TSN Chunk (FORWARD_TSN) (192)\n\t\t *\n\t\t * @see RFC 3758.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 192  |  Flags = 0x00 |        Length = Variable      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      New Cumulative TSN                       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |         Stream-1              |       Stream Sequence-1       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               /\n\t\t * /                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |         Stream-N              |       Stream Sequence-N       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 192.\n\t\t * - Flags: All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - New Cumulative TSN (32 bits): This indicates the new cumulative TSN to\n\t\t *   the data receiver.\n\t\t * - Stream-N (16 bits): Stream number that was skipped by this FWD-TSN.\n\t\t * - Stream Sequence-N (16 bit): Sequence number associated with the stream\n\t\t *   that was skipped.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass ForwardTsnChunk : public AnyForwardTsnChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t ForwardTsnChunkHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic ForwardTsnChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic ForwardTsnChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic ForwardTsnChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tForwardTsnChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ForwardTsnChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tForwardTsnChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetNewCumulativeTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetNewCumulativeTsn(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfSkippedStreams() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() / 4;\n\t\t\t}\n\n\t\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> GetSkippedStreams() const final;\n\n\t\t\tvoid AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream);\n\n\t\tprotected:\n\t\t\tForwardTsnChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn ForwardTsnChunk::ForwardTsnChunkHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t GetSkippedStreamIdAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 4));\n\t\t\t}\n\n\t\t\tuint16_t GetStreamSequenceAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 4) + 2);\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_HEARTBEAT_ACK_CHUNK_HPP\n#define MS_RTC_SCTP_HEARTBEAT_ACK_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Heartbeat Acknowledgement Chunk (HEARTBEAT_ACK) (5)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 5    |  Chunk Flags  |     Heartbeat Ack Length      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /          Heartbeat Information TLV (Variable-Length)          /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 5.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - Heartbeat Information (variable length).\n\t\t *\n\t\t * Mandatory Variable-Length Parameters:\n\t\t * - Heartbeat Info (1), mandatory.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass HeartbeatAckChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic HeartbeatAckChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a HeartbeatAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic HeartbeatAckChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic HeartbeatAckChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tHeartbeatAckChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~HeartbeatAckChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tHeartbeatAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tHeartbeatAckChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_HEARTBEAT_REQUEST_CHUNK_HPP\n#define MS_RTC_SCTP_HEARTBEAT_REQUEST_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Heartbeat Request Chunk (HEARTBEAT_REQUEST) (4).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 4    |  Chunk Flags  |       Heartbeat Length        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /          Heartbeat Information TLV (Variable-Length)          /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 4.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - Heartbeat Information (variable length).\n\t\t *\n\t\t * Mandatory Variable-Length Parameters:\n\t\t * - Heartbeat Info (1), mandatory.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass HeartbeatRequestChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatRequestChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic HeartbeatRequestChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a HeartbeatRequestChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic HeartbeatRequestChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatRequestChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic HeartbeatRequestChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tHeartbeatRequestChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~HeartbeatRequestChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tHeartbeatRequestChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tHeartbeatRequestChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/IDataChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_I_DATA_CHUNK_HPP\n#define MS_RTC_SCTP_I_DATA_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyDataChunk.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP I-Data Chunk (I_DATA) (64).\n\t\t *\n\t\t * @see RFC 8260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 64   |  Res  |I|U|B|E|       Length = Variable       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                              TSN                              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Stream Identifier      |          (Reserved)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      Message Identifier                       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |    Payload Protocol Identifier / Fragment Sequence Number     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                           User Data                           /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 64.\n\t\t * - Res (4 bits): All set to 0.\n\t\t * - I bit (1 bit): The (I)mmediate bit, if set, indicates that the\n\t\t *   receiver SHOULD NOT delay the sending of the corresponding SACK chunk.\n\t\t * - U bit (1 bit): The (U)nordered bit, if set, indicates the user message\n\t\t *   is unordered.\n\t\t * - B bit (1 bit): The (B)eginning fragment bit, if set, indicates the\n\t\t *   first fragment of a user message.\n\t\t * - E bit (1 bit): The (E)nding fragment bit, if set, indicates the last\n\t\t *   fragment of a user message.\n\t\t * - Length (16 bits): This field indicates the length of the I-DATA chunk in\n\t\t *   bytes from the beginning of the type field to the end of the User Data\n\t\t *   field excluding any padding.\n\t\t * - TSN (32 bits): This value represents the TSN for this I-DATA chunk.\n\t\t * - Stream Identifier (16 bits): Identifies the stream to which the user\n\t\t *   data belongs.\n\t\t * - Reserved (16 bits): All set to zero.\n\t\t * - Message Identifier (MID) (32 bits): The MID is the same for all\n\t\t *   fragments of a user message. It is used to determine which fragments\n\t\t *   (enumerated by the FSN) belong to the same user message. For ordered\n\t\t *   user messages, the MID is also used by the SCTP receiver to deliver\n\t\t *   the user messages in the correct order to the upper layer.\n\t\t * - Payload Protocol Identifier (PPID) / Fragment Sequence Number (FSN)\n\t\t *   (32 bits): If the B bit is set, this field contains the PPID of the\n\t\t *   user message. If the B bit is not set, this field contains the FSN.\n\t\t *   The FSN is used to enumerate all fragments of a single user message,\n\t\t *   starting from 0 and incremented by 1. The last fragment of a message\n\t\t *   MUST have the E bit set.\n\t\t * - User Data (variable length): This is the payload user data.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass IDataChunk : public AnyDataChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t IDataChunkHeaderLength{ 20 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a IDataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic IDataChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a IDataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic IDataChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a IDataChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic IDataChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tIDataChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~IDataChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tIDataChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool GetI() const final\n\t\t\t{\n\t\t\t\treturn GetBit3();\n\t\t\t}\n\n\t\t\tvoid SetI(bool flag);\n\n\t\t\tbool GetU() const final\n\t\t\t{\n\t\t\t\treturn GetBit2();\n\t\t\t}\n\n\t\t\tvoid SetU(bool flag);\n\n\t\t\tbool GetB() const final\n\t\t\t{\n\t\t\t\treturn GetBit1();\n\t\t\t}\n\n\t\t\tvoid SetB(bool flag);\n\n\t\t\tbool GetE() const final\n\t\t\t{\n\t\t\t\treturn GetBit0();\n\t\t\t}\n\n\t\t\tvoid SetE(bool flag);\n\n\t\t\tuint32_t GetTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetTsn(uint32_t value);\n\n\t\t\tuint16_t GetStreamId() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(const_cast<uint8_t*>(GetBuffer()), 8);\n\t\t\t}\n\n\t\t\tvoid SetStreamId(uint16_t value);\n\n\t\t\t/**\n\t\t\t * @remarks Only in DATA chunks.\n\t\t\t */\n\t\t\tuint16_t GetStreamSequenceNumber() const final\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tuint32_t GetMessageId() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(const_cast<uint8_t*>(GetBuffer()), 12);\n\t\t\t}\n\n\t\t\tvoid SetMessageId(uint32_t value);\n\n\t\t\tuint32_t GetPayloadProtocolId() const final\n\t\t\t{\n\t\t\t\tif (GetB())\n\t\t\t\t{\n\t\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @throw MediaSoupError - If the B bit is not set.\n\t\t\t */\n\t\t\tvoid SetPayloadProtocolId(uint32_t value);\n\n\t\t\tuint32_t GetFragmentSequenceNumber() const final\n\t\t\t{\n\t\t\t\tif (!GetB())\n\t\t\t\t{\n\t\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @throw MediaSoupError - If the B bit is set.\n\t\t\t */\n\t\t\tvoid SetFragmentSequenceNumber(uint32_t value);\n\n\t\t\tbool HasUserDataPayload() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUserDataPayload() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUserDataPayloadLength() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength);\n\n\t\t\tUserData MakeUserData() const final\n\t\t\t{\n\t\t\t\tconst auto* userData       = GetUserDataPayload();\n\t\t\t\tconst uint16_t userDataLen = GetUserDataPayloadLength();\n\n\t\t\t\tstd::vector<uint8_t> payload(userData, userData + userDataLen);\n\n\t\t\t\treturn UserData(\n\t\t\t\t  GetStreamId(),\n\t\t\t\t  GetStreamSequenceNumber(),\n\t\t\t\t  GetMessageId(),\n\t\t\t\t  GetFragmentSequenceNumber(),\n\t\t\t\t  GetPayloadProtocolId(),\n\t\t\t\t  std::move(payload),\n\t\t\t\t  GetB(),\n\t\t\t\t  GetE(),\n\t\t\t\t  GetU());\n\t\t\t}\n\n\t\t\tvoid SetUserData(UserData userData);\n\n\t\tprotected:\n\t\t\tIDataChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn IDataChunk::IDataChunkHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid SetReserved();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_I_FORWARD_TSN_CHUNK_HPP\n#define MS_RTC_SCTP_I_FORWARD_TSN_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP I-Forward Cumulative TSN Chunk (I_FORWARD_TSN) (194)\n\t\t *\n\t\t * @see RFC 8260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 194  | Flags = 0x00  |      Length = Variable        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                       New Cumulative TSN                      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |       Stream Identifier       |         (Reserved)          |U|\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                       Message Identifier                      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                                                               /\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |       Stream Identifier       |         (Reserved)          |U|\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                       Message Identifier                      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 194.\n\t\t * - Flags: All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - New Cumulative TSN (32 bits): This indicates the new cumulative TSN to\n\t\t *   the data receiver.\n\t\t * - Stream Identifier (SID) (16 bits): This field holds the stream number\n\t\t *   this entry refers to.\n\t\t * - Reserved (15 bits).\n\t\t * - U bit (1 bit): The U bit specifies if the Message Identifier of this\n\t\t *   entry refers to unordered messages (U bit is set) or ordered messages\n\t\t *   (U bit is not set).\n\t\t * - Message Identifier (MID) (32 bits): This field holds the largest\n\t\t *   Message Identifier for ordered or unordered messages indicated by the\n\t\t *   U bit that was skipped for the stream specified by the Stream\n\t\t *   Identifier. For ordered messages, this is similar to the FORWARD-TSN\n\t\t *   chunk, just replacing the 16-bit SSN by the 32-bit MID.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass IForwardTsnChunk : public AnyForwardTsnChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t IForwardTsnChunkHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a IForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic IForwardTsnChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a IForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic IForwardTsnChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a IForwardTsnChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic IForwardTsnChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tIForwardTsnChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~IForwardTsnChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tIForwardTsnChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetNewCumulativeTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetNewCumulativeTsn(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfSkippedStreams() const final\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() / 8;\n\t\t\t}\n\n\t\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> GetSkippedStreams() const final;\n\n\t\t\tvoid AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream);\n\n\t\tprotected:\n\t\t\tIForwardTsnChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn IForwardTsnChunk::IForwardTsnChunkHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t GetSkippedStreamIdAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 8));\n\t\t\t}\n\n\t\t\tbool GetUFlagAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn (Utils::Byte::Get1Byte(GetVariableLengthValuePointer(), (idx * 8) + 3) & 0x01) != 0;\n\t\t\t}\n\n\t\t\tuint32_t GetMessageIdentifierAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetVariableLengthValuePointer(), (idx * 8) + 4);\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/InitAckChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_INIT_ACK_CHUNK_HPP\n#define MS_RTC_SCTP_INIT_ACK_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyInitChunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Initiation Acknowledgement Chunk (INIT_ACK) (2).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 2    |  Chunk Flags  |      Chunk Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                         Initiate Tag                          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Advertised Receiver Window Credit (a_rwnd)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Number of Outbound Streams   |   Number of Inbound Streams   |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                          Initial TSN                          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /              Optional/Variable-Length Parameters              /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 2.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Initiate Tag (32 bits): The receiver of the INIT ACK chunk records the\n\t\t *   value of the Initiate Tag parameter. This value MUST be placed into\n\t\t *   the Verification Tag field of every SCTP packet that the receiver of\n\t\t *   the INIT ACK chunk transmits within this association.\n\t\t * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This value\n\t\t *   represents the dedicated buffer space, in number of bytes, the sender\n\t\t *   of the INIT ACK chunk has reserved in association with this window.\n\t\t *   The Advertised Receiver Window Credit MUST NOT be smaller than 1500.\n\t\t * - Number of Outbound Streams (OS) (16 bits): Defines the number of\n\t\t *   outbound streams the sender of this INIT ACK chunk wishes to create in\n\t\t *   this association. The value of 0 MUST NOT be used.\n\t\t * - Number of Inbound Streams (MIS) (16 bits): Defines the maximum number\n\t\t *   of streams the sender of this INIT ACK chunk allows the peer end to\n\t\t *   create in this association. The value 0 MUST NOT be used.\n\t\t * - Initial TSN (I-TSN) (32 bits): Defines the TSN that the sender of the\n\t\t *   INIT ACK chunk will use initially.\n\t\t *\n\t\t * Variable-Length Parameters:\n\t\t * - State Cookie (7), mandatory.\n\t\t * - IPv4 Address (5), optional.\n\t\t * - IPv6 Address (6), optional.\n\t\t * - Unrecognized Parameter\t(8), optional.\n\t\t * - Reserved for ECN Capable (32768, 0x8000), optional.\n\t\t * - Host Name Address (11), deprecated: The receiver of an INIT chunk or an\n\t\t *   INIT ACK containing a Host Name Address parameter MUST send an ABORT\n\t\t *   chunk and MAY include an \"Unresolvable Address\" error cause.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass InitAckChunk : public AnyInitChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t InitAckChunkHeaderLength{ 20 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a InitAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic InitAckChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a InitAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic InitAckChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a InitAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic InitAckChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tInitAckChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~InitAckChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tInitAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetInitiateTag() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetInitiateTag(uint32_t value);\n\n\t\t\tuint32_t GetAdvertisedReceiverWindowCredit() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetAdvertisedReceiverWindowCredit(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfOutboundStreams() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfOutboundStreams(uint16_t value);\n\n\t\t\tuint16_t GetNumberOfInboundStreams() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 14);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfInboundStreams(uint16_t value);\n\n\t\t\tuint32_t GetInitialTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t}\n\n\t\t\tvoid SetInitialTsn(uint32_t value);\n\n\t\tprotected:\n\t\t\tInitAckChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Chunk doesn't\n\t\t\t * have variable-length value (despite the fixed header doesn't have\n\t\t\t * default length). Optional/Variable-Length Parameters and/or Error\n\t\t\t * Causes don't account here.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn InitAckChunk::InitAckChunkHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/InitChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_INIT_CHUNK_HPP\n#define MS_RTC_SCTP_INIT_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyInitChunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Initiation Chunk (INIT) (1).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 1    |  Chunk Flags  |      Chunk Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                         Initiate Tag                          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Advertised Receiver Window Credit (a_rwnd)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Number of Outbound Streams   |   Number of Inbound Streams   |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                          Initial TSN                          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /              Optional/Variable-Length Parameters              /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 1.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Initiate Tag (32 bits): The receiver of the INIT chunk (the responding\n\t\t *   end) records the value of the Initiate Tag parameter. This value MUST\n\t\t *   be placed into the Verification Tag field of every SCTP packet that\n\t\t *   the receiver of the INIT chunk transmits within this association.\n\t\t * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This value\n\t\t *   represents the dedicated buffer space, in number of bytes, the sender\n\t\t *   of the INIT chunk has reserved in association with this window. The\n\t\t *   Advertised Receiver Window Credit MUST NOT be smaller than 1500.\n\t\t * - Number of Outbound Streams (OS) (16 bits): Defines the number of\n\t\t *   outbound streams the sender of this INIT chunk wishes to create in\n\t\t *   this association. The value of 0 MUST NOT be used.\n\t\t * - Number of Inbound Streams (MIS) (16 bits): Defines the maximum number\n\t\t *   of streams the sender of this INIT chunk allows the peer end to create\n\t\t *   in this association. The value 0 MUST NOT be used.\n\t\t * - Initial TSN (I-TSN) (32 bits): Defines the TSN that the sender of the\n\t\t *   INIT chunk will use initially.\n\t\t *\n\t\t * Variable-Length Parameters:\n\t\t * - IPv4 Address (5), optional.\n\t\t * - IPv6 Address (6), optional.\n\t\t * - Cookie Preservative (9), optional.\n\t\t * - Reserved for ECN Capable (32768, 0x8000), optional.\n\t\t * - Host Name Address (11), deprecated: The receiver of an INIT chunk or an\n\t\t *   INIT ACK containing a Host Name Address parameter MUST send an ABORT\n\t\t *   chunk and MAY include an \"Unresolvable Address\" error cause.\n\t\t * - Supported Address Types (12), optional.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass InitChunk : public AnyInitChunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t InitChunkHeaderLength{ 20 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a InitChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic InitChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a InitChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic InitChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a InitChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic InitChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tInitChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~InitChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tInitChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetInitiateTag() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetInitiateTag(uint32_t value);\n\n\t\t\tuint32_t GetAdvertisedReceiverWindowCredit() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetAdvertisedReceiverWindowCredit(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfOutboundStreams() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfOutboundStreams(uint16_t value);\n\n\t\t\tuint16_t GetNumberOfInboundStreams() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 14);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfInboundStreams(uint16_t value);\n\n\t\t\tuint32_t GetInitialTsn() const final\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t}\n\n\t\t\tvoid SetInitialTsn(uint32_t value);\n\n\t\tprotected:\n\t\t\tInitChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Chunk doesn't\n\t\t\t * have variable-length value (despite the fixed header doesn't have\n\t\t\t * default length). Optional/Variable-Length Parameters and/or Error\n\t\t\t * Causes don't account here.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn InitChunk::InitChunkHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/OperationErrorChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_OPERATION_ERROR_CHUNK_HPP\n#define MS_RTC_SCTP_OPERATION_ERROR_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Operation Error Chunk (OPERATION_ERROR) (9).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 9    |  Chunk Flags  |            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                   one or more Error Causes                    /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 9.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t *\n\t\t * Optional Variable-Length Error Causes (anyone).\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass OperationErrorChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a OperationErrorChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic OperationErrorChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a OperationErrorChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic OperationErrorChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a OperationErrorChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic OperationErrorChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tOperationErrorChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~OperationErrorChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tOperationErrorChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tOperationErrorChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/ReConfigChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_RE_CONFIG_CHUNK_HPP\n#define MS_RTC_SCTP_RE_CONFIG_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Re-Config Chunk (RE_CONFIG) (130).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * | Type = 130    |  Chunk Flags  |      Chunk Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                  Re-configuration Parameter                   /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /             Re-configuration Parameter (optional)             /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 130.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - Re-configuration Parameter (variable Length): This field holds a\n\t\t *   Re-configuration Request Parameter or a Re-configuration Response\n\t\t *   Parameter.\n\t\t *\n\t\t * Variable-Length Parameters:\n\t\t * - Re-configuration Request Parameter or a Re-configuration Response\n\t\t *   Parameter, mandatory.\n\t\t * - Another Parameter, optional.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass ReConfigChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ReConfigChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic ReConfigChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ReConfigChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic ReConfigChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ReConfigChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic ReConfigChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tReConfigChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ReConfigChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tReConfigChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tReConfigChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/SackChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_SACK_CHUNK_HPP\n#define MS_RTC_SCTP_SACK_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include <ostream>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Selective Acknowledgement Chunk (SACK) (3)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 3    |  Chunk Flags  |         Chunk Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      Cumulative TSN Ack                       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Advertised Receiver Window Credit (a_rwnd)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * | Number of Gap Ack Blocks = N  |  Number of Duplicate TSNs = M |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |    Gap Ack Block #1 Start     |     Gap Ack Block #1 End      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                                                               /\n\t\t * \\                              ...                              \\\n\t\t * /                                                               /\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |    Gap Ack Block #N Start     |     Gap Ack Block #N End      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                        Duplicate TSN 1                        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                                                               /\n\t\t * \\                              ...                              \\\n\t\t * /                                                               /\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                        Duplicate TSN M                        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 3.\n\t\t * - Res (4 bits): All set to 0.\n\t\t * - Length (16 bits).\n\t\t * - Cumulative TSN Ack (32 bits): The largest TSN, such that all TSNs\n\t\t *   smaller than or equal to it have been received and the next one has\n\t\t *   not been received. In the case where no DATA chunk has been received,\n\t\t *   this value is set to the peer's Initial TSN minus one.\n\t\t * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This field\n\t\t *   indicates the updated receive buffer space in bytes of the sender of\n\t\t *   this SACK chunk.\n\t\t * - Number of Gap Ack Blocks (16 bits): Indicates the number of Gap Ack\n\t\t *   Blocks included in this SACK chunk.\n\t\t * - Number of Duplicate TSNs (16 bit): This field contains the number of\n\t\t *   duplicate TSNs the endpoint has received. Each duplicate TSN is listed\n\t\t *   following the Gap Ack Block list.\n\t\t * - Gap Ack Blocks: These fields contain the Gap Ack Blocks. They are\n\t\t *   repeated for each Gap Ack Block up to the number of Gap Ack Blocks\n\t\t *   defined in the Number of Gap Ack Blocks field.\n\t\t * - Gap Ack Block Start (16 bits): Indicates the Start offset TSN for this\n\t\t *   Gap Ack Block.\n\t\t * - Gap Ack Block End (16 bits): Indicates the End offset TSN for this Gap\n\t\t *   Ack Block.\n\t\t * - Duplicate TSN (32 bits): Indicates the number of times a TSN was\n\t\t *   received in duplicate since the last SACK chunk was sent.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass SackChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstruct GapAckBlock\n\t\t\t{\n\t\t\t\tGapAckBlock(uint16_t start, uint16_t end) : start(start), end(end)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tGapAckBlock() = default;\n\n\t\t\t\tuint16_t start;\n\t\t\t\tuint16_t end;\n\n\t\t\t\tbool operator==(const GapAckBlock& other) const\n\t\t\t\t{\n\t\t\t\t\treturn start == other.start && end == other.end;\n\t\t\t\t}\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t SackChunkHeaderLength{ 16 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a SackChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic SackChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a SackChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic SackChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a SackChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic SackChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tSackChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~SackChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tSackChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetCumulativeTsnAck() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetCumulativeTsnAck(uint32_t tsn);\n\n\t\t\tuint32_t GetAdvertisedReceiverWindowCredit() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetAdvertisedReceiverWindowCredit(uint32_t aRwnd);\n\n\t\t\tstd::vector<SackChunk::GapAckBlock> GetGapAckBlocks() const;\n\n\t\t\tstd::vector<GapAckBlock> GetValidatedGapAckBlocks() const;\n\n\t\t\tstd::vector<uint32_t> GetDuplicateTsns() const;\n\n\t\t\tvoid AddAckBlock(uint16_t start, uint16_t end);\n\n\t\t\tvoid AddAckBlock(GapAckBlock gapAckBlock)\n\t\t\t{\n\t\t\t\tAddAckBlock(gapAckBlock.start, gapAckBlock.end);\n\t\t\t}\n\n\t\t\tvoid AddDuplicateTsn(uint32_t tsn);\n\n\t\tprotected:\n\t\t\tSackChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn SackChunk::SackChunkHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid SetNumberOfGapAckBlocks(uint16_t value);\n\n\t\t\tvoid SetNumberOfDuplicateTsns(uint16_t value);\n\n\t\t\tuint8_t* GetAckBlocksPointer() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValuePointer();\n\t\t\t}\n\n\t\t\tuint8_t* GetDuplicateTsnsPointer() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValuePointer() + (GetNumberOfGapAckBlocks() * 4);\n\t\t\t}\n\n\t\t\tuint16_t GetNumberOfGapAckBlocks() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\tuint16_t GetAckBlockStartAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetAckBlocksPointer(), (idx * 4));\n\t\t\t}\n\n\t\t\tuint16_t GetAckBlockEndAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetAckBlocksPointer(), (idx * 4) + 2);\n\t\t\t}\n\n\t\t\tuint16_t GetNumberOfDuplicateTsns() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 14);\n\t\t\t}\n\n\t\t\tuint32_t GetDuplicateTsnAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetDuplicateTsnsPointer(), (idx * 4));\n\t\t\t}\n\n\t\t\tbool ValidateGapAckBlocks() const;\n\t\t};\n\n\t\t/**\n\t\t * For logging purposes in Catch2 tests.\n\t\t */\n\t\tinline std::ostream& operator<<(std::ostream& os, const SackChunk::GapAckBlock& s)\n\t\t{\n\t\t\treturn os << \"{start:\" << s.start << \", end:\" << s.end << \"}\";\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_SHUTDOWN_ACK_CHUNK_HPP\n#define MS_RTC_SCTP_SHUTDOWN_ACK_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Shutdown Acknowledgement Chunk (SHUTDOWN_ACK) (8).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 8    |  Chunk Flags  |          Length = 4           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 8.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits): 4.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass ShutdownAckChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ShutdownAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic ShutdownAckChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ShutdownAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic ShutdownAckChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ShutdownAckChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic ShutdownAckChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tShutdownAckChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ShutdownAckChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tShutdownAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\tprotected:\n\t\t\tShutdownAckChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/ShutdownChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_SHUTDOWN_CHUNK_HPP\n#define MS_RTC_SCTP_SHUTDOWN_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Shutdown Association Chunk (SHUTDOWN) (7).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 7    |  Chunk Flags  |          Length = 8           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                      Cumulative TSN Ack                       |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 7.\n\t\t * - Flags (8 bits): All set to 0.\n\t\t * - Length (16 bits): 8.\n\t\t * - Cumulative TSN Ack (32 bits): The largest TSN, such that all TSNs\n\t\t *   smaller than or equal to it have been received and the next one has\n\t\t *   not been received.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass ShutdownChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\tstatic const size_t ShutdownChunkHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ShutdownChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic ShutdownChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ShutdownChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic ShutdownChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ShutdownChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic ShutdownChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tShutdownChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ShutdownChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tShutdownChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint32_t GetCumulativeTsnAck() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetCumulativeTsnAck(uint32_t value);\n\n\t\tprotected:\n\t\t\tShutdownChunk* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Chunk doesn't\n\t\t\t * have variable-length value (despite the fixed header doesn't have\n\t\t\t * default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn ShutdownChunk::ShutdownChunkHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_SHUTDOWN_COMPLETE_CHUNK_HPP\n#define MS_RTC_SCTP_SHUTDOWN_COMPLETE_CHUNK_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Shutdown Complete Chunk (SHUTDOWN_COMPLETE) (14).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |   Type = 14   |  Reserved   |T|          Length = 4           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t *\n\t\t * - Chunk Type (8 bits): 14\n\t\t * - T bit (1 bit): The T bit is set to 0 if the sender filled in the\n\t\t *   Verification Tag expected by the peer. If the Verification Tag is\n\t\t *   reflected, the T bit MUST be set to 1. Reflecting means that the sent\n\t\t *   Verification Tag is the same as the received one.\n\t\t * - Length (16 bits): 4.\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass ShutdownCompleteChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ShutdownCompleteChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic ShutdownCompleteChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ShutdownCompleteChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Chunk real length.\n\t\t\t */\n\t\t\tstatic ShutdownCompleteChunk* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ShutdownCompleteChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic ShutdownCompleteChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tShutdownCompleteChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ShutdownCompleteChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tShutdownCompleteChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool GetT() const\n\t\t\t{\n\t\t\t\treturn GetBit0();\n\t\t\t}\n\n\t\t\tvoid SetT(bool flag);\n\n\t\tprotected:\n\t\t\tShutdownCompleteChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/chunks/UnknownChunk.hpp",
    "content": "#ifndef MS_RTC_SCTP_DATA_UNKNOWN_HPP\n#define MS_RTC_SCTP_DATA_UNKNOWN_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unknown Chunk (UNKNOWN).\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Chunk Type   |  Chunk Flags  |         Chunk Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                          Unknown Value                        /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Packet;\n\n\t\tclass UnknownChunk : public Chunk\n\t\t{\n\t\t\t// We need that Packet calls protected and private methods in this class.\n\t\t\tfriend class Packet;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnknownChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Chunk.\n\t\t\t */\n\t\t\tstatic UnknownChunk* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnknownChunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Packet::Parse()`.\n\t\t\t */\n\t\t\tstatic UnknownChunk* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnknownChunk(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnknownChunk() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnknownChunk* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool CanHaveParameters() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool CanHaveErrorCauses() const final\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tbool HasUnknownType() const override\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool HasUnknownValue() const\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnknownValue() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnknownValueLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\tprotected:\n\t\t\tUnknownChunk* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_COOKIE_RECEIVED_WHILE_SHUTTING_DOWN_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_COOKIE_RECEIVED_WHILE_SHUTTING_DOWN_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Cookie Received While Shutting Down Error Cause\n\t\t * (COOKIE_RECEIVED_WHILE_SHUTTING_DOWN) (10)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 10        |       Cause Length = 4        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass CookieReceivedWhileShuttingDownErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a CookieReceivedWhileShuttingDownErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic CookieReceivedWhileShuttingDownErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a CookieReceivedWhileShuttingDownErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic CookieReceivedWhileShuttingDownErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a CookieReceivedWhileShuttingDownErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic CookieReceivedWhileShuttingDownErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tCookieReceivedWhileShuttingDownErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~CookieReceivedWhileShuttingDownErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tCookieReceivedWhileShuttingDownErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\tprotected:\n\t\t\tCookieReceivedWhileShuttingDownErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_OUT_OF_INVALID_MANDATORY_PARAMETER_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_OUT_OF_INVALID_MANDATORY_PARAMETER_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Invalid Mandatory Parameter Error Cause\n\t\t * (INVALID_MANDATORY_PARAMETER) (7)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 7         |       Cause Length = 4        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass InvalidMandatoryParameterErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a InvalidMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic InvalidMandatoryParameterErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a InvalidMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic InvalidMandatoryParameterErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a InvalidMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic InvalidMandatoryParameterErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tInvalidMandatoryParameterErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~InvalidMandatoryParameterErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tInvalidMandatoryParameterErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\tprotected:\n\t\t\tInvalidMandatoryParameterErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_INVALID_STREAM_IDENTIFIER_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_INVALID_STREAM_IDENTIFIER_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Invalid Stream Identifier Error Cause (INVALID_STREAM_IDENTIFIER)\n\t\t * (1)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 1         |       Cause Length = 8        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |       Stream Identifier       |          (Reserved)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass InvalidStreamIdentifierErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t InvalidStreamIdentifierErrorCauseHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a InvalidStreamIdentifierErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic InvalidStreamIdentifierErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a InvalidStreamIdentifierErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic InvalidStreamIdentifierErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a InvalidStreamIdentifierErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic InvalidStreamIdentifierErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tInvalidStreamIdentifierErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~InvalidStreamIdentifierErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tInvalidStreamIdentifierErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint16_t GetStreamIdentifier() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetStreamIdentifier(uint16_t value);\n\n\t\tprotected:\n\t\t\tInvalidStreamIdentifierErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Error Cause\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength;\n\t\t\t}\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\n\t\tprivate:\n\t\t\tvoid SetReserved();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_MISSING_MANDATORY_PARAMETER_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_MISSING_MANDATORY_PARAMETER_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Missing Mandatory Parameter Error Cause\n\t\t * (MISSING_MANDATORY_PARAMETER) (2)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 2         |   Cause Length = 8 + N * 2    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                 Number of missing params = N                  |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Missing Param Type #1     |     Missing Param Type #2     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |    Missing Param Type #N-1    |     Missing Param Type #N     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass MissingMandatoryParameterErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t MissingMandatoryParameterErrorCauseHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a MissingMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic MissingMandatoryParameterErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a MissingMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic MissingMandatoryParameterErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a MissingMandatoryParameterErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic MissingMandatoryParameterErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tMissingMandatoryParameterErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~MissingMandatoryParameterErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tMissingMandatoryParameterErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetNumberOfMissingParameters() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tParameter::ParameterType GetMissingParameterTypeAt(uint32_t idx) const\n\t\t\t{\n\t\t\t\treturn static_cast<Parameter::ParameterType>(\n\t\t\t\t  Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2)));\n\t\t\t}\n\n\t\t\tvoid AddMissingParameterType(Parameter::ParameterType parameterType);\n\n\t\tprotected:\n\t\t\tMissingMandatoryParameterErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Error Cause\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength;\n\t\t\t}\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\n\t\tprivate:\n\t\t\tvoid SetNumberOfMissingParameters(uint32_t value);\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_NO_USER_DATA_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_NO_USER_DATA_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP No User Data Error Cause (NO_USER_DATA) (9)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 9         |       Cause Length = 8        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                              TSN                              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass NoUserDataErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t NoUserDataErrorCauseHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a NoUserDataErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic NoUserDataErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a NoUserDataErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic NoUserDataErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a NoUserDataErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic NoUserDataErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tNoUserDataErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~NoUserDataErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tNoUserDataErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetTsn() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetTsn(uint32_t value);\n\n\t\tprotected:\n\t\t\tNoUserDataErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Error Cause\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength;\n\t\t\t}\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_OUT_OF_RESOURCE_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_OUT_OF_RESOURCE_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Out of Resource Error Cause (OUT_OF_RESOURCE) (4)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 4         |       Cause Length = 4        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass OutOfResourceErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a OutOfResourceErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic OutOfResourceErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a OutOfResourceErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic OutOfResourceErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a OutOfResourceErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic OutOfResourceErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tOutOfResourceErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~OutOfResourceErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tOutOfResourceErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\tprotected:\n\t\t\tOutOfResourceErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_PROTOCOL_VIOLATION_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_PROTOCOL_VIOLATION_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Protocol Violation Error Cause (PROTOCOL_VIOLATION) (13)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 13        |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                    Additional Information                     /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass ProtocolViolationErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ProtocolViolationErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic ProtocolViolationErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ProtocolViolationErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic ProtocolViolationErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ProtocolViolationErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic ProtocolViolationErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tProtocolViolationErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ProtocolViolationErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tProtocolViolationErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasAdditionalInformation() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetAdditionalInformation() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetAdditionalInformationLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetAdditionalInformation(const uint8_t* info, uint16_t infoLength);\n\n\t\t\tvoid SetAdditionalInformation(const std::string& info);\n\n\t\tprotected:\n\t\t\tProtocolViolationErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Restart of an Association with New Addresses Error Cause\n\t\t * (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES) (11)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 11        |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                       New Address TLVs                        /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass RestartOfAnAssociationWithNewAddressesErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a RestartOfAnAssociationWithNewAddressesErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic RestartOfAnAssociationWithNewAddressesErrorCause* Parse(\n\t\t\t  const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a RestartOfAnAssociationWithNewAddressesErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic RestartOfAnAssociationWithNewAddressesErrorCause* Factory(\n\t\t\t  uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a RestartOfAnAssociationWithNewAddressesErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic RestartOfAnAssociationWithNewAddressesErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tRestartOfAnAssociationWithNewAddressesErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~RestartOfAnAssociationWithNewAddressesErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tRestartOfAnAssociationWithNewAddressesErrorCause* Clone(\n\t\t\t  uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasNewAddressTlvs() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetNewAddressTlvs() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetNewAddressTlvsLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetNewAddressTlvs(const uint8_t* tlvs, uint16_t tlvsLength);\n\n\t\tprotected:\n\t\t\tRestartOfAnAssociationWithNewAddressesErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_STALE_COOKIE_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_STALE_COOKIE_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Stale Cookie Error Cause (STALE_COOKIE) (3)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 3         |       Cause Length = 8        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                 Measure of Staleness (usec.)                  |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass StaleCookieErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t StaleCookieErrorCauseHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a StaleCookieErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic StaleCookieErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a StaleCookieErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic StaleCookieErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a StaleCookieErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic StaleCookieErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tStaleCookieErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~StaleCookieErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tStaleCookieErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetMeasureOfStaleness() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetMeasureOfStaleness(uint32_t value);\n\n\t\tprotected:\n\t\t\tStaleCookieErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Error Cause\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength;\n\t\t\t}\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNKNOWN_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_UNKNOWN_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unknown Error Cause (UNKNOWN)\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Cause Code           |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                         Unknown Value                         /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnknownErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnknownErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic UnknownErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnknownErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic UnknownErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnknownErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnknownErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnknownErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool HasUnknownCode() const override\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvirtual bool HasUnknownValue() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnknownValue() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnknownValueLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\tprotected:\n\t\t\tUnknownErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNRECOGNIZED_CHUNK_TYPE_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_UNRECOGNIZED_CHUNK_TYPE_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unrecognized Chunk Type Error Cause (UNRECOGNIZED_CHUNK_TYPE) (6)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 6         |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                      Unrecognized Chunk                       /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnrecognizedChunkTypeErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedChunkTypeErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic UnrecognizedChunkTypeErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a UnrecognizedChunkTypeErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic UnrecognizedChunkTypeErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedChunkTypeErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic UnrecognizedChunkTypeErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnrecognizedChunkTypeErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnrecognizedChunkTypeErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnrecognizedChunkTypeErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasUnrecognizedChunk() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnrecognizedChunk() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnrecognizedChunkLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUnrecognizedChunk(const uint8_t* chunk, uint16_t chunkLength);\n\n\t\tprotected:\n\t\t\tUnrecognizedChunkTypeErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNRECOGNIZED_PARAMETERS_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_UNRECOGNIZED_PARAMETERS_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unrecognized Parameters Error Cause (UNRECOGNIZED_PARAMETERS) (8)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 8         |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                    Unrecognized Parameters                    /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnrecognizedParametersErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedParametersErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic UnrecognizedParametersErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a UnrecognizedParametersErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic UnrecognizedParametersErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedParametersErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic UnrecognizedParametersErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnrecognizedParametersErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnrecognizedParametersErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnrecognizedParametersErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasUnrecognizedParameters() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnrecognizedParameters() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnrecognizedParametersLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUnrecognizedParameters(const uint8_t* parameters, uint16_t parametersLength);\n\n\t\tprotected:\n\t\t\tUnrecognizedParametersErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNRESOLVABLE_ADDRESS_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_UNRESOLVABLE_ADDRESS_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unresolvable Address Error Cause (UNRESOLVABLE_ADDRESS) (5)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 5         |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                     Unresolvable Address                      /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnresolvableAddressErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnresolvableAddressErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic UnresolvableAddressErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a UnresolvableAddressErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic UnresolvableAddressErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnresolvableAddressErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic UnresolvableAddressErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnresolvableAddressErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnresolvableAddressErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnresolvableAddressErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasUnresolvableAddress() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnresolvableAddress() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnresolvableAddressLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUnresolvableAddress(const uint8_t* address, uint16_t addressLength);\n\n\t\tprotected:\n\t\t\tUnresolvableAddressErrorCause* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp",
    "content": "#ifndef MS_RTC_SCTP_USER_INITIATED_ABORT_ERROR_CAUSE_HPP\n#define MS_RTC_SCTP_USER_INITIATED_ABORT_ERROR_CAUSE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP User-Initiated Abort Error Cause (USER_INITIATED_ABORT) (12)\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Cause Code = 12        |         Cause Length          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                   Upper Layer Abort Reason                    /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UserInitiatedAbortErrorCause : public ErrorCause\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UserInitiatedAbortErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Error Cause.\n\t\t\t */\n\t\t\tstatic UserInitiatedAbortErrorCause* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a UserInitiatedAbortErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Error Cause real length.\n\t\t\t */\n\t\t\tstatic UserInitiatedAbortErrorCause* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UserInitiatedAbortErrorCause.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseErrorCauses()`.\n\t\t\t */\n\t\t\tstatic UserInitiatedAbortErrorCause* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUserInitiatedAbortErrorCause(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UserInitiatedAbortErrorCause() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUserInitiatedAbortErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasUpperLayerAbortReason() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst std::string_view GetUpperLayerAbortReason() const\n\t\t\t{\n\t\t\t\tconst auto* value = GetVariableLengthValue();\n\n\t\t\t\tif (!value)\n\t\t\t\t{\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\n\t\t\t\treturn std::string_view(reinterpret_cast<const char*>(value), GetVariableLengthValueLength());\n\t\t\t}\n\n\t\t\tvoid SetUpperLayerAbortReason(const std::string_view& reason);\n\n\t\tprotected:\n\t\t\tUserInitiatedAbortErrorCause* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\tvirtual const std::string ContentToString() const override final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_HPP\n#define MS_RTC_SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Add Incoming Streams Request Parameter\n\t\t * (ADD_INCOMING_STREAMS_REQUEST) (18).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 18       |      Parameter Length = 12    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Re-configuration Request Sequence Number             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |      Number of new streams    |        (Reserved)             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass AddIncomingStreamsRequestParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t AddIncomingStreamsRequestParameterHeaderLength{ 12 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a AddIncomingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic AddIncomingStreamsRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a AddIncomingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic AddIncomingStreamsRequestParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a AddIncomingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic AddIncomingStreamsRequestParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tAddIncomingStreamsRequestParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~AddIncomingStreamsRequestParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tAddIncomingStreamsRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationRequestSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationRequestSequenceNumber(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfNewStreams() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfNewStreams(uint16_t value);\n\n\t\tprotected:\n\t\t\tAddIncomingStreamsRequestParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid SetReserved();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_HPP\n#define MS_RTC_SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Add Outgoing Streams Request Parameter\n\t\t * (ADD_OUTGOING_STREAMS_REQUEST) (17).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 17       |      Parameter Length = 12    |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Re-configuration Request Sequence Number             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |      Number of new streams    |        (Reserved)             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass AddOutgoingStreamsRequestParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t AddOutgoingStreamsRequestParameterHeaderLength{ 12 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a AddOutgoingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic AddOutgoingStreamsRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a AddOutgoingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic AddOutgoingStreamsRequestParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a AddOutgoingStreamsRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic AddOutgoingStreamsRequestParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tAddOutgoingStreamsRequestParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~AddOutgoingStreamsRequestParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tAddOutgoingStreamsRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationRequestSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationRequestSequenceNumber(uint32_t value);\n\n\t\t\tuint16_t GetNumberOfNewStreams() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetNumberOfNewStreams(uint16_t value);\n\n\t\tprotected:\n\t\t\tAddOutgoingStreamsRequestParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid SetReserved();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_COOKIE_PRESERVATIVE_PARAMETER_HPP\n#define MS_RTC_SCTP_COOKIE_PRESERVATIVE_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Cookie Preservative Parameter (COOKIE_PRESERVATIVE) (9).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 9            |          Length = 8           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |         Suggested Cookie Life-Span Increment (msec.)          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass CookiePreservativeParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t CookiePreservativeParameterHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a CookiePreservativeParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic CookiePreservativeParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a CookiePreservativeParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic CookiePreservativeParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a CookiePreservativeParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic CookiePreservativeParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tCookiePreservativeParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~CookiePreservativeParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tCookiePreservativeParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetLifeSpanIncrement() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetLifeSpanIncrement(uint32_t increment);\n\n\t\tprotected:\n\t\t\tCookiePreservativeParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn CookiePreservativeParameter::CookiePreservativeParameterHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_FORWARD_TSN_SUPPORTED_PARAMETER_HPP\n#define MS_RTC_SCTP_FORWARD_TSN_SUPPORTED_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Forward-TSN-Supported Parameter (FORWARD_TSN_SUPPORTED) (49152).\n\t\t *\n\t\t * @see RFC 3758.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |    Parameter Type = 49152     |  Parameter Length = 4         |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass ForwardTsnSupportedParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ForwardTsnSupportedParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic ForwardTsnSupportedParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ForwardTsnSupportedParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic ForwardTsnSupportedParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ForwardTsnSupportedParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic ForwardTsnSupportedParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tForwardTsnSupportedParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ForwardTsnSupportedParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tForwardTsnSupportedParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\tprotected:\n\t\t\tForwardTsnSupportedParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_HEARTBEAT_INFO_PARAMETER_HPP\n#define MS_RTC_SCTP_HEARTBEAT_INFO_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP HeartbeatInfo Parameter (HEARBEAT_INFO) (1).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 1            |        HB Info Length         |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                Sender-Specific Heartbeat Info                 /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass HeartbeatInfoParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatInfoParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic HeartbeatInfoParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a HeartbeatInfoParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic HeartbeatInfoParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a HeartbeatInfoParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic HeartbeatInfoParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tHeartbeatInfoParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~HeartbeatInfoParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tHeartbeatInfoParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasInfo() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetInfo() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetInfoLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetInfo(const uint8_t* info, uint16_t infoLength);\n\n\t\tprotected:\n\t\t\tHeartbeatInfoParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_IPV4_ADDRESS_PARAMETER_HPP\n#define MS_RTC_SCTP_IPV4_ADDRESS_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP IPv4 Adress Parameter (IPV4 ADDRESS) (5).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 5            |          Length = 8           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                         IPv4 Address                          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass IPv4AddressParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t IPv4AddressParameterHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a IPv4AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic IPv4AddressParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a IPv4AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic IPv4AddressParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a IPv4AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic IPv4AddressParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tIPv4AddressParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~IPv4AddressParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tIPv4AddressParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\t/**\n\t\t\t * @return A pointer to a 4 bytes unsigned integer in network order\n\t\t\t * representing the binary encoded IPv4 value.\n\t\t\t */\n\t\t\tconst uint8_t* GetIPv4Address() const\n\t\t\t{\n\t\t\t\treturn GetBuffer() + 4;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param ip - A pointer to a 4 bytes unsigned integer in network order\n\t\t\t * representing the binary encoded IPv4 value.\n\t\t\t */\n\t\t\tvoid SetIPv4Address(const uint8_t* ip);\n\n\t\tprotected:\n\t\t\tIPv4AddressParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn IPv4AddressParameter::IPv4AddressParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid ResetIPv4Address();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_IPV6_ADDRESS_PARAMETER_HPP\n#define MS_RTC_SCTP_IPV6_ADDRESS_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP IPv6 Adress Parameter (IPV6_ADDRESS) (6).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 6            |          Length = 20          |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                                                               |\n\t\t * |                         IPv6 Address                          |\n\t\t * |                                                               |\n\t\t * |                                                               |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass IPv6AddressParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t IPv6AddressParameterHeaderLength{ 20 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a IPv6AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic IPv6AddressParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a IPv6AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic IPv6AddressParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a IPv6AddressParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic IPv6AddressParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tIPv6AddressParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~IPv6AddressParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tIPv6AddressParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\t/**\n\t\t\t * @return A pointer to a 16 bytes unsigned integer in network order\n\t\t\t * representing the binary encoded IPv6 value.\n\t\t\t */\n\t\t\tconst uint8_t* GetIPv6Address() const\n\t\t\t{\n\t\t\t\treturn GetBuffer() + 4;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param ip - A pointer to a 16 bytes unsigned integer in network order\n\t\t\t * representing the binary encoded IPv6 value.\n\t\t\t */\n\t\t\tvoid SetIPv6Address(const uint8_t* ip);\n\n\t\tprotected:\n\t\t\tIPv6AddressParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn IPv6AddressParameter::IPv6AddressParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tvoid ResetIPv6Address();\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_HPP\n#define MS_RTC_SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Incoming SSN Reset Request Parameter (INCOMING_SSN_RESET_REQUEST)\n\t\t * (14).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 14       |  Parameter Length = 8 + 2 * N |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Re-configuration Request Sequence Number             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Stream Number 1 (optional)   |    Stream Number 2 (optional) |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                            ......                             /\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Stream Number N-1 (optional) |    Stream Number N (optional) |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass IncomingSsnResetRequestParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t IncomingSsnResetRequestParameterHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a IncomingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic IncomingSsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a IncomingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic IncomingSsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a IncomingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic IncomingSsnResetRequestParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tIncomingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~IncomingSsnResetRequestParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tIncomingSsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationRequestSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationRequestSequenceNumber(uint32_t value);\n\n\t\t\tstd::vector<uint16_t> GetStreamIds() const;\n\n\t\t\tvoid AddStreamId(uint16_t streamId);\n\n\t\tprotected:\n\t\t\tIncomingSsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t GetNumberOfStreams() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() / 2;\n\t\t\t}\n\n\t\t\tuint16_t GetStreamAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2));\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_HPP\n#define MS_RTC_SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Outgoing SSN Reset Request Parameter (OUTGOING_SSN_RESET_REQUEST)\n\t\t * (13).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 13       | Parameter Length = 16 + 2 * N |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Re-configuration Request Sequence Number            |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Re-configuration Response Sequence Number           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                Sender's Last Assigned TSN                     |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Stream Number 1 (optional)   |    Stream Number 2 (optional) |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                            ......                             /\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |  Stream Number N-1 (optional) |    Stream Number N (optional) |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass OutgoingSsnResetRequestParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t OutgoingSsnResetRequestParameterHeaderLength{ 16 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a OutgoingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic OutgoingSsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a OutgoingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic OutgoingSsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a OutgoingSsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic OutgoingSsnResetRequestParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tOutgoingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~OutgoingSsnResetRequestParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tOutgoingSsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationRequestSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationRequestSequenceNumber(uint32_t value);\n\n\t\t\tuint32_t GetReconfigurationResponseSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 8);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationResponseSequenceNumber(uint32_t value);\n\n\t\t\tuint32_t GetSenderLastAssignedTsn() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\tvoid SetSenderLastAssignedTsn(uint32_t value);\n\n\t\t\tstd::vector<uint16_t> GetStreamIds() const;\n\n\t\t\tvoid AddStreamId(uint16_t streamId);\n\n\t\tprotected:\n\t\t\tOutgoingSsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t GetNumberOfStreams() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() / 2;\n\t\t\t}\n\n\t\t\tuint16_t GetStreamAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2));\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_RECONFIGURATION_RESPONSE_PARAMETER_HPP\n#define MS_RTC_SCTP_RECONFIGURATION_RESPONSE_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Re-configuration Response Parameter (RECONFIGURATION_RESPONSE)\n\t\t * (16).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 16       |      Parameter Length         |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |         Re-configuration Response Sequence Number             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                            Result                             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                   Sender's Next TSN (optional)                |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                  Receiver's Next TSN (optional)               |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass ReconfigurationResponseParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t// NOLINTNEXTLINE(performance-enum-siz)\n\t\t\tenum class Result : uint32_t\n\t\t\t{\n\t\t\t\tSUCCESS_NOTHING_TO_DO             = 0x00000000,\n\t\t\t\tSUCCESS_PERFORMED                 = 0x00000001,\n\t\t\t\tDENIED                            = 0x00000002,\n\t\t\t\tERROR_WRONG_SSN                   = 0x00000003,\n\t\t\t\tERROR_REQUEST_ALREADY_IN_PROGRESS = 0x00000004,\n\t\t\t\tERROR_BAD_SEQUENCE_NUMBER         = 0x00000005,\n\t\t\t\tIN_PROGRESS                       = 0x00000006,\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t ReconfigurationResponseParameterHeaderLength{ 12 };\n\t\t\tstatic const size_t ReconfigurationResponseParameterHeaderLengthWithOptionalFields{ 20 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ReconfigurationResponseParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic ReconfigurationResponseParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ReconfigurationResponseParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic ReconfigurationResponseParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\t\tstatic const std::string& ResultToString(Result result);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ReconfigurationResponseParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic ReconfigurationResponseParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\tstatic const std::unordered_map<Result, std::string> Result2String;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tReconfigurationResponseParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ReconfigurationResponseParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tReconfigurationResponseParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationResponseSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationResponseSequenceNumber(uint32_t value);\n\n\t\t\tResult GetResult() const\n\t\t\t{\n\t\t\t\treturn static_cast<Result>(Utils::Byte::Get4Bytes(GetBuffer(), 8));\n\t\t\t}\n\n\t\t\tvoid SetResult(Result result);\n\n\t\t\tbool HasNextTsns() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() ==\n\t\t\t\t       ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields -\n\t\t\t\t         ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength;\n\t\t\t}\n\n\t\t\tuint32_t GetSenderNextTsn() const\n\t\t\t{\n\t\t\t\tif (!HasNextTsns())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 12);\n\t\t\t}\n\n\t\t\tuint32_t GetReceiverNextTsn() const\n\t\t\t{\n\t\t\t\tif (!HasNextTsns())\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 16);\n\t\t\t}\n\n\t\t\tvoid SetNextTsns(uint32_t senderNextTsn, uint32_t receiverNextTsn);\n\n\t\tprotected:\n\t\t\tReconfigurationResponseParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We need to override this method since this Chunk has a variable-length\n\t\t\t * value and the fixed header doesn't have default length.\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_HPP\n#define MS_RTC_SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Zero Checksum Acceptable Parameter (SSN_TSN_RESET_REQUEST)\n\t\t * (15).\n\t\t *\n\t\t * @see RFC 6525.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Type = 0x000F        |          Length = 8           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |         Re-configuration Request Sequence Number              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass SsnTsnResetRequestParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\tstatic const size_t SsnTsnResetRequestParameterHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a SsnTsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic SsnTsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a SsnTsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic SsnTsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a SsnTsnResetRequestParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic SsnTsnResetRequestParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tSsnTsnResetRequestParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~SsnTsnResetRequestParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tSsnTsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint32_t GetReconfigurationRequestSequenceNumber() const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\t\t\t}\n\n\t\t\tvoid SetReconfigurationRequestSequenceNumber(uint32_t value);\n\n\t\tprotected:\n\t\t\tSsnTsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/StateCookieParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_STATE_COOKIE_PARAMETER_HPP\n#define MS_RTC_SCTP_STATE_COOKIE_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP State Cookie Parameter (STATE_COOKIE) (7).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 7            |            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                            Cookie                             /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass StateCookieParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a StateCookieParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic StateCookieParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a StateCookieParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic StateCookieParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a StateCookieParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic StateCookieParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tStateCookieParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~StateCookieParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tStateCookieParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasCookie() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetCookie() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetCookieLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetCookie(const uint8_t* cookie, uint16_t cookieLength);\n\n\t\t\t/**\n\t\t\t * Write a locally generated StateCookie in place within the Cookie\n\t\t\t * field.\n\t\t\t *\n\t\t\t * This method is more performant than SetCookie() since it doesn't\n\t\t\t * require neither the allocation of a StateCookie class instance nor a\n\t\t\t * copy of its buffer to the StateCookieParameter.\n\t\t\t */\n\t\t\tvoid WriteStateCookieInPlace(\n\t\t\t  uint32_t localVerificationTag,\n\t\t\t  uint32_t remoteVerificationTag,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  uint64_t tieTag,\n\t\t\t  const NegotiatedCapabilities& negotiatedCapabilities);\n\n\t\tprotected:\n\t\t\tStateCookieParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_HPP\n#define MS_RTC_SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Supported Address Types Parameter (SUPPORTED_ADDRESS_TYPES) (12).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 12           |            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Address Type #1        |        Address Type #2        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                            ......                             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass SupportedAddressTypesParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a SupportedAddressTypesParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic SupportedAddressTypesParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a SupportedAddressTypesParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic SupportedAddressTypesParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a SupportedAddressTypesParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic SupportedAddressTypesParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tSupportedAddressTypesParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~SupportedAddressTypesParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tSupportedAddressTypesParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint16_t GetNumberOfAddressTypes() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength() / 2;\n\t\t\t}\n\n\t\t\tuint16_t GetAddressTypeAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2));\n\t\t\t}\n\n\t\t\tvoid AddAddressType(uint16_t addressType);\n\n\t\tprotected:\n\t\t\tSupportedAddressTypesParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_SUPPORTED_EXTENSIONS_PARAMETER_HPP\n#define MS_RTC_SCTP_SUPPORTED_EXTENSIONS_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Supported Extensions Parameter (SUPPORTED_EXTENSIONS) (32776).\n\t\t *\n\t\t * @see RFC 5061.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |     Parameter Type = 0x8008   |      Parameter Length         |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * | CHUNK TYPE 1  |  CHUNK TYPE 2 |  CHUNK TYPE 3 |  CHUNK TYPE 4 |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |                             ....                              |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * | CHUNK TYPE N  |      PAD      |      PAD      |      PAD      |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass SupportedExtensionsParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a SupportedExtensionsParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic SupportedExtensionsParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a SupportedExtensionsParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic SupportedExtensionsParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a SupportedExtensionsParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic SupportedExtensionsParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tSupportedExtensionsParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~SupportedExtensionsParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tSupportedExtensionsParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tuint16_t GetNumberOfChunkTypes() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tChunk::ChunkType GetChunkTypeAt(uint16_t idx) const\n\t\t\t{\n\t\t\t\treturn static_cast<Chunk::ChunkType>(\n\t\t\t\t  Utils::Byte::Get1Byte(GetVariableLengthValuePointer(), idx));\n\t\t\t}\n\n\t\t\tbool IncludesChunkType(Chunk::ChunkType chunkType) const\n\t\t\t{\n\t\t\t\tfor (size_t idx{ 0 }; idx < GetNumberOfChunkTypes(); ++idx)\n\t\t\t\t{\n\t\t\t\t\tif (chunkType == GetChunkTypeAt(idx))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvoid AddChunkType(Chunk::ChunkType chunkType);\n\n\t\tprotected:\n\t\t\tSupportedExtensionsParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/UnknownParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNKNOWN_PARAMETER_HPP\n#define MS_RTC_SCTP_UNKNOWN_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unknown Parameter (UNKNOWN).\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |        Parameter Type         |       Parameter Length        |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * \\                                                               \\\n\t\t * /                         Unknown Value                         /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnknownParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnknownParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic UnknownParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnknownParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic UnknownParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse() and ParseStrict() static methods.\n\t\t\t */\n\t\t\tUnknownParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnknownParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnknownParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tbool HasUnknownType() const override\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvirtual bool HasUnknownValue() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnknownValue() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnknownValueLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\tprotected:\n\t\t\tUnknownParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_HPP\n#define MS_RTC_SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Unrecognized Parameter Parameter (UNRECOGNIZED_PARAMETER) (7).\n\t\t *\n\t\t * @see RFC 9260.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Type = 8            |            Length             |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * /                    Unrecognized Parameter                     /\n\t\t * \\                                                               \\\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass UnrecognizedParameterParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedParameterParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic UnrecognizedParameterParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a UnrecognizedParameterParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic UnrecognizedParameterParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a UnrecognizedParameterParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic UnrecognizedParameterParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tUnrecognizedParameterParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~UnrecognizedParameterParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tUnrecognizedParameterParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tvirtual bool HasUnrecognizedParameter() const final\n\t\t\t{\n\t\t\t\treturn HasVariableLengthValue();\n\t\t\t}\n\n\t\t\tconst uint8_t* GetUnrecognizedParameter() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValue();\n\t\t\t}\n\n\t\t\tuint16_t GetUnrecognizedParameterLength() const\n\t\t\t{\n\t\t\t\treturn GetVariableLengthValueLength();\n\t\t\t}\n\n\t\t\tvoid SetUnrecognizedParameter(const uint8_t* parameter, uint16_t parameterLength);\n\n\t\tprotected:\n\t\t\tUnrecognizedParameterParameter* SoftClone(const uint8_t* buffer) const final;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp",
    "content": "#ifndef MS_RTC_SCTP_ZERO_CHECKSUM_ACCEPTABLE_PARAMETER_HPP\n#define MS_RTC_SCTP_ZERO_CHECKSUM_ACCEPTABLE_PARAMETER_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Zero Checksum Acceptable Parameter (ZERO_CHECKSUM_ACCEPTABLE)\n\t\t * (32769).\n\t\t *\n\t\t * @see RFC 9653.\n\t\t *\n\t\t *  0                   1                   2                   3\n\t\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |          Type = 0x8001        |          Length = 8           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t * |           Error Detection Method Identifier (EDMID)           |\n\t\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t\t */\n\n\t\t// Forward declaration.\n\t\tclass Chunk;\n\n\t\tclass ZeroChecksumAcceptableParameter : public Parameter\n\t\t{\n\t\t\t// We need that Chunk calls protected and private methods in this class.\n\t\t\tfriend class Chunk;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Zero Checksum Alternate Error Detection Method.\n\t\t\t */\n\t\t\t// NOLINTNEXTLINE(performance-enum-size)\n\t\t\tenum class AlternateErrorDetectionMethod : uint32_t\n\t\t\t{\n\t\t\t\tNONE           = 0x0000,\n\t\t\t\tSCTP_OVER_DTLS = 0x0001,\n\t\t\t};\n\n\t\tpublic:\n\t\t\tstatic const size_t ZeroChecksumAcceptableParameterHeaderLength{ 8 };\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Parse a ZeroChecksumAcceptableParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` may exceed the exact length of the Parameter.\n\t\t\t */\n\t\t\tstatic ZeroChecksumAcceptableParameter* Parse(const uint8_t* buffer, size_t bufferLength);\n\n\t\t\t/**\n\t\t\t * Create a ZeroChecksumAcceptableParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * `bufferLength` could be greater than the Parameter real length.\n\t\t\t */\n\t\t\tstatic ZeroChecksumAcceptableParameter* Factory(uint8_t* buffer, size_t bufferLength);\n\n\t\t\tstatic const std::string& AlternateErrorDetectionMethodToString(\n\t\t\t  AlternateErrorDetectionMethod alternateErrorDetectionMethod);\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Parse a ZeroChecksumAcceptableParameter.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * To be used only by `Chunk::ParseParameters()`.\n\t\t\t */\n\t\t\tstatic ZeroChecksumAcceptableParameter* ParseStrict(\n\t\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding);\n\n\t\t\tstatic const std::unordered_map<AlternateErrorDetectionMethod, std::string>\n\t\t\t  AlternateErrorDetectionMethod2String;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Only used by Parse(), ParseStrict() and Factory() static methods.\n\t\t\t */\n\t\t\tZeroChecksumAcceptableParameter(uint8_t* buffer, size_t bufferLength);\n\n\t\tpublic:\n\t\t\t~ZeroChecksumAcceptableParameter() override;\n\n\t\t\tvoid Dump(int indentation = 0) const final;\n\n\t\t\tZeroChecksumAcceptableParameter* Clone(uint8_t* buffer, size_t bufferLength) const final;\n\n\t\t\tAlternateErrorDetectionMethod GetAlternateErrorDetectionMethod() const\n\t\t\t{\n\t\t\t\tconst auto method = Utils::Byte::Get4Bytes(GetBuffer(), 4);\n\n\t\t\t\tif (\n\t\t\t\t  method == static_cast<uint32_t>(AlternateErrorDetectionMethod::NONE) ||\n\t\t\t\t  method == static_cast<uint32_t>(AlternateErrorDetectionMethod::SCTP_OVER_DTLS))\n\t\t\t\t{\n\t\t\t\t\treturn static_cast<AlternateErrorDetectionMethod>(method);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn AlternateErrorDetectionMethod::NONE;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid SetAlternateErrorDetectionMethod(AlternateErrorDetectionMethod alternateErrorDetectionMethod);\n\n\t\tprotected:\n\t\t\tZeroChecksumAcceptableParameter* SoftClone(const uint8_t* buffer) const final;\n\n\t\t\t/**\n\t\t\t * We don't really need to override this method since this Parameter\n\t\t\t * doesn't have variable-length value (despite the fixed header doesn't\n\t\t\t * have default length).\n\t\t\t */\n\t\t\tsize_t GetHeaderLength() const final\n\t\t\t{\n\t\t\t\treturn ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength;\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/AssociationInterface.hpp",
    "content": "#ifndef MS_RTC_SCTP_ASSOCIATION_INTERFACE_HPP\n#define MS_RTC_SCTP_ASSOCIATION_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/AssociationMetrics.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <FBS/sctpParameters.h>\n#include <span>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * The SCTP Association class represents the mediasoup side of an SCTP\n\t\t * association with a remote peer.\n\t\t *\n\t\t * It manages all Packet and Chunk dispatching and the connection flow.\n\t\t */\n\t\tclass AssociationInterface\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~AssociationInterface() = default;\n\n\t\t\tvirtual void Dump(int indentation = 0) const = 0;\n\n\t\t\tvirtual flatbuffers::Offset<FBS::SctpParameters::SctpParameters> FillBuffer(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) const = 0;\n\n\t\t\tvirtual Types::AssociationState GetAssociationState() const = 0;\n\n\t\t\t/**\n\t\t\t * May invoke `Connect()` but only if the parent transport is ready for\n\t\t\t * SCTP transmission (e.g. the WebRtcTransport has ICE and DTLS connected).\n\t\t\t */\n\t\t\tvirtual void MayConnect() = 0;\n\n\t\t\t/**\n\t\t\t * Initiate the SCTP association with the remote peer. It sends an INIT\n\t\t\t * Chunk.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - The SCTP association must be in New state.\n\t\t\t */\n\t\t\tvirtual void Connect() = 0;\n\n\t\t\t/**\n\t\t\t * Gracefully shutdowns the Association and sends all outstanding data.\n\t\t\t * This is an asynchronous operation and `OnAssociationClosed()` will be\n\t\t\t * called on success.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - libwebrtc never calls the corresponding DcSctpSocket::Shutdown()\n\t\t\t *   method due to a bug and hence we shouldn't either.\n\t\t\t *\n\t\t\t * @see https://issues.webrtc.org/issues/42222897\n\t\t\t */\n\t\t\tvirtual void Shutdown() = 0;\n\n\t\t\t/**\n\t\t\t * Closes the Association non-gracefully. Will send ABORT if the connection\n\t\t\t * is not already closed. No callbacks will be made after Close() has\n\t\t\t * returned. However, before Close() returns, it may have called\n\t\t\t * `OnAssociationClosed()` or `OnAssociationAborted()` callbacks.\n\t\t\t */\n\t\t\tvirtual void Close() = 0;\n\n\t\t\t/**\n\t\t\t * Retrieves the latest metrics. If the Association is not fully connected,\n\t\t\t * `std::nullopt` will be returned.\n\t\t\t */\n\t\t\tvirtual std::optional<AssociationMetrics> GetMetrics() const = 0;\n\n\t\t\t/**\n\t\t\t * Returns the currently set priority for an outgoing stream. The initial\n\t\t\t * value, when not set, is `SctpOptions::defaultStreamPriority`.\n\t\t\t */\n\t\t\tvirtual uint16_t GetStreamPriority(uint16_t streamId) const = 0;\n\n\t\t\t/**\n\t\t\t * Sets the priority of an outgoing stream. The initial value, when not\n\t\t\t * set, is `SctpOptions::defaultStreamPriority`.\n\t\t\t */\n\t\t\tvirtual void SetStreamPriority(uint16_t streamId, uint16_t priority) = 0;\n\n\t\t\t/**\n\t\t\t * Sets the maximum size of sent messages. The initial value, when not\n\t\t\t * set, is `SctpOptions::maxSendMessageSize`.\n\t\t\t */\n\t\t\tvirtual void SetMaxSendMessageSize(size_t maxMessageSize) = 0;\n\n\t\t\t/**\n\t\t\t * Returns the number of bytes of data currently queued to be sent on a\n\t\t\t * given stream.\n\t\t\t */\n\t\t\tvirtual size_t GetStreamBufferedAmount(uint16_t streamId) const = 0;\n\n\t\t\t/**\n\t\t\t * Returns the number of buffered outgoing bytes that is considered \"low\"\n\t\t\t * for a given stream. See `SetStreamBufferedAmountLowThreshold()`.\n\t\t\t */\n\t\t\tvirtual size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const = 0;\n\n\t\t\t/**\n\t\t\t * Specifies the number of bytes of buffered outgoing data that is\n\t\t\t * considered \"low\" for a given stream, which will trigger\n\t\t\t * `OnAssociationStreamBufferedAmountLow()` event. The default value is 0.\n\t\t\t */\n\t\t\tvirtual void SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) = 0;\n\n\t\t\t/**\n\t\t\t * Resetting streams is an asynchronous operation and the results will be\n\t\t\t * notified using `OnAssociationStreamsResetPerformed()` on success and\n\t\t\t * `OnAssociationStreamsResetFailed()` on failure.\n\t\t\t *\n\t\t\t * When it's known that the peer has reset its own outgoing streams,\n\t\t\t * `OnAssociationInboundStreamsReset()` is called.\n\t\t\t *\n\t\t\t * Resetting streams can only be done on an established association that\n\t\t\t * supports stream resetting. Calling this method on e.g. a closed SCTP\n\t\t\t * association or streams that don't support resetting will not perform\n\t\t\t * any operation.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Only outbound streams can be reset.\n\t\t\t * - Resetting a stream will also remove all queued messages on those\n\t\t\t *   streams, but will ensure that the currently sent message (if any) is\n\t\t\t *   fully sent before closing the stream.\n\t\t\t */\n\t\t\tvirtual Types::ResetStreamsStatus ResetStreams(std::span<const uint16_t> outboundStreamIds) = 0;\n\n\t\t\t/**\n\t\t\t * Sends an SCTP message using the provided send options. Sending a message\n\t\t\t * is an asynchronous operation, and the `OnAssociationError()` callback\n\t\t\t * may be invoked to indicate any errors in sending the message.\n\t\t\t *\n\t\t\t * The association does not have to be established before calling this\n\t\t\t * method. If it's called before there is an established association, the\n\t\t\t * message will be queued.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Copy constructor is disabled and there is move constructor. That's why\n\t\t\t *   we don't pass a reference here. We could pass `Message&&` but that's\n\t\t\t *   worse opens the door to bugs.\n\t\t\t */\n\t\t\tvirtual Types::SendMessageStatus SendMessage(\n\t\t\t  Message message, const SendMessageOptions& sendMessageOptions) = 0;\n\n\t\t\t/**\n\t\t\t * Sends SCTP messages using the provided send options. Sending a message\n\t\t\t * is an asynchronous operation, and the `OnAssociationError()` callback\n\t\t\t * may be invoked to indicate any errors in sending a message.\n\t\t\t *\n\t\t\t * The association does not have to be established before calling this\n\t\t\t * method. If it's called before there is an established association, the\n\t\t\t * message will be queued.\n\t\t\t *\n\t\t\t * This has identical semantics to `SendMessage()', except that it may\n\t\t\t * coalesce many messages into a single SCTP Packet if they would fit.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Same as in `SendMessage()`.\n\t\t\t */\n\t\t\tvirtual std::vector<Types::SendMessageStatus> SendManyMessages(\n\t\t\t  std::span<Message> messages, const SendMessageOptions& sendMessageOptions) = 0;\n\n\t\t\t/**\n\t\t\t * Receives SCTP data (hopefully an SCTP Packet) from the remote peer.\n\t\t\t */\n\t\t\tvirtual void ReceiveSctpData(const uint8_t* data, size_t len) = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/AssociationListenerInterface.hpp",
    "content": "#ifndef MS_RTC_SCTP_ASSOCIATION_LISTENER_INTERFACE_HPP\n#define MS_RTC_SCTP_ASSOCIATION_LISTENER_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <span>\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tclass AssociationListenerInterface\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~AssociationListenerInterface() = default;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Called when an SCTP Packet must be sent to the remote endpoint.\n\t\t\t *\n\t\t\t * @return\n\t\t\t * - `true` if the packet was successfully sent. However, since\n\t\t\t *   sending is unreliable, there are no guarantees that the Packet was\n\t\t\t *   actually delivered.\n\t\t\t * - `false` if the Packet failed to be sent.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual bool OnAssociationSendData(const uint8_t* data, size_t len) = 0;\n\n\t\t\t/**\n\t\t\t * Called when calling Connect().\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationConnecting() = 0;\n\n\t\t\t/**\n\t\t\t * Called when calling Connect() succeeds and also for incoming successful\n\t\t\t * connection attempts.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationConnected() = 0;\n\n\t\t\t/**\n\t\t\t * Called when calling Connect() and also for incoming connection attempts\n\t\t\t * in case the association fails.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationFailed(Types::ErrorKind errorKind, std::string_view errorMessage) = 0;\n\n\t\t\t/**\n\t\t\t * Called when the Association is closed in a controlled way or when the\n\t\t\t * Association has aborted - either as decided by this Association due to\n\t\t\t * e.g. too many retransmission attempts or by the peer when receiving an\n\t\t\t * ABORT command. No other callbacks will be done after this callback,\n\t\t\t * unless reconnecting.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationClosed(Types::ErrorKind errorKind, std::string_view errorMessage) = 0;\n\n\t\t\t/**\n\t\t\t * Called on connection restarted (by peer). This is just a notification,\n\t\t\t * and the association is expected to work fine after this call, but there\n\t\t\t * could have been packet loss as a result of restarting the association.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationRestarted() = 0;\n\n\t\t\t/**\n\t\t\t * Triggered when an non-fatal error is reported by either this library or\n\t\t\t * from the other peer (by sending an ERROR command). These should be\n\t\t\t * logged, but no other action need to be taken as the association is still\n\t\t\t * viable.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationError(Types::ErrorKind errorKind, std::string_view errorMessage) = 0;\n\n\t\t\t/**\n\t\t\t * Called when an SCTP message in full has been received.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationMessageReceived(Message message) = 0;\n\n\t\t\t/**\n\t\t\t * Indicates that a stream reset request has been performed.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationStreamsResetPerformed(std::span<const uint16_t> outboundStreamIds) = 0;\n\n\t\t\t/**\n\t\t\t * Indicates that a stream reset request has failed.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationStreamsResetFailed(\n\t\t\t  std::span<const uint16_t> outboundStreamIds, std::string_view errorMessage) = 0;\n\n\t\t\t/**\n\t\t\t * When a peer has reset some of its outbound streams, this will be\n\t\t\t * called. An empty list indicates that all streams have been reset.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationInboundStreamsReset(std::span<const uint16_t> inboundStreamIds) = 0;\n\n\t\t\t/**\n\t\t\t * Called when the amount of data buffered to be sent falls to or below\n\t\t\t * the threshold set when calling SetStreamBufferedAmountLowThreshold().\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationStreamBufferedAmountLow(uint16_t streamId) = 0;\n\n\t\t\t/**\n\t\t\t * Called when the total amount of data buffered (in the entire send\n\t\t\t * buffer, for all streams) falls to or below the threshold specified in\n\t\t\t * SctpOptions::totalBufferedAmountLowThreshold`.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationTotalBufferedAmountLow() = 0;\n\n\t\t\t/**\n\t\t\t * Called when the Association needs to know if the parent transport is\n\t\t\t * ready for SCTP traffic (e.g. whether the WebRtcTransport has ICE and\n\t\t\t * DTLS connected and at least a DataProducer or DataConsumer has been\n\t\t\t * created). Returned boolean indicates it.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual bool OnAssociationIsTransportReadyForSctp() = 0;\n\n\t\t\t/**\n\t\t\t * SCTP message lifecycle events.\n\t\t\t *\n\t\t\t * If a `lifecycleId` is provided as `MessageSendOptions`, lifecycle\n\t\t\t * callbacks will be triggered as the message is processed by the library.\n\t\t\t *\n\t\t\t * The possible transitions are shown in the graph below:\n\t\t\t *\n\t\t\t * Association::SendMessage() ────────────────────────┐\n\t\t\t *                │                                   │\n\t\t\t *                │                                   │\n\t\t\t *                v                                   v\n\t\t\t * OnAssociationLifecycleMessageFullySent ──> OnAssociationLifecycleMessageExpired\n\t\t\t *                │                                   │\n\t\t\t *                │                                   │\n\t\t\t *                v                                   v\n\t\t\t * OnAssociationLifeCycleMessageDelivered ──>   OnAssociationLifecycleEnd\n\t\t\t */\n\n\t\t\t/**\n\t\t\t * Called when a message has been fully sent, meaning that the last\n\t\t\t * fragment has been produced from the send queue and sent on the network.\n\t\t\t * Note that this will trigger at most once per message even if the\n\t\t\t * message was retransmitted due to packet loss.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This is a message lifecycle event.\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) {};\n\n\t\t\t/**\n\t\t\t * Called when a message has expired. If it was expired with data\n\t\t\t * remaining in the send queue that had not been sent ever,\n\t\t\t * `maybeDelivered` will be set to false. If `maybeDelivered` is true,\n\t\t\t * the message has at least once been sent and may have been correctly\n\t\t\t * received by the peer, but it has expired before the receiver managed\n\t\t\t * to acknowledge it. This means that if `maybeDelivered` is true, it's\n\t\t\t * unknown if the message was lost or was delivered, and if\n\t\t\t * `maybeDelivered` is false, it's guaranteed to not be delivered.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This is a message lifecycle event.\n\t\t\t * - It's guaranteed that OnAssociationLifecycleMessageDelivered() is not called\n\t\t\t *   if this callback has triggered.\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered)\n\t\t\t{\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Called whena non-expired message has been acknowledged by the peer as\n\t\t\t * delivered.\n\t\t\t *\n\t\t\t * Note that this will trigger only when the peer moves its cumulative\n\t\t\t * TSN ack beyond this message, and will not fire for messages acked using\n\t\t\t * gap-ack-blocks as those are renegable. This means that this may fire a\n\t\t\t * bit later than the message was actually first \"acked\" by the peer, as\n\t\t\t * according to the protocol, those acks may be unacked later by the\n\t\t\t * client.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This is a message lifecycle event.\n\t\t\t * - It's guaranteed that OnAssociationLifecycleMessageEnd() is not called if\n\t\t\t *   this callback has triggered.\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId)\n\t\t\t{\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Called when a lifecycle event has reached its end. It will be called\n\t\t\t * when processing of a message is complete, no matter how it completed.\n\t\t\t * It will be called after all other lifecycle events, if any.\n\t\t\t *\n\t\t\t * Note that it's possible that this callback triggers without any other\n\t\t\t * lifecycle callbacks having been called before in case of errors, such\n\t\t\t * as attempting to send an empty message or failing to enqueue a message\n\t\t\t * if the send queue is full.\n\t\t\t *\n\t\t\t * @remarks:\n\t\t\t * - This is a message lifecycle event.\n\t\t\t * - When the Association is deallocated, there will be no\n\t\t\t *   OnAssociationLifecycleMessageEnd() callbacks sent for messages that were\n\t\t\t *   enqueued. But as long as the Association is alive, these callbacks are\n\t\t\t *   guaranteed to be sent as messages are either expired or successfully\n\t\t\t *   acknowledged.\n\t\t\t * - It is NOT allowed to call methods in Association within this callback.\n\t\t\t */\n\t\t\tvirtual void OnAssociationLifecycleMessageEnd(uint64_t lifecycleId)\n\t\t\t{\n\t\t\t}\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/AssociationMetrics.hpp",
    "content": "#ifndef MS_RTC_SCTP_ASSOCIATION_METRICS_HPP\n#define MS_RTC_SCTP_ASSOCIATION_METRICS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP Association metrics.\n\t\t */\n\t\tstruct AssociationMetrics\n\t\t{\n\t\t\t/**\n\t\t\t * Number of SCTP Packets sent.\n\t\t\t */\n\t\t\tuint64_t txPacketsCount{ 0 };\n\n\t\t\t/**\n\t\t\t * Number of messages requested to be sent.\n\t\t\t */\n\t\t\tuint64_t txMessagesCount{ 0 };\n\n\t\t\t/**\n\t\t\t * Number of SCTP Packets received.\n\t\t\t */\n\t\t\tuint64_t rxPacketsCount{ 0 };\n\n\t\t\t/**\n\t\t\t * Number of messages received.\n\t\t\t */\n\t\t\tuint64_t rxMessagesCount{ 0 };\n\n\t\t\t/**\n\t\t\t * Number of Packets retransmitted. Since SCTP Packets can contain both\n\t\t\t * retransmitted DATA or I-DATA Chunks and Chunks that are transmitted for\n\t\t\t * the first time, this represents an upper bound as it's incremented\n\t\t\t * every time a Packet contains a retransmitted DATA or I-DATA chunk.\n\t\t\t */\n\t\t\tuint64_t rtxPacketsCount{ 0 };\n\n\t\t\t/**\n\t\t\t * Total number of bytes retransmitted. This includes the payload and\n\t\t\t * DATA/I-DATA headers, but not SCTP packet headers.\n\t\t\t */\n\t\t\tuint64_t rtxBytesCount{ 0 };\n\n\t\t\t/**\n\t\t\t * The current congestion window (cwnd) in bytes, corresponding to\n\t\t\t * `spinfo_cwnd` defined in RFC 6458.\n\t\t\t */\n\t\t\tsize_t cwndBytes{ 0 };\n\n\t\t\t/**\n\t\t\t * Smoothed round trip time (in ms), corresponding to `spinfo_srtt`\n\t\t\t * defined in RFC 6458.\n\t\t\t */\n\t\t\tuint64_t srttMs{ 0 };\n\n\t\t\t/**\n\t\t\t * Number of data items in the retransmission queue that haven’t been\n\t\t\t * acked/nacked yet and are in-flight. Corresponding to `sstat_unackdata`\n\t\t\t * defined in RFC 6458. This may be an approximation when there are\n\t\t\t * messages in the send queue that haven't been fragmented/packetized yet.\n\t\t\t */\n\t\t\tsize_t unackDataCount{ 0 };\n\n\t\t\t/**\n\t\t\t * The peer’s last announced receiver window size, corresponding to\n\t\t\t * `sstat_rwnd` defined in RFC 6458.\n\t\t\t */\n\t\t\tuint32_t peerRwndBytes{ 0 };\n\n\t\t\t/**\n\t\t\t * SCTP implementation of the peer. Only detected when the peer sends an\n\t\t\t * INIT_ACK Chunk to us with a State Cookie.\n\t\t\t */\n\t\t\tTypes::SctpImplementation peerImplementation{ Types::SctpImplementation::UNKNOWN };\n\n\t\t\t/**\n\t\t\t * The number of negotiated outbound streams, which is configured locally\n\t\t\t * as `SctpOptions::maxOutboundStreams`, and which will be signaled by the\n\t\t\t * remote during connection.\n\t\t\t */\n\t\t\tuint16_t negotiatedMaxOutboundStreams{ 0 };\n\n\t\t\t/**\n\t\t\t * The number of negotiated inbound streams, which is configured locally\n\t\t\t * as `SctpOptions::maxInboundStreams`, and which will be signaled by the\n\t\t\t * remote during connection.\n\t\t\t */\n\t\t\tuint16_t negotiatedMaxInboundStreams{ 0 };\n\n\t\t\t/**\n\t\t\t * Whether Partial Reliability has been negotiated.\n\t\t\t *\n\t\t\t * @see RFC 3758.\n\t\t\t */\n\t\t\tbool usesPartialReliability{ false };\n\n\t\t\t/**\n\t\t\t * Whether Stream Schedulers and User Message Interleaving (I-DATA Chunks)\n\t\t\t * have been negotiated.\n\t\t\t *\n\t\t\t * @see RFC 8260.\n\t\t\t */\n\t\t\tbool usesMessageInterleaving{ false };\n\n\t\t\t/**\n\t\t\t * Whether Stream Re-Configuration has been negotiated.\n\t\t\t *\n\t\t\t * @see RFC 6525.\n\t\t\t */\n\t\t\tbool usesReConfig{ false };\n\n\t\t\t/**\n\t\t\t * Whether Alternate Error Detection Method for Zero Checksum has been\n\t\t\t * negotiated.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This feature is only enabled if both peers signal their wish to use\n\t\t\t *   the same (non-zero) Zero Checksum Alternate Error Detection Method.\n\t\t\t *\n\t\t\t * @see RFC 9653.\n\t\t\t */\n\t\t\tbool usesZeroChecksum{ false };\n\n\t\t\tvoid Dump(int indentation = 0) const;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/Message.hpp",
    "content": "#ifndef MS_RTC_SCTP_MESSAGE_HPP\n#define MS_RTC_SCTP_MESSAGE_HPP\n\n#include \"common.hpp\"\n#include <span>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * An SCTP message is a group of bytes sent or received as a whole on a\n\t\t * specified stream identifier (`streamId`) and with a payload protocol\n\t\t * identifier (`ppid`).\n\t\t */\n\t\tclass Message\n\t\t{\n\t\tpublic:\n\t\t\tMessage(uint16_t streamId, uint32_t ppid, std::vector<uint8_t> payload);\n\n\t\t\t/**\n\t\t\t * Move constructor. No need to do anything special since std::vector\n\t\t\t * already implements move.\n\t\t\t */\n\t\t\tMessage(Message&& other) = default;\n\n\t\t\t/**\n\t\t\t * Move assignment. No need to do anything special since std::vector\n\t\t\t * already implements move.\n\t\t\t */\n\t\t\tMessage& operator=(Message&& other) = default;\n\n\t\t\t/**\n\t\t\t * Copy constructor disabled.\n\t\t\t */\n\t\t\tMessage(const Message&) = delete;\n\n\t\t\t/**\n\t\t\t * Copy assignment disabled.\n\t\t\t */\n\t\t\tMessage& operator=(const Message&) = delete;\n\n\t\t\t~Message();\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\tuint16_t GetStreamId() const\n\t\t\t{\n\t\t\t\treturn this->streamId;\n\t\t\t}\n\n\t\t\tvoid SetStreamId(uint16_t streamId);\n\n\t\t\tuint32_t GetPayloadProtocolId() const\n\t\t\t{\n\t\t\t\treturn this->ppid;\n\t\t\t}\n\n\t\t\tstd::span<const uint8_t> GetPayload() const\n\t\t\t{\n\t\t\t\treturn this->payload;\n\t\t\t}\n\n\t\t\tsize_t GetPayloadLength() const\n\t\t\t{\n\t\t\t\treturn this->payload.size();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Useful to extract the payload and its ownership when destructing the\n\t\t\t * Message.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - && at the end means that it can only be called from a rvalue.\n\t\t\t *\n\t\t\t * @usage\n\t\t\t * ```c++\n\t\t\t * const auto payload = std::move(message).ReleasePayload();\n\t\t\t * ```\n\t\t\t */\n\t\t\tstd::vector<uint8_t> ReleasePayload() &&\n\t\t\t{\n\t\t\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)\n\t\t\t\treturn std::move(this->payload);\n\t\t\t}\n\n\t\t\tMessage Clone() const\n\t\t\t{\n\t\t\t\treturn Message(this->streamId, this->ppid, this->payload);\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint16_t streamId;\n\t\t\tuint32_t ppid;\n\t\t\tstd::vector<uint8_t> payload;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/SctpOptions.hpp",
    "content": "#ifndef MS_RTC_SCTP_OPTIONS_HPP\n#define MS_RTC_SCTP_OPTIONS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * SCTP options.\n\t\t */\n\t\tstruct SctpOptions\n\t\t{\n\t\t\t/**\n\t\t\t * Signaled source port.\n\t\t\t */\n\t\t\tuint16_t sourcePort{ 5000 };\n\n\t\t\t/**\n\t\t\t * Signaled destination port.\n\t\t\t */\n\t\t\tuint16_t destinationPort{ 5000 };\n\n\t\t\t/**\n\t\t\t * Announced maximum number of outbound streams (OS).\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - We use maximum value by default.\n\t\t\t */\n\t\t\tuint16_t announcedMaxOutboundStreams{ 65535 };\n\n\t\t\t/**\n\t\t\t * Announced maximum number of inbound streams (MIS).\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - We use maximum value by default.\n\t\t\t */\n\t\t\tuint16_t announcedMaxInboundStreams{ 65535 };\n\n\t\t\t/**\n\t\t\t * Maximum size of an SCTP Packet. It doesn't include any overhead of\n\t\t\t * DTLS, TURN, UDP or IP headers.\n\t\t\t */\n\t\t\tsize_t mtu{ RTC::Consts::MaxSafeMtuSizeForSctp };\n\n\t\t\t/**\n\t\t\t * The largest allowed message payload to be sent. Messages will be rejected\n\t\t\t * if their payload is larger than this value. Note that this doesn't affect\n\t\t\t * incoming messages, which may larger than this value (but smaller than\n\t\t\t * `maxReceiverWindowBufferSize`).\n\t\t\t */\n\t\t\tsize_t maxSendMessageSize{ 256 * 1024 };\n\n\t\t\t/**\n\t\t\t * The default stream priority, if not overridden by\n\t\t\t * `Association::SetStreamPriority()`. The default value is selected to be\n\t\t\t * compatible with https://www.w3.org/TR/webrtc-priority/, section 4.2-4.3.\n\t\t\t */\n\t\t\tuint16_t defaultStreamPriority{ 256 };\n\n\t\t\t/**\n\t\t\t * Maximum received window buffer size. This should be a bit larger than\n\t\t\t * the largest sized message you want to be able to receive. This\n\t\t\t * essentially limits the memory usage on the receive side. Note that\n\t\t\t * memory is allocated dynamically, and this represents the maximum amount\n\t\t\t * of buffered data. The actual memory usage of the library will be\n\t\t\t * smaller in normal operation, and will be larger than this due to other\n\t\t\t * allocations and overhead if the buffer is fully utilized.\n\t\t\t */\n\t\t\tsize_t maxReceiverWindowBufferSize{ 5 * 1024 * 1024 };\n\n\t\t\t/**\n\t\t\t * Send queue total size limit. It will not be possible to queue more data\n\t\t\t * if the queue size is larger than this number.\n\t\t\t */\n\t\t\tsize_t maxSendBufferSize{ 2000000 };\n\n\t\t\t/**\n\t\t\t * Per stream send queue size limit. Similar to `maxSendBufferSize`, but\n\t\t\t * limiting the size of individual streams.\n\t\t\t */\n\t\t\tsize_t perStreamSendQueueLimit{ 2000000 };\n\n\t\t\t/**\n\t\t\t * A threshold that, when the amount of data in the send buffer goes below\n\t\t\t * this value, will trigger `Association::OnAssociationTotalBufferedAmountLow()`.\n\t\t\t */\n\t\t\tsize_t totalBufferedAmountLowThreshold{ 1800000 };\n\n\t\t\t/**\n\t\t\t * Max allowed RTT value. When the RTT is measured and it's found to be\n\t\t\t * larger than this value, it will be discarded and not used for e.g. any\n\t\t\t * RTO calculation. The default value is an extreme maximum but can be\n\t\t\t * adapted to better match the environment.\n\t\t\t */\n\t\t\tuint64_t maxRttMs{ 60000 };\n\n\t\t\t/**\n\t\t\t * Initial RTO value.\n\t\t\t */\n\t\t\tuint64_t initialRtoMs{ 500 };\n\n\t\t\t/**\n\t\t\t * Minimum RTO value.\n\t\t\t */\n\t\t\tuint64_t minRtoMs{ 400 };\n\n\t\t\t/**\n\t\t\t * Minimum RTO value.\n\t\t\t */\n\t\t\tuint64_t maxRtoMs{ 60000 };\n\n\t\t\t/**\n\t\t\t * T1-init timeout (ms).\n\t\t\t */\n\t\t\tuint64_t t1InitTimeoutMs{ 1000 };\n\n\t\t\t/**\n\t\t\t * T1-cookie timeout (ms).\n\t\t\t */\n\t\t\tuint64_t t1CookieTimeoutMs{ 1000 };\n\n\t\t\t/**\n\t\t\t * T2-shutdown timeout (ms).\n\t\t\t */\n\t\t\tuint64_t t2ShutdownTimeoutMs{ 1000 };\n\n\t\t\t/**\n\t\t\t * Maximum duration of the backoff timeout. If no value is given, no\n\t\t\t * limit is set.\n\t\t\t */\n\t\t\tstd::optional<uint64_t> timerMaxBackoffTimeoutMs{ std::nullopt };\n\n\t\t\t/**\n\t\t\t * Hearbeat interval (on idle connections only). Set to zero to disable.\n\t\t\t */\n\t\t\tuint64_t heartbeatIntervalMs{ 30000 };\n\n\t\t\t/**\n\t\t\t * The maximum time when a SACK will be sent from the arrival of an\n\t\t\t * unacknowledged Packet. Whatever is smallest of RTO/2 and this will be\n\t\t\t * used.\n\t\t\t */\n\t\t\tuint64_t delayedAckMaxTimeoutMs{ 200 };\n\n\t\t\t/**\n\t\t\t * The minimum limit for the measured RTT variance.\n\t\t\t *\n\t\t\t * Setting this below the expected delayed ack timeout (+ margin) of the\n\t\t\t * peer might result in unnecessary retransmissions, as the maximum time\n\t\t\t * it takes to ACK a DATA chunk is typically RTT + ATO (delayed ack\n\t\t\t * timeout), and when the SCTP channel is quite idle, and heartbeats\n\t\t\t * dominate the source of RTT measurement, the RTO would converge with the\n\t\t\t * smoothed RTT (SRTT). The default ATO is 200ms in usrsctp, and a 20ms\n\t\t\t * (10%) margin would include the processing time of received packets and\n\t\t\t * the clock granularity when setting the delayed ack timer on the peer.\n\t\t\t *\n\t\t\t * This is defined as \"G\" in the algorithm for TCP in\n\t\t\t * https://datatracker.ietf.org/doc/html/rfc6298#section-4.\n\t\t\t */\n\t\t\tuint64_t minRttVarianceMs{ 220 };\n\n\t\t\t/**\n\t\t\t * The initial congestion window size, in number of MTUs.\n\t\t\t *\n\t\t\t * @see https://tools.ietf.org/html/rfc4960#section-7.2.1 which defaults\n\t\t\t * at ~3 and https://research.google/pubs/pub36640/ which argues for at\n\t\t\t * least ten segments.\n\t\t\t */\n\t\t\tsize_t initialCwndMtus{ 10 };\n\n\t\t\t/**\n\t\t\t * The minimum congestion window size, in number of MTUs, upon detection\n\t\t\t * of/ packet loss by SACK. Note that if the retransmission timer expires,\n\t\t\t * the congestion window will be as small as one MTU.\n\t\t\t *\n\t\t\t * @see https://tools.ietf.org/html/rfc4960#section-7.2.3.\n\t\t\t */\n\t\t\tsize_t minCwndMtus{ 4 };\n\n\t\t\t/**\n\t\t\t * When the congestion window is at or above this number of MTUs, the\n\t\t\t * congestion control algorithm will avoid filling the congestion window\n\t\t\t * fully, if that results in fragmenting large messages into quite small\n\t\t\t * packets. When the congestion window is smaller than this option, it\n\t\t\t * will aim to fill the congestion window as much as it can, even if it\n\t\t\t * results in creating small fragmented packets.\n\t\t\t */\n\t\t\tsize_t avoidFragmentationCwndMtus{ 6 };\n\n\t\t\t/**\n\t\t\t * When the congestion window is below this number of MTUs, sent data\n\t\t\t * chunks will have the \"I\" (Immediate SACK - RFC7053) bit set. That will\n\t\t\t * prevent the receiver from delaying the SACK, which result in shorter\n\t\t\t * time until the sender can send the next packet as its driven by SACKs.\n\t\t\t * This can reduce latency for low utilized and lossy connections.\n\t\t\t *\n\t\t\t * Default value set to be same as initial congestion window. Set to zero\n\t\t\t * to disable.\n\t\t\t */\n\t\t\tsize_t immediateSackUnderCwndMtus{ 10 };\n\n\t\t\t/**\n\t\t\t * The number of packets that may be sent at once. This is limited to\n\t\t\t * avoid bursts that too quickly fill the send buffer. Typically in a\n\t\t\t * connection in its \"slow start\" phase (when it sends as much as it can),\n\t\t\t * it will send up to three packets for every SACK received, so the default\n\t\t\t * limit is set just above that, and then mostly applicable for (but not\n\t\t\t * limited to) fast retransmission scenarios.\n\t\t\t */\n\t\t\tsize_t maxBurst{ 4 };\n\n\t\t\t/**\n\t\t\t * Maximum data retransmit attempts (for DATA, I_DATA and other Chunks).\n\t\t\t * Set to std::nullopt for no limit.\n\t\t\t */\n\t\t\tstd::optional<uint16_t> maxRetransmissions{ 10 };\n\n\t\t\t/**\n\t\t\t * Max.Init.Retransmits. Set to std::nullopt for no limit.\n\t\t\t *\n\t\t\t * @see https://datatracker.ietf.org/doc/html/rfc9260#section-16\n\t\t\t */\n\t\t\tstd::optional<size_t> maxInitRetransmissions{ 8 };\n\n\t\t\t/**\n\t\t\t * Enable Partial Reliability Extension.\n\t\t\t * @see RFC 3758.\n\t\t\t */\n\t\t\tbool enablePartialReliability{ true };\n\n\t\t\t/**\n\t\t\t * Enable Stream Schedulers and User Message Interleaving (I-DATA Chunks).\n\t\t\t *\n\t\t\t * @see RFC 8260.\n\t\t\t */\n\t\t\tbool enableMessageInterleaving{ true };\n\n\t\t\t/**\n\t\t\t * Whether RTO should be added to heartbeat interval.\n\t\t\t */\n\t\t\tbool heartbeatIntervalIncludeRtt{ true };\n\n\t\t\t/**\n\t\t\t * Alternate Error Detection Method for Zero Checksum.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This feature is only enabled if both peers signal their wish to use\n\t\t\t *   the same (non-zero) Zero Checksum Alternate Error Detection Method.\n\t\t\t *\n\t\t\t * @see RFC 9653.\n\t\t\t */\n\t\t\tZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod zeroChecksumAlternateErrorDetectionMethod{\n\t\t\t\tZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE\n\t\t\t};\n\t\t};\n\n\t\t/**\n\t\t * Send options given when sending SCTP messages.\n\t\t */\n\t\tstruct SendMessageOptions\n\t\t{\n\t\t\t/**\n\t\t\t * Whether the message should be sent with unordered message delivery.\n\t\t\t */\n\t\t\tbool unordered{ false };\n\n\t\t\t/**\n\t\t\t * If set, will discard messages that haven't been correctly sent and\n\t\t\t * received before the lifetime has expired. This is only available if\n\t\t\t * the peer supports Partial Reliability Extension (RFC 3758).\n\t\t\t */\n\t\t\tstd::optional<uint64_t> lifetimeMs{ std::nullopt };\n\n\t\t\t/**\n\t\t\t * If set, limits the number of retransmissions. This is only available\n\t\t\t * if the peer supports Partial Reliability Extension (RFC 3758).\n\t\t\t */\n\t\t\tstd::optional<uint16_t> maxRetransmissions{ std::nullopt };\n\n\t\t\t/**\n\t\t\t * If set, will generate lifecycle events for this message. See e.g.\n\t\t\t * `AssociationListener::OnAssociationLifecycleMessageFullySent()`. This\n\t\t\t * value is decided by the application and the library will provide it to\n\t\t\t * all lifecycle callbacks.\n\t\t\t */\n\t\t\tstd::optional<uint64_t> lifecycleId{ std::nullopt };\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/public/SctpTypes.hpp",
    "content": "#ifndef MS_RTC_SCTP_TYPES_HPP\n#define MS_RTC_SCTP_TYPES_HPP\n\n#include \"common.hpp\"\n#include \"Utils/UnwrappedSequenceNumber.hpp\"\n#include <limits>\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tnamespace Types\n\t\t{\n\t\t\t/**\n\t\t\t * Publicly exposed SCTP Association state.\n\t\t\t */\n\t\t\tenum class AssociationState : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * Initial state.\n\t\t\t\t *\n\t\t\t\t * @remarks\n\t\t\t\t * - Once state changes it will never transition to NEW again.\n\t\t\t\t */\n\t\t\t\tNEW,\n\t\t\t\t/**\n\t\t\t\t * The Association is closed.\n\t\t\t\t */\n\t\t\t\tCLOSED,\n\t\t\t\t/**\n\t\t\t\t * The Association has initiated a connection, which is not yet\n\t\t\t\t * established.\n\t\t\t\t *\n\t\t\t\t * @remarks\n\t\t\t\t * - For incoming connections and for reconnections when the Association\n\t\t\t\t *   is already connected, the Association will not transition to this\n\t\t\t\t *   state.\n\t\t\t\t */\n\t\t\t\tCONNECTING,\n\t\t\t\t/**\n\t\t\t\t * The Association is connected and the connection is established.\n\t\t\t\t */\n\t\t\t\tCONNECTED,\n\t\t\t\t/**\n\t\t\t\t * The Association is shutting down, and the connection is not yet closed.\n\t\t\t\t */\n\t\t\t\tSHUTTING_DOWN\n\t\t\t};\n\n\t\t\tconstexpr std::string_view AssociationStateToString(AssociationState associationState)\n\t\t\t{\n\t\t\t\tswitch (associationState)\n\t\t\t\t{\n\t\t\t\t\tcase AssociationState::NEW:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"NEW\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AssociationState::CLOSED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"CLOSED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AssociationState::CONNECTING:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"CONNECTING\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AssociationState::CONNECTED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"CONNECTED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AssociationState::SHUTTING_DOWN:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SHUTTING_DOWN\";\n\t\t\t\t\t}\n\n\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Kinds of errors that are exposed in the API.\n\t\t\t */\n\t\t\tenum class ErrorKind : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * Indicates that no error has occurred. This will never be the case when\n\t\t\t\t * OnError() or OnAborted() is called.\n\t\t\t\t */\n\t\t\t\tSUCCESS,\n\t\t\t\t/**\n\t\t\t\t * There have been too many retries or timeouts, and the library has given\n\t\t\t\t * up.\n\t\t\t\t */\n\t\t\t\tTOO_MANY_RETRIES,\n\t\t\t\t/**\n\t\t\t\t * A command was received that is only possible to execute when the\n\t\t\t\t * Association is connected, which it is not.\n\t\t\t\t */\n\t\t\t\tNOT_CONNECTED,\n\t\t\t\t/**\n\t\t\t\t * Parsing of the command or its parameters failed.\n\t\t\t\t */\n\t\t\t\tPARSE_FAILED,\n\t\t\t\t/**\n\t\t\t\t * Commands are received in the wrong sequence, which indicates a\n\t\t\t\t * synchronisation mismatch between the peers.\n\t\t\t\t */\n\t\t\t\tWRONG_SEQUENCE,\n\t\t\t\t/**\n\t\t\t\t * The peer has reported an issue using ERROR or ABORT command.\n\t\t\t\t */\n\t\t\t\tPEER_REPORTED,\n\t\t\t\t/**\n\t\t\t\t * The peer has performed a protocol violation.\n\t\t\t\t */\n\t\t\t\tPROTOCOL_VIOLATION,\n\t\t\t\t/**\n\t\t\t\t * The receive or send buffers have been exhausted.\n\t\t\t\t */\n\t\t\t\tRESOURCE_EXHAUSTION,\n\t\t\t\t/**\n\t\t\t\t * The application has performed an invalid operation.\n\t\t\t\t */\n\t\t\t\tUNSUPPORTED_OPERATION\n\t\t\t};\n\n\t\t\tconstexpr std::string_view ErrorKindToString(ErrorKind errorKind)\n\t\t\t{\n\t\t\t\tswitch (errorKind)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: In dcsctp this is `NO_ERROR` but it fails on MSVC because\n\t\t\t\t\t// some Windows headers define `NO_ERROR` macro.\n\t\t\t\t\tcase ErrorKind::SUCCESS:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SUCCESS\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::TOO_MANY_RETRIES:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"TOO_MANY_RETRIES\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::NOT_CONNECTED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"NOT_CONNECTED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::PARSE_FAILED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"PARSE_FAILED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::WRONG_SEQUENCE:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"WRONG_SEQUENCE\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::PEER_REPORTED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"PEER_REPORTED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::PROTOCOL_VIOLATION:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"PROTOCOL_VIOLATION\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::RESOURCE_EXHAUSTION:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"RESOURCE_EXHAUSTION\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorKind::UNSUPPORTED_OPERATION:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"UNSUPPORTED_OPERATION\";\n\t\t\t\t\t}\n\n\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * SCTP implementation determined by first 8 bytes of the State Cookie\n\t\t\t * sent by the remote peer.\n\t\t\t */\n\t\t\tenum class SctpImplementation : uint8_t\n\t\t\t{\n\t\t\t\tUNKNOWN,\n\t\t\t\tMEDIASOUP,\n\t\t\t\tDCSCTP,\n\t\t\t\tUSRSCTP\n\t\t\t};\n\n\t\t\tconstexpr std::string_view SctpImplementationToString(SctpImplementation sctpImplementation)\n\t\t\t{\n\t\t\t\tswitch (sctpImplementation)\n\t\t\t\t{\n\t\t\t\t\tcase SctpImplementation::UNKNOWN:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"unknown\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SctpImplementation::MEDIASOUP:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"mediasoup\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SctpImplementation::DCSCTP:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"dcsctp\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SctpImplementation::USRSCTP:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"usrsctp\";\n\t\t\t\t\t}\n\n\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Return value of Association::ResetStreams().\n\t\t\t */\n\t\t\tenum class ResetStreamsStatus : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * If the connection is not yet established, this will be returned.\n\t\t\t\t */\n\t\t\t\tNOT_CONNECTED,\n\n\t\t\t\t/**\n\t\t\t\t * Indicates that ResetStreams operation has been successfully\n\t\t\t\t * initiated.\n\t\t\t\t */\n\t\t\t\tPERFORMED,\n\n\t\t\t\t/**\n\t\t\t\t * Indicates that resetting streams has failed as it's not supported by\n\t\t\t\t * the peer.\n\t\t\t\t */\n\t\t\t\tNOT_SUPPORTED\n\t\t\t};\n\n\t\t\tconstexpr std::string_view ResetStreamsStatusToString(ResetStreamsStatus status)\n\t\t\t{\n\t\t\t\tswitch (status)\n\t\t\t\t{\n\t\t\t\t\tcase ResetStreamsStatus::NOT_CONNECTED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"NOT_CONNECTED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ResetStreamsStatus::PERFORMED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"PERFORMED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ResetStreamsStatus::NOT_SUPPORTED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"NOT_SUPPORTED\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Return value of Association::SendMessage() and\n\t\t\t * Association::SendManyMessages().\n\t\t\t */\n\t\t\tenum class SendMessageStatus : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * The message was enqueued successfully. As sending the message is done\n\t\t\t\t * asynchronously, this is no guarantee that the message has been\n\t\t\t\t * actually sent.\n\t\t\t\t */\n\t\t\t\tSUCCESS,\n\t\t\t\t/**\n\t\t\t\t * The message was rejected as the payload was empty (which is not\n\t\t\t\t * allowed in SCTP).\n\t\t\t\t */\n\t\t\t\tERROR_MESSAGE_EMPTY,\n\n\t\t\t\t/**\n\t\t\t\t * The message was rejected as the payload was larger than what has been\n\t\t\t\t * set as `SctpOptions.maxMessageSize`.\n\t\t\t\t */\n\t\t\t\tERROR_MESSAGE_TOO_LARGE,\n\n\t\t\t\t/**\n\t\t\t\t * The message could not be enqueued as the Association is out of\n\t\t\t\t * resources. This mainly indicates that the send queue is full.\n\t\t\t\t */\n\t\t\t\tERROR_RESOURCE_EXHAUSTION,\n\n\t\t\t\t/**\n\t\t\t\t * The message could not be sent as the Association is shutting down.\n\t\t\t\t */\n\t\t\t\tERROR_SHUTTING_DOWN\n\t\t\t};\n\n\t\t\tconstexpr std::string_view SendMessageStatusToString(SendMessageStatus status)\n\t\t\t{\n\t\t\t\tswitch (status)\n\t\t\t\t{\n\t\t\t\t\tcase SendMessageStatus::SUCCESS:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"SUCCESS\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SendMessageStatus::ERROR_MESSAGE_EMPTY:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"ERROR_MESSAGE_EMPTY\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SendMessageStatus::ERROR_MESSAGE_TOO_LARGE:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"ERROR_MESSAGE_TOO_LARGE\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SendMessageStatus::ERROR_RESOURCE_EXHAUSTION:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"ERROR_RESOURCE_EXHAUSTION\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SendMessageStatus::ERROR_SHUTTING_DOWN:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"ERROR_SHUTTING_DOWN\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconstexpr uint16_t MaxRetransmitsNoLimit{ std::numeric_limits<uint16_t>::max() };\n\n\t\t\tconstexpr uint64_t ExpiresAtMsInfinite{ std::numeric_limits<uint64_t>::max() };\n\n\t\t\t/**\n\t\t\t * Unwrapped Transmission Sequence Numbers (TSN).\n\t\t\t */\n\t\t\tusing UnwrappedTsn = Utils::UnwrappedSequenceNumber<uint32_t>;\n\n\t\t\t/**\n\t\t\t * Unwrapped Stream Sequence Numbers (SSN).\n\t\t\t */\n\t\t\tusing UnwrappedSsn = Utils::UnwrappedSequenceNumber<uint16_t>;\n\n\t\t\t/**\n\t\t\t * Unwrapped Message Identifier (MID).\n\t\t\t */\n\t\t\tusing UnwrappedMid = Utils::UnwrappedSequenceNumber<uint32_t>;\n\t\t} // namespace Types\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/rx/DataTracker.hpp",
    "content": "#ifndef MS_RTC_SCTP_DATA_TRACKER_HPP\n#define MS_RTC_SCTP_DATA_TRACKER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"Utils/UnwrappedSequenceNumber.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <set>\n#include <string_view>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Keeps track of received DATA and I-DATA chunks and handles all logic for\n\t\t * when_to create SACKs and also how_to generate them.\n\t\t *\n\t\t * It only uses TSNs to track delivery and doesn't need to be aware of\n\t\t * streams.\n\t\t *\n\t\t * SACKs are optimally sent every second packet on association with no packet\n\t\t * loss. When packet loss is detected, it's sent for every packet. When SACKs\n\t\t * are not sent directly, a timer is used to send a SACK delayed (by RTO/2,\n\t\t * or 200ms, whatever is smallest).\n\t\t */\n\t\tclass DataTracker\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * The maximum number of duplicate TSNs that will be reported in a SACK.\n\t\t\t */\n\t\t\tstatic constexpr size_t MaxDuplicateTsnReported{ 20 };\n\n\t\t\t/**\n\t\t\t * The maximum number of gap-ack-blocks that will be reported in a SACK.\n\t\t\t */\n\t\t\tstatic constexpr size_t MaxGapAckBlocksReported{ 20 };\n\n\t\t\t/**\n\t\t\t * The maximum number of accepted in-flight DATA / I-DATA chunks. This\n\t\t\t * indicates the maximum difference from this buffer's last cumulative ack\n\t\t\t * TSN, and any received data. Data received beyond this limit will be\n\t\t\t * dropped, which will force the transmitter to send data that actually\n\t\t\t * increases the last cumulative acked TSN.\n\t\t\t */\n\t\t\tstatic constexpr uint32_t MaxAcceptedOutstandingFragments{ 100000 };\n\n\t\tprivate:\n\t\t\tenum class AckState : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * No need to send an ACK.\n\t\t\t\t */\n\t\t\t\tIDLE,\n\n\t\t\t\t/**\n\t\t\t\t * Has received data chunks (but not yet end of packet).\n\t\t\t\t */\n\t\t\t\tBECOMING_DELAYED,\n\n\t\t\t\t/**\n\t\t\t\t * Has received data chunks and the end of a packet. Delayed ack timer is\n\t\t\t\t * running and a SACK will be sent on expiry, or if DATA / I-DATA is sent,\n\t\t\t\t * or after next packet with data.\n\t\t\t\t */\n\t\t\t\tDELAYED,\n\n\t\t\t\t/**\n\t\t\t\t * Send a SACK immediately after handling this packet.\n\t\t\t\t */\n\t\t\t\tIMMEDIATE,\n\t\t\t};\n\n\t\tprivate:\n\t\t\tstatic constexpr std::string_view AckStateToString(AckState ackState)\n\t\t\t{\n\t\t\t\t// NOTE: We cannot use MS_TRACE() here because clang in Linux will\n\t\t\t\t// complain about \"read of non-constexpr variable 'configuration' is not\n\t\t\t\t// allowed in a constant expression\".\n\n\t\t\t\tswitch (ackState)\n\t\t\t\t{\n\t\t\t\t\tcase AckState::IDLE:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"IDLE\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AckState::BECOMING_DELAYED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"BECOMING_DELAYED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AckState::DELAYED:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"DELAYED\";\n\t\t\t\t\t}\n\n\t\t\t\t\tcase AckState::IMMEDIATE:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn \"IMMEDIATE\";\n\t\t\t\t\t}\n\n\t\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t\t}\n\t\t\t}\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Represents ranges of TSNs that have been received that are not directly\n\t\t\t * following the last cumulative acked TSN. This information is returned\n\t\t\t * to the sender in the \"gap-ack-blocks\" in the SACK chunk. The blocks are\n\t\t\t * always non-overlapping and non-adjacent.\n\t\t\t */\n\t\t\tclass AdditionalTsnBlocks\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\t/**\n\t\t\t\t * Represents an inclusive range of received TSNs, i.e. [firstTsn, lastTsn].\n\t\t\t\t */\n\t\t\t\tstruct TsnRange\n\t\t\t\t{\n\t\t\t\t\tTsnRange(Types::UnwrappedTsn firstTsn, Types::UnwrappedTsn lastTsn)\n\t\t\t\t\t  : firstTsn(firstTsn), lastTsn(lastTsn)\n\t\t\t\t\t{\n\t\t\t\t\t}\n\n\t\t\t\t\tTypes::UnwrappedTsn firstTsn;\n\t\t\t\t\tTypes::UnwrappedTsn lastTsn;\n\t\t\t\t};\n\n\t\t\t\t/**\n\t\t\t\t * Adds a TSN to the set. This will try to expand any existing block and\n\t\t\t\t * might merge blocks to ensure that all blocks are non-adjacent. If a\n\t\t\t\t * current block can't be expanded, a new block is created.\n\t\t\t\t *\n\t\t\t\t * The return value indicates if `tsn` was added. If false is returned,\n\t\t\t\t * the `tsn` was already represented in one of the blocks.\n\t\t\t\t */\n\t\t\t\tbool Add(Types::UnwrappedTsn tsn);\n\n\t\t\t\t/**\n\t\t\t\t * Erases all TSNs up to, and including `tsn`. This will remove all\n\t\t\t\t * blocks that are completely below `tsn` and may truncate a block where\n\t\t\t\t * `tsn` is within that block. In that case, the frontmost block's start\n\t\t\t\t * TSN will be the next following tsn after `tsn`.\n\t\t\t\t */\n\t\t\t\tvoid EraseTo(Types::UnwrappedTsn tsn);\n\n\t\t\t\t/**\n\t\t\t\t * Removes the first block. Must not be called on an empty set.\n\t\t\t\t */\n\t\t\t\tvoid PopFront();\n\n\t\t\t\tconst std::vector<TsnRange>& GetBlocks() const\n\t\t\t\t{\n\t\t\t\t\treturn this->blocks;\n\t\t\t\t}\n\n\t\t\t\tbool IsEmpty() const\n\t\t\t\t{\n\t\t\t\t\treturn this->blocks.empty();\n\t\t\t\t}\n\n\t\t\t\tconst TsnRange& Front() const\n\t\t\t\t{\n\t\t\t\t\treturn this->blocks.front();\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// A sorted vector of non-overlapping and non-adjacent blocks.\n\t\t\t\tstd::vector<TsnRange> blocks;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tDataTracker(BackoffTimerHandleInterface* delayedAckTimer, uint32_t remoteInitialTsn);\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Indicates if the provided TSN is valid. If this return false, the data\n\t\t\t * should be dropped and not added to any other buffers, which essentially\n\t\t\t * means that there is intentional packet loss.\n\t\t\t */\n\t\t\tbool IsTsnValid(uint32_t tsn) const;\n\n\t\t\t/**\n\t\t\t * Called for every incoming data chunk. Returns `true` if `tsn` was seen\n\t\t\t * for the first time, and `false` if it has been seen before (a duplicate\n\t\t\t * `tsn`).\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - `IsTsnValid()` must be called prior to calling this method.\n\t\t\t */\n\t\t\tbool Observe(uint32_t tsn, bool immediateAck = false);\n\n\t\t\t/**\n\t\t\t * Called at the end of processing an SCTP packet.\n\t\t\t */\n\t\t\tvoid ObservePacketEnd();\n\n\t\t\t/**\n\t\t\t * Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks. Indicates if the\n\t\t\t * chunk had any effect.\n\t\t\t */\n\t\t\tbool HandleForwardTsn(uint32_t newCumulativeTsn);\n\n\t\t\t/**\n\t\t\t * Indicates if a SACK should be sent. There may be other reasons to send\n\t\t\t * a SACK, but if this function indicates so, it should be sent as soon as\n\t\t\t * possible. Calling this function will make it clear a flag so that if\n\t\t\t * it's called again, it will probably return `false`.\n\t\t\t *\n\t\t\t * If the delayed ack timer is running, this method will return `false`\n\t\t\t * unless `alsoIfDelayed` is set to `true`. Then it will return true as\n\t\t\t * well.\n\t\t\t */\n\t\t\tbool ShouldSendAck(bool alsoIfDelayed = false);\n\n\t\t\t/**\n\t\t\t * Forces `ShouldSendSack()` to return `true`.\n\t\t\t */\n\t\t\tvoid ForceImmediateSack();\n\n\t\t\t/**\n\t\t\t * Returns the last cumulative ack TSN - the last seen data chunk's TSN\n\t\t\t * value before any packet loss was detected.\n\t\t\t */\n\t\t\tuint32_t GetLastCumulativeAckedTsn() const\n\t\t\t{\n\t\t\t\treturn this->lastCumulativeAckedTsn.Wrap();\n\t\t\t}\n\n\t\t\tbool IsLaterThanCumulativeAckedTsn(uint32_t tsn) const\n\t\t\t{\n\t\t\t\treturn this->tsnUnwrapper.PeekUnwrap(tsn) > this->lastCumulativeAckedTsn;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns `true` if the received `tsn` would increase the cumulative ack\n\t\t\t * TSN.\n\t\t\t */\n\t\t\tbool WillIncreaseCumAckTsn(uint32_t tsn) const;\n\n\t\t\t/**\n\t\t\t * Adds a SACK chunk with selective ack to the given Packet.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - It will clear `this->duplicates`, so every SACK chunk that is\n\t\t\t *   consumed must be sent.\n\t\t\t */\n\t\t\tvoid AddSackSelectiveAck(Packet* packet, size_t aRwnd);\n\n\t\t\tvoid HandleDelayedAckTimerExpiry();\n\n\t\tprivate:\n\t\t\tvoid UpdateAckState(AckState newAckState, std::string_view reason);\n\n\t\tprivate:\n\t\t\t// If a packet has ever been seen.\n\t\t\tBackoffTimerHandleInterface* delayedAckTimer;\n\t\t\tbool packetSeen{ false };\n\t\t\tAckState ackState{ AckState::IDLE };\n\t\t\tTypes::UnwrappedTsn::Unwrapper tsnUnwrapper;\n\t\t\t// All TSNs up until (and including) this value have been seen.\n\t\t\tTypes::UnwrappedTsn lastCumulativeAckedTsn;\n\t\t\t// Received TSNs that are not directly following `lastCumulativeAckedTsn`.\n\t\t\tAdditionalTsnBlocks additionalTsnBlocks;\n\t\t\tstd::set<uint32_t> duplicateTsns;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/rx/InterleavedReassemblyStreams.hpp",
    "content": "#ifndef MS_RTC_SCTP_INTERLEAVED_REASSEMBLY_STREAMS_HPP\n#define MS_RTC_SCTP_INTERLEAVED_REASSEMBLY_STREAMS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyStreamsInterface.hpp\"\n#include <map>\n#include <span>\n#include <tuple> // std::tie()\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Handles reassembly of incoming data when interleaved message sending is\n\t\t * enabled on the association, i.e. when RFC 8260 is in use.\n\t\t *\n\t\t * In other words, this class handles data received via I-DATA chunks.\n\t\t */\n\t\tclass InterleavedReassemblyStreams : public ReassemblyStreamsInterface\n\t\t{\n\t\tprivate:\n\t\t\tstruct FullStreamId\n\t\t\t{\n\t\t\t\tFullStreamId(bool unordered, uint16_t streamId) : unordered(unordered), streamId(streamId)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tfriend bool operator<(FullStreamId a, FullStreamId b)\n\t\t\t\t{\n\t\t\t\t\treturn std::tie(a.unordered, a.streamId) < std::tie(b.unordered, b.streamId);\n\t\t\t\t}\n\n\t\t\t\tconst bool unordered;\n\t\t\t\tconst uint16_t streamId;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tclass Stream\n\t\t\t{\n\t\t\tprivate:\n\t\t\t\tusing ChunkMap = std::map<uint32_t /*fsn*/, std::pair<Types::UnwrappedTsn, UserData>>;\n\n\t\t\tpublic:\n\t\t\t\tStream(FullStreamId fullStreamId, InterleavedReassemblyStreams* parent, uint32_t nextMid = 0)\n\t\t\t\t  : fullStreamId(fullStreamId), parent(*parent), nextMid(midUnwrapper.Unwrap(nextMid))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\tpublic:\n\t\t\t\tint32_t AddData(Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\t\tsize_t EraseTo(uint32_t mid);\n\n\t\t\t\tvoid Reset()\n\t\t\t\t{\n\t\t\t\t\tthis->midUnwrapper.Reset();\n\t\t\t\t\tthis->nextMid = this->midUnwrapper.Unwrap(0);\n\t\t\t\t}\n\n\t\t\t\tbool HasUnassembledChunks() const\n\t\t\t\t{\n\t\t\t\t\treturn !this->chunksByMid.empty();\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t/**\n\t\t\t\t * Try to assemble one message identified by `mid`. Returns the number\n\t\t\t\t * of bytes assembled if a message was assembled.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessage(Types::UnwrappedMid mid);\n\n\t\t\t\t/**\n\t\t\t\t * Try to assemble several messages in order from the stream. Returns\n\t\t\t\t * the number of bytes assembled if a message was assembled.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessages();\n\n\t\t\t\tsize_t AssembleMessage(ChunkMap& tsnChunks);\n\n\t\t\t\tsize_t AssembleMessage(Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\tprivate:\n\t\t\t\tconst FullStreamId fullStreamId;\n\t\t\t\tInterleavedReassemblyStreams& parent;\n\t\t\t\tstd::map<Types::UnwrappedMid, ChunkMap> chunksByMid;\n\t\t\t\tTypes::UnwrappedMid::Unwrapper midUnwrapper;\n\t\t\t\tTypes::UnwrappedMid nextMid;\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit InterleavedReassemblyStreams(\n\t\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage);\n\n\t\tpublic:\n\t\t\tint32_t AddData(Types::UnwrappedTsn tsn, UserData data) override;\n\n\t\t\tsize_t HandleForwardTsn(\n\t\t\t  Types::UnwrappedTsn newCumulativeTsn,\n\t\t\t  std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams) override;\n\n\t\t\tvoid ResetStreams(std::span<const uint16_t> streamIds) override;\n\n\t\tprivate:\n\t\t\tStream& GetOrCreateStream(const FullStreamId& streamId);\n\n\t\tprivate:\n\t\t\t// Callback for when a message has been assembled.\n\t\t\tconst OnAssembledMessage onAssembledMessage;\n\t\t\t// All unordered and ordered streams, managing not-yet-assembled data.\n\t\t\tstd::map<FullStreamId, Stream> streams;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp",
    "content": "#ifndef MS_RTC_SCTP_REASSEMBLY_QUEUE_HPP\n#define MS_RTC_SCTP_REASSEMBLY_QUEUE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyStreamsInterface.hpp\"\n#include <deque>\n#include <set>\n#include <span>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Contains the received DATA / I-DATA chunks that haven't yet been\n\t\t * reassembled, and reassembles chunks when possible.\n\t\t *\n\t\t * The actual assembly is handled by an implementation of the\n\t\t * `ReassemblyStreamsInterface` interface.\n\t\t *\n\t\t * Except for reassembling fragmented messages, this class will also handle\n\t\t * two less common operations. To handle the receiver-side of partial\n\t\t * reliability (limited number of retransmissions or limited message\n\t\t * lifetime) as well as stream resetting, which is used when a sender wishes\n\t\t * to close SCTP streams.\n\t\t *\n\t\t * Partial reliability is handled when a FORWARD-TSN or I-FORWARD-TSN chunk\n\t\t * is received, and it will simply delete any chunks matching the parameters\n\t\t * in that chunk. This is mainly implemented in ReassemblyStreams classes.\n\t\t *\n\t\t * Resetting streams is handled when a RECONFIG chunks is received with an\n\t\t * \"Outgoing SSN Reset Request\" parameter. That parameter will contain a list\n\t\t * of streams to reset, and a `senderLastAssignedTsn`. If this TSN is not yet\n\t\t * seen, the stream cannot be directly reset, and this class will respond\n\t\t * that the reset is \"deferred\". But if this TSN provided is known, the\n\t\t * stream can be immediately be reset.\n\t\t *\n\t\t * The reassembly queue has a maximum size, as it would otherwise be an DoS\n\t\t * attack vector where a peer could consume all memory of the other peer by\n\t\t * sending a lot of ordered chunks, but carefully withholding an early one.\n\t\t * It also has a watermark limit, which the caller can query is the number\n\t\t * of bytes is above that limit. This is used by the caller to be selective\n\t\t * in what to add to the reassembly queue, so that it's not exhausted. The\n\t\t * caller is expected to call `IsFull()` prior to adding data to the queue\n\t\t * and to act accordingly if the queue is full.\n\t\t */\n\t\tclass ReassemblyQueue\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * When the queue is filled over this fraction (of its maximum size), the\n\t\t\t * SCTP association should restrict incoming data to avoid filling up the\n\t\t\t * queue.\n\t\t\t */\n\t\t\tstatic constexpr float HighWatermarkLimit{ 0.9 };\n\n\t\tprivate:\n\t\t\tstruct DeferredResetStreams\n\t\t\t{\n\t\t\t\tDeferredResetStreams(Types::UnwrappedTsn senderLastAssignedTsn, std::set<uint16_t> streamIds)\n\t\t\t\t  : senderLastAssignedTsn(senderLastAssignedTsn), streamIds(std::move(streamIds))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tTypes::UnwrappedTsn senderLastAssignedTsn;\n\t\t\t\tstd::set<uint16_t> streamIds;\n\t\t\t\tstd::vector<std::function<void()>> deferredActions;\n\t\t\t\t// TODO: Once we upgrade to C++23, replace with:\n\t\t\t\t// std::vector<std::move_only_function<void()>> deferredActions;\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit ReassemblyQueue(size_t maxLengthBytes, bool useMessageInterleaving = false);\n\n\t\t\t~ReassemblyQueue();\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Adds a data chunk to the queue, with a `tsn` and other parameters in\n\t\t\t * `data`.\n\t\t\t */\n\t\t\tvoid AddData(uint32_t tsn, UserData data);\n\n\t\t\t/**\n\t\t\t * Indicates if the reassembly queue has any reassembled messages that can\n\t\t\t * be retrieved by calling `GetNextMessage()`.\n\t\t\t */\n\t\t\tbool HasMessages() const\n\t\t\t{\n\t\t\t\treturn !this->reassembledMessages.empty();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the number of reassembled messages that are ready to be\n\t\t\t * retrieved by calling `GetNextMessage()`.\n\t\t\t */\n\t\t\tsize_t GetMessagesReadyCount() const\n\t\t\t{\n\t\t\t\treturn this->reassembledMessages.size();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the next reassembled message or `std::nullopt` if there are no\n\t\t\t * messages ready.\n\t\t\t */\n\t\t\tstd::optional<Message> GetNextMessage();\n\n\t\t\t/**\n\t\t\t * Handles a FORWARD-TSN/I-FORWARD-TSN chunk, when the sender has indicated\n\t\t\t * that the received (this class) should forget about some chunks. This is\n\t\t\t * used to implement partial reliability.\n\t\t\t */\n\t\t\tvoid HandleForwardTsn(\n\t\t\t  uint32_t newCumulativeTsn, std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams);\n\n\t\t\t/**\n\t\t\t * Resets the provided streams and leaves deferred reset processing, if\n\t\t\t * enabled.\n\t\t\t */\n\t\t\tvoid ResetStreamsAndLeaveDeferredReset(std::span<const uint16_t> streamIds);\n\n\t\t\t/**\n\t\t\t * Enters deferred reset processing.\n\t\t\t */\n\t\t\tvoid EnterDeferredReset(uint32_t senderLastAssignedTsn, std::span<const uint16_t> streamIds);\n\n\t\t\t/**\n\t\t\t * The number of payload bytes that have been queued. Note that the actual\n\t\t\t * memory usage is higher due to additional overhead of tracking received\n\t\t\t * data.\n\t\t\t */\n\t\t\tsize_t GetQueuedBytes() const\n\t\t\t{\n\t\t\t\treturn this->queuedBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * The remaining bytes until the queue has reached the watermark limit.\n\t\t\t */\n\t\t\tsize_t GetRemainingBytes() const\n\t\t\t{\n\t\t\t\treturn this->watermarkBytes - this->queuedBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Indicates if the queue is full. Data should not be added to the queue\n\t\t\t * when it's full.\n\t\t\t */\n\t\t\tbool IsFull() const\n\t\t\t{\n\t\t\t\treturn this->queuedBytes >= this->maxLengthBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Indicates if the queue is above the watermark limit, which is a certain\n\t\t\t * percentage of its size.\n\t\t\t */\n\t\t\tbool IsAboveWatermark() const\n\t\t\t{\n\t\t\t\treturn this->queuedBytes >= this->watermarkBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the watermark limit, in bytes.\n\t\t\t */\n\t\t\tsize_t GetWatermarkBytes() const\n\t\t\t{\n\t\t\t\treturn this->watermarkBytes;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::unique_ptr<ReassemblyStreamsInterface> CreateReassemblyStreams(\n\t\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage,\n\t\t\t  bool useMessageInterleaving);\n\n\t\t\tvoid AddReassembledMessage(std::span<const Types::UnwrappedTsn> tsns, Message message);\n\n\t\t\tvoid AssertIsConsistent() const;\n\n\t\tprivate:\n\t\t\tconst size_t maxLengthBytes;\n\t\t\tconst size_t watermarkBytes;\n\t\t\tTypes::UnwrappedTsn::Unwrapper tsnUnwrapper;\n\t\t\t// Messages that have been reassembled, and will be consumed from by\n\t\t\t// `GetNextMessage()`.\n\t\t\tstd::deque<Message> reassembledMessages;\n\t\t\t// If present, \"deferred reset processing\" mode is active.\n\t\t\tstd::optional<DeferredResetStreams> deferredResetStreams;\n\t\t\t// The number of \"payload bytes\" that are in this queue, in total.\n\t\t\tsize_t queuedBytes = 0;\n\t\t\t// The actual implementation of ReassemblyStreams.\n\t\t\tstd::unique_ptr<ReassemblyStreamsInterface> reassemblyStreams;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/rx/ReassemblyStreamsInterface.hpp",
    "content": "#ifndef MS_RTC_SCTP_REASSEMBLY_STREAMS_INTERFACE_HPP\n#define MS_RTC_SCTP_REASSEMBLY_STREAMS_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <span>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Implementations of this interface will be called when data is received,\n\t\t * when data should be skipped/forgotten or when sequence number should be\n\t\t * reset.\n\t\t *\n\t\t * As a result of these operations - mainly when data is received - the\n\t\t * implementations of this interface should notify when a message has been\n\t\t * assembled, by calling the provided callback of type `OnAssembledMessage()`.\n\t\t * How it assembles messages will depend on e.g. if a message was sent on an\n\t\t * ordered or unordered stream.\n\t\t *\n\t\t * Implementations will - for each operation - indicate how much additional\n\t\t * memory that has been used as a result of performing the operation. This\n\t\t * is used to limit the maximum amount of memory used, to prevent\n\t\t * out-of-memory situations.\n\t\t */\n\t\tclass ReassemblyStreamsInterface\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * This callback will be provided as an argument to the constructor of the\n\t\t\t * concrete class implementing this interface and should be called when a\n\t\t\t * message has been assembled as well as indicating from which TSNs this\n\t\t\t * message was assembled from.\n\t\t\t */\n\t\t\tusing OnAssembledMessage =\n\t\t\t  std::function<void(std::span<const Types::UnwrappedTsn> tsns, Message message)>;\n\n\t\tpublic:\n\t\t\tvirtual ~ReassemblyStreamsInterface() = default;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Adds a data chunk to a stream as identified in `data`. If it was the\n\t\t\t * last remaining chunk in a message, reassemble one (or several, in case\n\t\t\t * of ordered chunks) messages.\n\t\t\t *\n\t\t\t * Returns the additional number of bytes added to the queue as a result\n\t\t\t * of performing this operation. If this addition resulted in messages\n\t\t\t * being assembled and delivered, this may be negative.\n\t\t\t */\n\t\t\tvirtual int32_t AddData(Types::UnwrappedTsn tsn, UserData data) = 0;\n\n\t\t\t/**\n\t\t\t * Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks - when the sender\n\t\t\t * wishes the received to skip/forget about data up until the provided\n\t\t\t * TSN. This is used to implement partial reliability, such as limiting\n\t\t\t * the number of retransmissions or the an expiration duration. As a\n\t\t\t * result of skipping data, this may result in the implementation being\n\t\t\t * able to assemble messages in ordered streams.\n\t\t\t *\n\t\t\t * Returns the number of bytes removed from the queue as a result of this\n\t\t\t * operation.\n\t\t\t */\n\t\t\tvirtual size_t HandleForwardTsn(\n\t\t\t  Types::UnwrappedTsn newCumulativeTsn,\n\t\t\t  std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams) = 0;\n\n\t\t\t/**\n\t\t\t * Called for incoming (possibly deferred) RE-CONFIG chunks asking for\n\t\t\t * either a few streams, or all streams (when the list is empty) to be\n\t\t\t * reset - to have their next SSN or Message ID to be zero.\n\t\t\t */\n\t\t\tvirtual void ResetStreams(std::span<const uint16_t> streamIds) = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/rx/TraditionalReassemblyStreams.hpp",
    "content": "#ifndef MS_RTC_SCTP_TRADITIONAL_REASSEMBLY_STREAMS_HPP\n#define MS_RTC_SCTP_TRADITIONAL_REASSEMBLY_STREAMS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyStreamsInterface.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Handles reassembly of incoming data when interleaved message sending is\n\t\t * not enabled on the association, i.e. when RFC 8260 is not in use and\n\t\t * RFC 9260 is to be followed.\n\t\t *\n\t\t * In other words, this class handles data received via DATA chunks.\n\t\t */\n\t\tclass TraditionalReassemblyStreams : public ReassemblyStreamsInterface\n\t\t{\n\t\tprivate:\n\t\t\tusing ChunkMap = std::map<Types::UnwrappedTsn, UserData>;\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Base class for `UnorderedStream` and `OrderedStream` classes.\n\t\t\t */\n\t\t\tclass StreamBase\n\t\t\t{\n\t\t\tprotected:\n\t\t\t\texplicit StreamBase(TraditionalReassemblyStreams* parent) : parent(*parent)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tsize_t AssembleMessage(ChunkMap::iterator start, ChunkMap::iterator end);\n\n\t\t\t\tsize_t AssembleMessage(Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\tprotected:\n\t\t\t\tTraditionalReassemblyStreams& parent;\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Manages all received data for a specific ordered stream, and assembles\n\t\t\t * messages when possible.\n\t\t\t */\n\t\t\tclass OrderedStream : StreamBase\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit OrderedStream(TraditionalReassemblyStreams* parent, uint16_t nextSsn = 0)\n\t\t\t\t  : StreamBase(parent), nextSsn(ssnUnwrapper.Unwrap(nextSsn))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tint32_t AddData(Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\t\tsize_t EraseTo(uint16_t ssn);\n\n\t\t\t\tvoid Reset()\n\t\t\t\t{\n\t\t\t\t\tthis->ssnUnwrapper.Reset();\n\t\t\t\t\tthis->nextSsn = this->ssnUnwrapper.Unwrap(uint16_t(0));\n\t\t\t\t}\n\n\t\t\t\tuint16_t GetNextSsn() const\n\t\t\t\t{\n\t\t\t\t\treturn this->nextSsn.Wrap();\n\t\t\t\t}\n\n\t\t\t\tbool HasUnassembledChunks() const\n\t\t\t\t{\n\t\t\t\t\treturn !chunksBySsn.empty();\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t/**\n\t\t\t\t * Try to assemble one message in order from the stream. Returns the\n\t\t\t\t * number of bytes assembled if a message was assembled.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessage();\n\n\t\t\t\t/**\n\t\t\t\t * Try to assemble several messages in order from the stream. Returns\n\t\t\t\t * the number of bytes assembled if a message was assembled.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessages();\n\n\t\t\t\t/**\n\t\t\t\t * Same as above but when inserting the first complete message avoid\n\t\t\t\t * insertion into the map.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessagesFastpath(\n\t\t\t\t  Types::UnwrappedSsn ssn, Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\tprivate:\n\t\t\t\t// NOTE: This must be an ordered container to be able to iterate in SSN\n\t\t\t\t// order.\n\t\t\t\tstd::map<Types::UnwrappedSsn, ChunkMap> chunksBySsn;\n\t\t\t\tTypes::UnwrappedSsn::Unwrapper ssnUnwrapper;\n\t\t\t\tTypes::UnwrappedSsn nextSsn;\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Manages all received data for a specific unordered stream, and assembles\n\t\t\t *  messages when possible.\n\t\t\t */\n\t\t\tclass UnorderedStream : StreamBase\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit UnorderedStream(TraditionalReassemblyStreams* parent) : StreamBase(parent)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tint32_t AddData(Types::UnwrappedTsn tsn, UserData data);\n\n\t\t\t\t/**\n\t\t\t\t * Returns the number of bytes removed from the queue.\n\t\t\t\t */\n\t\t\t\tsize_t EraseTo(Types::UnwrappedTsn tsn);\n\n\t\t\t\tbool HasUnassembledChunks() const\n\t\t\t\t{\n\t\t\t\t\treturn !this->chunks.empty();\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t/**\n\t\t\t\t * Given an iterator to any chunk within the map, try to assemble a\n\t\t\t\t * message containing it and - if successful - erase those chunks from\n\t\t\t\t * the stream chunks map.\n\t\t\t\t *\n\t\t\t\t * Returns the number of bytes that were assembled.\n\t\t\t\t */\n\t\t\t\tsize_t TryToAssembleMessage(ChunkMap::iterator it);\n\n\t\t\t\t/**\n\t\t\t\t * Given a map (`chunks`) and an iterator to within that map (`it`),\n\t\t\t\t * this method will return an iterator to the first chunk in that\n\t\t\t\t * message, which has the `isBeginning` flag set. If there are any gaps,\n\t\t\t\t * or if the beginning can't be found, `std::nullopt` is returned.\n\t\t\t\t */\n\t\t\t\tstd::optional<std::map<Types::UnwrappedTsn, UserData>::iterator> FindBeginning(\n\t\t\t\t  std::map<Types::UnwrappedTsn, UserData>::iterator it);\n\n\t\t\t\t/**\n\t\t\t\t * Given a map (`chunks`) and an iterator to within that map (`it`),\n\t\t\t\t * this method will return an iterator to the chunk after the last chunk\n\t\t\t\t * in that message, which has the `isEnd` flag set. If there are any\n\t\t\t\t * gaps, or if the end can't be found, `std::nullopt` is returned.\n\t\t\t\t */\n\t\t\t\tstd::optional<std::map<Types::UnwrappedTsn, UserData>::iterator> FindEnd(\n\t\t\t\t  std::map<Types::UnwrappedTsn, UserData>::iterator it);\n\n\t\t\tprivate:\n\t\t\t\tChunkMap chunks;\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit TraditionalReassemblyStreams(\n\t\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage);\n\n\t\tpublic:\n\t\t\tint32_t AddData(Types::UnwrappedTsn tsn, UserData data) override;\n\n\t\t\tsize_t HandleForwardTsn(\n\t\t\t  Types::UnwrappedTsn newCumulativeTsn,\n\t\t\t  std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams) override;\n\n\t\t\tvoid ResetStreams(std::span<const uint16_t> streamIds) override;\n\n\t\tprivate:\n\t\t\t// Callback for when a message has been assembled.\n\t\t\tconst OnAssembledMessage onAssembledMessage;\n\t\t\t// All unordered and ordered streams, managing not-yet-assembled data.\n\t\t\tstd::map<uint16_t, UnorderedStream> unorderedStreams;\n\t\t\tstd::map<uint16_t, OrderedStream> orderedStreams;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/OutstandingData.hpp",
    "content": "#ifndef MS_RTC_SCTP_OUTSTANDING_DATA_HPP\n#define MS_RTC_SCTP_OUTSTANDING_DATA_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <deque>\n#include <ostream>\n#include <set>\n#include <span>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * This class keeps track of outstanding data Chunks (sent, not yet acked)\n\t\t * and handles acking, nacking, rescheduling and abandoning.\n\t\t *\n\t\t * Items are added to this queue as they are sent and will be removed when\n\t\t * the peer acks them using the cumulative TSN ack.\n\t\t */\n\t\tclass OutstandingData\n\t\t{\n#ifdef MS_TEST\n\t\tpublic:\n\t\t\t/**\n\t\t\t * State for DATA Chunks (message fragments) in the queue.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Used in tests.\n\t\t\t */\n\t\t\tenum class State : uint8_t\n\t\t\t{\n\t\t\t\t/**\n\t\t\t\t * The Chunk has been sent but not received yet (from the sender's point\n\t\t\t\t * of view, as no SACK has been received yet that reference this Chunk).\n\t\t\t\t */\n\t\t\t\tIN_FLIGHT,\n\t\t\t\t/**\n\t\t\t\t * A SACK has been received which explicitly marked this Chunk as missing.\n\t\t\t\t * It's now NACKED and may be retransmitted if NACKED enough times.\n\t\t\t\t */\n\t\t\t\tNACKED,\n\t\t\t\t/**\n\t\t\t\t * A Chunk that will be retransmitted when possible.\n\t\t\t\t */\n\t\t\t\tTO_BE_RETRANSMITTED,\n\t\t\t\t/**\n\t\t\t\t * A SACK has been received which explicitly marked this Chunk as\n\t\t\t\t * received.\n\t\t\t\t */\n\t\t\t\tACKED,\n\t\t\t\t/**\n\t\t\t\t * A Chunk whose message has expired or has been retransmitted too many\n\t\t\t\t * times (RFC3758). It will not be retransmitted anymore.\n\t\t\t\t */\n\t\t\t\tABANDONED,\n\t\t\t};\n#endif\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Contains variables scoped to a processing of an incoming SACK.\n\t\t\t */\n\t\t\tstruct AckInfo\n\t\t\t{\n\t\t\t\texplicit AckInfo(Types::UnwrappedTsn cumulativeTsnAck) : highestTsnAcked(cumulativeTsnAck)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Bytes acked by increasing `cumulativeTsnAck` and `gapAckBlocks`.\n\t\t\t\t */\n\t\t\t\tsize_t bytesAcked{ 0 };\n\n\t\t\t\t/**\n\t\t\t\t * Indicates if this SACK indicates that packet loss has occurred. Just\n\t\t\t\t * because a packet is missing in the SACK doesn't necessarily mean that\n\t\t\t\t * there is packet loss as that packet might be in-flight and received\n\t\t\t\t * out-of-order. But when it has been reported missing consecutive\n\t\t\t\t * times, it will eventually be considered \"lost\" and this will be set.\n\t\t\t\t */\n\t\t\t\tbool hasPacketLoss{ false };\n\n\t\t\t\t/**\n\t\t\t\t * Highest TSN Newly Acknowledged, an SCTP variable.\n\t\t\t\t */\n\t\t\t\tTypes::UnwrappedTsn highestTsnAcked;\n\n\t\t\t\t/**\n\t\t\t\t * The set of lifecycle IDs that were acked using `cumulativeTsnAck`.\n\t\t\t\t */\n\t\t\t\tstd::vector<uint64_t> ackedLifecycleIds;\n\n\t\t\t\t/**\n\t\t\t\t * The set of lifecycle IDs that were acked, but had been abandoned.\n\t\t\t\t */\n\t\t\t\tstd::vector<uint64_t> abandonedLifecycleIds;\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * A fragmented message's DATA Chunk while in the retransmission queue,\n\t\t\t * and its associated metadata.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This data structure has been optimized for size, by ordering fields\n\t\t\t *   to avoid unnecessary padding.\n\t\t\t */\n\t\t\tclass Item\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tenum class NackAction : uint8_t\n\t\t\t\t{\n\t\t\t\t\tNOTHING,\n\t\t\t\t\tRETRANSMIT,\n\t\t\t\t\tABANDON,\n\t\t\t\t};\n\n\t\t\tprivate:\n\t\t\tprivate:\n\t\t\t\tenum class Lifecycle : uint8_t\n\t\t\t\t{\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk is alive (sent, received, etc).\n\t\t\t\t\t */\n\t\t\t\t\tACTIVE,\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk is scheduled to be retransmitted, and will then\n\t\t\t\t\t * transition to become active.\n\t\t\t\t\t */\n\t\t\t\t\tTO_BE_RETRANSMITTED,\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk has been abandoned. This is a terminal state.\n\t\t\t\t\t */\n\t\t\t\t\tABANDONED\n\t\t\t\t};\n\n\t\t\t\tenum class AckState : uint8_t\n\t\t\t\t{\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk is in-flight.\n\t\t\t\t\t */\n\t\t\t\t\tUNACKED,\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk has been received and acknowledged.\n\t\t\t\t\t */\n\t\t\t\t\tACKED,\n\t\t\t\t\t/**\n\t\t\t\t\t * The Chunk has been nacked and is possibly lost.\n\t\t\t\t\t */\n\t\t\t\t\tNACKED\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tItem(\n\t\t\t\t  uint32_t outgoingMessageId,\n\t\t\t\t  UserData data,\n\t\t\t\t  uint64_t timeSentMs,\n\t\t\t\t  uint16_t maxRetransmissions,\n\t\t\t\t  uint64_t expiresAtMs,\n\t\t\t\t  std::optional<uint64_t> lifecycleId);\n\n\t\t\t\tItem(const Item&) = delete;\n\n\t\t\t\tItem& operator=(const Item&) = delete;\n\n\t\t\tpublic:\n\t\t\t\tuint32_t GetOutgoingMessageId() const\n\t\t\t\t{\n\t\t\t\t\treturn this->outgoingMessageId;\n\t\t\t\t}\n\n\t\t\t\tuint64_t GetTimeSentMs() const\n\t\t\t\t{\n\t\t\t\t\treturn this->timeSentMs;\n\t\t\t\t}\n\n\t\t\t\tconst UserData& GetData() const\n\t\t\t\t{\n\t\t\t\t\treturn this->data;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Acks an item.\n\t\t\t\t */\n\t\t\t\tvoid Ack();\n\n\t\t\t\t/**\n\t\t\t\t * Nacks an item. If it has been nacked enough times, or if\n\t\t\t\t * `retransmitNow` is set, it might be marked for retransmission. If\n\t\t\t\t * the item has reached its max retransmission value, it will instead\n\t\t\t\t * be abandoned. The action performed is indicated as return value.\n\t\t\t\t */\n\t\t\t\tNackAction Nack(bool retransmitNow);\n\n\t\t\t\t/**\n\t\t\t\t * Prepares the item to be retransmitted. Sets it as outstanding and\n\t\t\t\t * clears all nack counters.\n\t\t\t\t */\n\t\t\t\tvoid MarkAsRetransmitted();\n\n\t\t\t\t/**\n\t\t\t\t * Marks this item as abandoned.\n\t\t\t\t */\n\t\t\t\tvoid Abandon();\n\n\t\t\t\tbool IsOutstanding() const\n\t\t\t\t{\n\t\t\t\t\treturn this->ackState != AckState::ACKED && this->lifecycle == Lifecycle::ACTIVE;\n\t\t\t\t}\n\n\t\t\t\tbool IsAcked() const\n\t\t\t\t{\n\t\t\t\t\treturn this->ackState == AckState::ACKED;\n\t\t\t\t}\n\n\t\t\t\tbool IsNacked() const\n\t\t\t\t{\n\t\t\t\t\treturn this->ackState == AckState::NACKED;\n\t\t\t\t}\n\n\t\t\t\tbool IsAbandoned() const\n\t\t\t\t{\n\t\t\t\t\treturn this->lifecycle == Lifecycle::ABANDONED;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Indicates if this Chunk should be retransmitted.\n\t\t\t\t */\n\t\t\t\tbool ShouldBeRetransmitted() const\n\t\t\t\t{\n\t\t\t\t\treturn this->lifecycle == Lifecycle::TO_BE_RETRANSMITTED;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Indicates if this Chunk has ever been retransmitted.\n\t\t\t\t */\n\t\t\t\tbool HasBeenRetransmitted() const\n\t\t\t\t{\n\t\t\t\t\treturn this->numRetransmissions > 0;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Given the current time, and the current state of this DATA Chunk, it\n\t\t\t\t * will indicate if it has expired (SCTP Partial Reliability Extension).\n\t\t\t\t */\n\t\t\t\tbool HasExpired(uint64_t nowMs) const\n\t\t\t\t{\n\t\t\t\t\treturn (this->expiresAtMs != Types::ExpiresAtMsInfinite && this->expiresAtMs <= nowMs);\n\t\t\t\t}\n\n\t\t\t\tstd::optional<uint64_t> GetLifecycleId() const\n\t\t\t\t{\n\t\t\t\t\treturn this->lifecycleId;\n\t\t\t\t}\n\n\t\t\t\tconst uint32_t outgoingMessageId;\n\t\t\t\t// The actual data to send/retransmit.\n\t\t\t\tconst UserData data;\n\t\t\t\t// When the packet was sent, and placed in this queue.\n\t\t\t\tconst uint64_t timeSentMs;\n\t\t\t\t// If the message was sent with a maximum number of retransmissions,\n\t\t\t\t// this is set to that number. The value zero (0) means that it will\n\t\t\t\t// never be retransmitted.\n\t\t\t\tconst uint16_t maxRetransmissions;\n\t\t\t\t// At this exact millisecond, the item is considered expired. If the\n\t\t\t\t// message is not to be expired, this is set to the infinite future.\n\t\t\t\t// NOTE: If 0 it means infinite time.\n\t\t\t\tconst uint64_t expiresAtMs;\n\t\t\t\t// An optional lifecycle id, which may only be set for the last\n\t\t\t\t// fragment.\n\t\t\t\tconst std::optional<uint64_t> lifecycleId;\n\t\t\t\t// Indicates the life cycle status of this Chunk.\n\t\t\t\tLifecycle lifecycle{ Lifecycle::ACTIVE };\n\t\t\t\t// Indicates the presence of this Chunk, if it's in flight (UNACKED),\n\t\t\t\t// has been received (ACKED) or is possibly lost (NACKED).\n\t\t\t\tAckState ackState{ AckState::UNACKED };\n\t\t\t\t// The number of times the DATA Chunk has been nacked (by having\n\t\t\t\t// received a SACK which doesn't include it). Will be cleared on\n\t\t\t\t// retransmissions.\n\t\t\t\tuint8_t nackCount{ 0 };\n\t\t\t\t// The number of times the DATA Chunk has been retransmitted.\n\t\t\t\tuint16_t numRetransmissions{ 0 };\n\t\t\t};\n\n\t\tpublic:\n\t\t\tOutstandingData(\n\t\t\t  size_t dataChunkHeaderLength,\n\t\t\t  Types::UnwrappedTsn lastCumulativeTsnAck,\n\t\t\t  std::function<bool(uint16_t /*streamId*/, uint32_t /*outgoingMessageId*/)> discardFromSendQueue);\n\n\t\tpublic:\n\t\t\tAckInfo HandleSack(\n\t\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t\t  bool isInFastRecovery);\n\n\t\t\t/**\n\t\t\t * Returns as many of the Chunks that are eligible for fast retransmissions\n\t\t\t * and that would fit in a single packet of `maxLength`. The eligible\n\t\t\t * Chunks that didn't fit will be marked for (normal) retransmission and\n\t\t\t * will not be returned if this method is called again.\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> GetChunksToBeFastRetransmitted(size_t maxLength);\n\n\t\t\t/**\n\t\t\t * Given `maxLength` of space left in a packet, which Chunks can be added\n\t\t\t * to it?\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> GetChunksToBeRetransmitted(size_t maxLength);\n\n\t\t\t/**\n\t\t\t * How many inflight bytes there are, as sent on the wire as packets.\n\t\t\t */\n\t\t\tsize_t GetUnackedPacketBytes() const\n\t\t\t{\n\t\t\t\treturn this->unackedPacketBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * How many inflight bytes there are, counting only the payload.\n\t\t\t */\n\t\t\tsize_t GetUnackedPayloadBytes() const\n\t\t\t{\n\t\t\t\treturn this->unackedPayloadBytes;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the number of DATA Chunks that are in-flight (not acked or\n\t\t\t * nacked).\n\t\t\t */\n\t\t\tsize_t GetUnackedItems() const\n\t\t\t{\n\t\t\t\treturn this->unackedItems;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Given the current time `nowMs`, expire and abandon outstanding (sent\n\t\t\t * at least once) Chunks that have a limited lifetime.\n\t\t\t */\n\t\t\tvoid ExpireOutstandingChunks(uint64_t nowMs);\n\n\t\t\tbool IsEmpty() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.empty();\n\t\t\t}\n\n\t\t\tbool HasDataToBeFastRetransmitted() const\n\t\t\t{\n\t\t\t\treturn !this->toBeFastRetransmitted.empty();\n\t\t\t}\n\n\t\t\tbool HasDataToBeRetransmitted() const\n\t\t\t{\n\t\t\t\treturn !this->toBeRetransmitted.empty() || !this->toBeFastRetransmitted.empty();\n\t\t\t}\n\n\t\t\tTypes::UnwrappedTsn GetLastCumulativeTsnAck() const\n\t\t\t{\n\t\t\t\treturn this->lastCumulativeTsnAck;\n\t\t\t}\n\n\t\t\tTypes::UnwrappedTsn GetNextTsn() const\n\t\t\t{\n\t\t\t\treturn this->GetHighestOutstandingTsn().GetNextValue();\n\t\t\t}\n\n\t\t\tTypes::UnwrappedTsn GetHighestOutstandingTsn() const;\n\n\t\t\t/**\n\t\t\t * Schedules `data` to be sent, with the provided partial reliability\n\t\t\t * parameters. Returns the TSN if the item was actually added and\n\t\t\t * scheduled to be sent, and std::nullopt if it shouldn't be sent.\n\t\t\t */\n\t\t\tstd::optional<Types::UnwrappedTsn> Insert(\n\t\t\t  uint32_t outgoingMessageId,\n\t\t\t  const UserData& data,\n\t\t\t  uint64_t timeSentMs,\n\t\t\t  uint16_t maxRetransmissions         = Types::MaxRetransmitsNoLimit,\n\t\t\t  uint64_t expiresAtMs                = Types::ExpiresAtMsInfinite,\n\t\t\t  std::optional<uint64_t> lifecycleId = std::nullopt);\n\n\t\t\t/**\n\t\t\t * Nacks all outstanding data.\n\t\t\t */\n\t\t\tvoid NackAll();\n\n\t\t\t/**\n\t\t\t * Adds a FORWARD-TSN Chunk to the given Packet and returns it.\n\t\t\t */\n\t\t\tconst ForwardTsnChunk* AddForwardTsn(Packet* packet) const;\n\n\t\t\t/**\n\t\t\t * Adds an I-FORWARD-TSN Chunk to the given Packet and returns it.\n\t\t\t */\n\t\t\tconst IForwardTsnChunk* AddIForwardTsn(Packet* packet) const;\n\n\t\t\t/**\n\t\t\t * Given the current time and a TSN, it returns the measured RTT between\n\t\t\t * when the Chunk was sent and now. It takes into acccount Karn's\n\t\t\t * algorithm, so if the Chunk has ever been retransmitted, it will return\n\t\t\t * `std::nullopt`.\n\t\t\t */\n\t\t\tstd::optional<uint64_t> MeasureRtt(uint64_t nowMs, Types::UnwrappedTsn tsn) const;\n\n\t\t\t/**\n\t\t\t * Returns true if the next Chunk that is not acked by the peer has been\n\t\t\t * abandoned, which means that a FORWARD-TSN should be sent.\n\t\t\t */\n\t\t\tbool ShouldSendForwardTsn() const;\n\n\t\t\t/**\n\t\t\t * Called when an outgoing stream reset is sent, marking the last assigned\n\t\t\t * TSN as a breakpoint that a FORWARD-TSN shouldn't cross.\n\t\t\t */\n\t\t\tvoid BeginResetStreams();\n\n#ifdef MS_TEST\n\t\t\t/**\n\t\t\t * Returns the internal state of all queued Chunks.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Used in tests.\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, State>> GetChunkStatesForTesting() const;\n#endif\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Returns how large a Chunk will be, serialized, carrying the data.\n\t\t\t */\n\t\t\tsize_t GetSerializedChunkLength(const UserData& data) const;\n\n\t\t\tItem& GetItem(Types::UnwrappedTsn tsn);\n\n\t\t\tconst Item& GetItem(Types::UnwrappedTsn tsn) const;\n\n\t\t\t/**\n\t\t\t * Given a `cumulativeTsnAck` from an incoming SACK, will remove those\n\t\t\t * items in the retransmission queue up until this value and will update\n\t\t\t * `ackInfo` by setting `this->lastCumulativeTsnAck`.\n\t\t\t */\n\t\t\tvoid RemoveAcked(Types::UnwrappedTsn cumulativeTsnAck, AckInfo& ackInfo);\n\n\t\t\t/**\n\t\t\t * Will mark the Chunks covered by the `gapAckBlocks` from an incoming\n\t\t\t * SACK as \"acked\" and update `ackInfo` by adding new TSNs to\n\t\t\t * `this->cumulativeTsnAck`.\n\t\t\t */\n\t\t\tvoid AckGapBlocks(\n\t\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t\t  AckInfo& ackInfo);\n\n\t\t\t/**\n\t\t\t * Mark Chunks reported as \"missing\", as \"nacked\" or \"to be retransmitted\"\n\t\t\t * depending how many times this has happened. Only packets up until\n\t\t\t * `ackInfo.highestTsnAcked` (highest TSN newly acknowledged) are\n\t\t\t * nacked/retransmitted. The method will set `ackInfo.hasPacketLoss`.\n\t\t\t */\n\t\t\tvoid NackBetweenAckBlocks(\n\t\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t\t  bool isInFastRecovery,\n\t\t\t  bool cumulativeTsnAckedAdvanced,\n\t\t\t  AckInfo& ackInfo);\n\n\t\t\t/**\n\t\t\t * Process the acknowledgement of the Chunk referenced by `item` and\n\t\t\t * updates state in `ackInfo` and the object's state.\n\t\t\t */\n\t\t\tvoid AckChunk(AckInfo& ackInfo, Types::UnwrappedTsn tsn, Item& item);\n\n\t\t\t/**\n\t\t\t * Helper method to process an incoming nack of an item and perform the\n\t\t\t * correct operations given the action indicated when nacking an item\n\t\t\t * (e.g. retransmitting or abandoning). The return value indicate if an\n\t\t\t * action was performed, meaning that packet loss was detected and acted\n\t\t\t * upon. If `doFastRetransmit` is set and if the item has been nacked\n\t\t\t * sufficiently many times so that it should be retransmitted, this will\n\t\t\t * schedule it to be \"fast retransmitted\". This is only done just before\n\t\t\t * going into fast recovery.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Note that since nacking an item may result in it becoming abandoned,\n\t\t\t *   which in turn could alter `this->outstandingData`, any iterators are\n\t\t\t *   invalidated after having called this method.\n\t\t\t */\n\t\t\tbool NackItem(Types::UnwrappedTsn tsn, bool retransmitNow, bool doFastRetransmit);\n\n\t\t\t/**\n\t\t\t * Given that a message fragment, `item` has been abandoned, abandon all\n\t\t\t * other fragments that share the same message - both never-before-sent\n\t\t\t * fragments that are still in the SendQueue and outstanding Chunks.\n\t\t\t */\n\t\t\tvoid AbandonAllFor(const OutstandingData::Item& item);\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> ExtractChunksThatCanFit(\n\t\t\t  std::set<Types::UnwrappedTsn>& chunks, size_t maxLength);\n\n\t\t\tvoid AssertIsConsistent() const;\n\n\t\tprivate:\n\t\t\t// The size of the data Chunk (DATA/I-DATA) header that is used.\n\t\t\tconst size_t dataChunkHeaderLength;\n\t\t\t// The last cumulative TSN ack number.\n\t\t\tTypes::UnwrappedTsn lastCumulativeTsnAck;\n\t\t\t// Callback when to discard items from the send queue.\n\t\t\tstd::function<bool(uint16_t /*streamId*/, uint32_t /*outgoingMessageId*/)> discardFromSendQueue;\n\t\t\t// Outstanding items. If non-empty, the first element has\n\t\t\t// `TSN=this->lastCumulativeTsnAck_ + 1` and the following items are in\n\t\t\t// strict increasing TSN order. The last item has\n\t\t\t// `TSN=GetHighestOutstandingTsn()`.\n\t\t\tstd::deque<Item> outstandingData;\n\t\t\t// The number of bytes that are in-flight, counting only the payload.\n\t\t\tsize_t unackedPayloadBytes{ 0 };\n\t\t\t// The number of bytes that are in-flight, as sent on the wire (as\n\t\t\t// packets).\n\t\t\tsize_t unackedPacketBytes{ 0 };\n\t\t\t// The number of DATA Chunks that are in-flight (sent but not yet acked\n\t\t\t// or nacked).\n\t\t\tsize_t unackedItems{ 0 };\n\t\t\t// Data Chunks that are eligible for fast retransmission.\n\t\t\tstd::set<Types::UnwrappedTsn> toBeFastRetransmitted;\n\t\t\t// Data Chunks that are to be retransmitted.\n\t\t\tstd::set<Types::UnwrappedTsn> toBeRetransmitted;\n\t\t\t// Wben a stream reset has begun, the \"next TSN to assign\" is added to\n\t\t\t// this set, and removed when the cum-ack TSN reaches it. This is used\n\t\t\t// to limit a FORWARD-TSN to reset streams past a \"stream reset last\n\t\t\t// assigned TSN\".\n\t\t\t// NOTE: dcsctp uses `webrtc::flat_set<UnwrappedTSN>` type which is more\n\t\t\t// efficient in read operations.\n\t\t\tstd::set<Types::UnwrappedTsn> streamResetBreakpointTsns;\n\t\t};\n\n#ifdef MS_TEST\n\t\t/**\n\t\t * For logging purposes in Catch2 tests.\n\t\t */\n\t\tinline std::ostream& operator<<(std::ostream& os, OutstandingData::State state)\n\t\t{\n\t\t\tswitch (state)\n\t\t\t{\n\t\t\t\tcase OutstandingData::State::IN_FLIGHT:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"IN_FLIGHT\";\n\t\t\t\t}\n\n\t\t\t\tcase OutstandingData::State::NACKED:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"NACKED\";\n\t\t\t\t}\n\n\t\t\t\tcase OutstandingData::State::TO_BE_RETRANSMITTED:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"TO_BE_RETRANSMITTED\";\n\t\t\t\t}\n\n\t\t\t\tcase OutstandingData::State::ACKED:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"ACKED\";\n\t\t\t\t}\n\n\t\t\t\tcase OutstandingData::State::ABANDONED:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"ABANDONED\";\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\treturn os << \"UNKNOWN(\" << static_cast<int>(state) << \")\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * For Catch2 to print it nicely.\n\t\t */\n\t\tinline std::ostream& operator<<(\n\t\t  std::ostream& os, const std::pair<uint32_t, OutstandingData::State>& s)\n\t\t{\n\t\t\treturn os << \"{tsn:\" << s.first << \", state:\" << s.second << \"}\";\n\t\t}\n#endif\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/RetransmissionErrorCounter.hpp",
    "content": "#ifndef MS_RTC_SCTP_RETRANSMISSION_ERROR_COUNTER_HPP\n#define MS_RTC_SCTP_RETRANSMISSION_ERROR_COUNTER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * The RetransmissionErrorCounter is a simple counter with a limit, and when\n\t\t * the limit is exceeded, the counter is exhausted and the Association will\n\t\t * be closed. It's incremented on retransmission errors, such as the T3-RTX\n\t\t * timer expiring, but also missing heartbeats and stream reset requests.\n\t\t */\n\t\tclass RetransmissionErrorCounter\n\t\t{\n\t\tpublic:\n\t\t\tRetransmissionErrorCounter(const SctpOptions& sctpOptions);\n\n\t\t\t~RetransmissionErrorCounter();\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\t/**\n\t\t\t * Increments the retransmission timer. Returns `true` if the maximum\n\t\t\t * error count has been reached, `false` will be returned.\n\t\t\t */\n\t\t\tbool Increment(std::string_view reason);\n\n\t\t\t/**\n\t\t\t * Whether maximum error count has been reached.\n\t\t\t * @return [description]\n\t\t\t */\n\t\t\tbool IsExhausted() const\n\t\t\t{\n\t\t\t\treturn this->limit.has_value() && this->counter > this->limit.value();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Clears the retransmission errors.\n\t\t\t */\n\t\t\tvoid Clear();\n\n\t\t\t/**\n\t\t\t * Returns its current counter value.\n\t\t\t */\n\t\t\tsize_t GetCounter() const\n\t\t\t{\n\t\t\t\treturn this->counter;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::optional<uint16_t> limit;\n\t\t\tsize_t counter{ 0 };\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/RetransmissionQueue.hpp",
    "content": "#ifndef MS_RTC_SCTP_RETRANSMISSION_QUEUE_HPP\n#define MS_RTC_SCTP_RETRANSMISSION_QUEUE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/OutstandingData.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * The RetransmissionQueue manages all DATA/I-DATA chunks that are in-flight\n\t\t * and schedules them to be retransmitted if necessary. Chunks are\n\t\t * retransmitted when they have been lost for a number of consecutive SACKs,\n\t\t * or when the retransmission timer expires.\n\t\t *\n\t\t * As congestion control is tightly connected with the state of transmitted\n\t\t * packets, that's also managed here to limit the amount of data that is\n\t\t * in-flight (sent, but not yet acknowledged).\n\t\t */\n\t\tclass RetransmissionQueue\n\t\t{\n\t\tpublic:\n\t\t\tclass Listener\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~Listener() = default;\n\n\t\t\tpublic:\n\t\t\t\tvirtual void OnRetransmissionQueueNewRttMs(uint64_t newRttMs)  = 0;\n\t\t\t\tvirtual void OnRetransmissionQueueClearRetransmissionCounter() = 0;\n\t\t\t};\n\n\t\tprivate:\n\t\t\tenum class CongestionAlgorithmPhase : uint8_t\n\t\t\t{\n\t\t\t\tSLOW_START,\n\t\t\t\tCONGESTION_AVOIDANCE,\n\t\t\t};\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Creates a RetransmissionQueue which will send data using\n\t\t\t * `localInitialTsn` as the first TSN to use for sent fragments. It will\n\t\t\t * poll data from `sendQueue`. When SACKs are received, it will estimate\n\t\t\t * the RTT and call `listener->OnRetransmissionQueueNewRttMs()`. When an\n\t\t\t * outstanding chunk has been acked, it will call\n\t\t\t * `listener->OnRetransmissionQueueClearRetransmissionCounter() and will\n\t\t\t * also use `t3RtxTimer`, which is the SCTP retransmission timer to manage\n\t\t\t * retransmissions.\n\t\t\t */\n\t\t\tRetransmissionQueue(\n\t\t\t  Listener* listener,\n\t\t\t  AssociationListenerInterface& associationListener,\n\t\t\t  uint32_t localInitialTsn,\n\t\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t\t  SendQueueInterface& sendQueue,\n\t\t\t  BackoffTimerHandleInterface* t3RtxTimer,\n\t\t\t  const SctpOptions& sctpOptions,\n\t\t\t  bool supportsPartialReliability,\n\t\t\t  bool useMessageInterleaving);\n\n\t\t\t~RetransmissionQueue();\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Handles a received SACK. Returns true if the SACK was processed and\n\t\t\t * false if it was discarded due to received out-of-order and not relevant.\n\t\t\t */\n\t\t\tbool HandleReceivedSackChunk(uint64_t nowMs, const SackChunk* receivedSackChunk);\n\n\t\t\t/**\n\t\t\t * Handles an expired retransmission timer.\n\t\t\t */\n\t\t\tvoid HandleT3RtxTimerExpiry();\n\n\t\t\tbool HasDataToBeFastRetransmitted() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.HasDataToBeFastRetransmitted();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns a list of Chunks to \"fast retransmit\" that would fit in\n\t\t\t * `maxLength` (bytes). The current value of `cwnd` is ignored.\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> GetChunksForFastRetransmit(size_t maxLength);\n\n\t\t\t/**\n\t\t\t * Returns a list of Chunks to send that would fit in `maxLength`\n\t\t\t * (bytes). This may be further limited by the congestion control windows.\n\t\t\t * Note that `ShouldSendForwardTsn()` must be called prior to this method,\n\t\t\t * to abandon expired Chunks, as this method will not expire any Chunks.\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> GetChunksToSend(\n\t\t\t  uint64_t nowMs, size_t maxLength);\n\n#ifdef MS_TEST\n\t\t\t/**\n\t\t\t * Returns the internal state of all queued Chunks.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - Used in tests.\n\t\t\t */\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, OutstandingData::State>> GetChunkStatesForTesting() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.GetChunkStatesForTesting();\n\t\t\t}\n#endif\n\n\t\t\t/**\n\t\t\t * Returns the next TSN that will be allocated for sent DATA Chunks.\n\t\t\t */\n\t\t\tuint32_t GetNextTsn() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.GetNextTsn().Wrap();\n\t\t\t}\n\n\t\t\tuint32_t GetLastAssignedTsn() const\n\t\t\t{\n\t\t\t\treturn Types::UnwrappedTsn::AddTo(this->outstandingData.GetNextTsn(), -1).Wrap();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the size of the congestion window, in bytes. This is the number\n\t\t\t * of bytes that may be in-flight.\n\t\t\t */\n\t\t\tsize_t GetCwnd() const\n\t\t\t{\n\t\t\t\treturn this->cwnd;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Overrides the current congestion window size.\n\t\t\t */\n\t\t\tvoid SetCwnd(size_t cwnd)\n\t\t\t{\n\t\t\t\tthis->cwnd = cwnd;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the current receiver window size.\n\t\t\t */\n\t\t\tsize_t GetRwnd() const\n\t\t\t{\n\t\t\t\treturn this->rwnd;\n\t\t\t}\n\n\t\t\tsize_t GetRtxPacketsCount() const\n\t\t\t{\n\t\t\t\treturn this->rtxPacketsCount;\n\t\t\t}\n\n\t\t\tuint64_t GetRtxBytesCount() const\n\t\t\t{\n\t\t\t\treturn this->rtxBytesCount;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * How many inflight bytes there are, as sent on the wire as packets.\n\t\t\t */\n\t\t\tsize_t GetUnackedPacketBytes() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.GetUnackedPacketBytes();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the number of DATA/I-DATA chunks that are in-flight.\n\t\t\t */\n\t\t\tsize_t GetUnackedItems() const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.GetUnackedItems();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Given the current time `nowMs`, it will evaluate if there are Chunks\n\t\t\t * that have expired and that need to be discarded. It returns true if a\n\t\t\t * FORWARD-TSN should be sent.\n\t\t\t */\n\t\t\tbool ShouldSendForwardTsn(uint64_t nowMs);\n\n\t\t\t/**\n\t\t\t * Adds a FORWARD-TSN Chunk to the given Packet and returns it.\n\t\t\t */\n\t\t\tconst ForwardTsnChunk* AddForwardTsn(Packet* packet) const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.AddForwardTsn(packet);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Adds an I-FORWARD-TSN Chunk to the given Packet and returns it.\n\t\t\t */\n\t\t\tconst IForwardTsnChunk* AddIForwardTsn(Packet* packet) const\n\t\t\t{\n\t\t\t\treturn this->outstandingData.AddIForwardTsn(packet);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @see SendQueueInterface for a longer description of these methods\n\t\t\t * related to stream resetting.\n\t\t\t */\n\t\t\tvoid PrepareResetStream(uint16_t streamId);\n\t\t\tbool HasStreamsReadyToBeReset() const;\n\t\t\tstd::vector<uint16_t /*streamId*/> BeginResetStreams();\n\t\t\tvoid CommitResetStreams();\n\t\t\tvoid RollbackResetStreams();\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Returns how large a chunk will be, serialized, carrying the data.\n\t\t\t */\n\t\t\tsize_t GetSerializedChunkLength(const UserData& data) const;\n\n\t\t\t/**\n\t\t\t * Indicates if the congestion control algorithm is in \"fast recovery\".\n\t\t\t */\n\t\t\tbool IsInFastRecovery() const\n\t\t\t{\n\t\t\t\treturn this->fastRecoveryExitTsn.has_value();\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Indicates if the provided SACK Chunk is valid given what has previously\n\t\t\t * been received. If it returns false, the SACK is most likely a duplicate\n\t\t\t * of something already seen, so this returning false doesn't necessarily\n\t\t\t * mean that the SACK is illegal.\n\t\t\t */\n\t\t\tbool IsSackChunkValid(const SackChunk* sackChunk) const;\n\n\t\t\t/**\n\t\t\t * When a SACK Chunk is received, this method will be called which may\n\t\t\t * call into the `RetransmissionTimeout` to update the RTO.\n\t\t\t */\n\t\t\tvoid UpdateRttMs(uint64_t nowMs, Types::UnwrappedTsn cumulativeTsnAck);\n\n\t\t\t/**\n\t\t\t * If the congestion control is in \"fast recovery mode\", this may be\n\t\t\t * exited now.\n\t\t\t */\n\t\t\tvoid MayExitFastRecovery(Types::UnwrappedTsn cumulativeTsnAck);\n\n\t\t\t/**\n\t\t\t * If Chunks have been ACKed, stop the retransmission timer.\n\t\t\t *\n\t\t\t * @remarks\n\t\t\t * - This method is NOT defined in dcsctp! See bug report:\n\t\t\t *   https://issues.webrtc.org/issues/505751236\n\t\t\t */\n\t\t\tvoid StopT3RtxTimerOnIncreasedCumulativeTsnAck(Types::UnwrappedTsn cumulativeTsnAck);\n\n\t\t\t/**\n\t\t\t * Update the congestion control algorithm given as the cumulative ack TSN\n\t\t\t * value has increased, as reported in an incoming SACK Chunk.\n\t\t\t */\n\t\t\tvoid HandleIncreasedCumulativeTsnAck(size_t unackedPacketBytes, size_t totalBytesAcked);\n\n\t\t\t/**\n\t\t\t * Update the congestion control algorithm, given as packet loss has been\n\t\t\t * detected, as reported in an incoming SACK Chunk.\n\t\t\t */\n\t\t\tvoid HandlePacketLoss(Types::UnwrappedTsn highestTsnAcked);\n\n\t\t\t/**\n\t\t\t * Update the view of the receiver window size.\n\t\t\t */\n\t\t\tvoid UpdateReceiverWindow(uint32_t aRwnd);\n\n\t\t\t/**\n\t\t\t * If there is data sent and not acked, ensure that the retransmission\n\t\t\t * timer is running.\n\t\t\t */\n\t\t\tvoid StartT3RtxTimerIfOutstandingData();\n\n\t\t\t/**\n\t\t\t * Returns the current congestion control algorithm phase.\n\t\t\t */\n\t\t\tCongestionAlgorithmPhase GetCongestionAlgorithmPhase() const\n\t\t\t{\n\t\t\t\treturn (this->cwnd <= this->ssthresh) ? CongestionAlgorithmPhase::SLOW_START\n\t\t\t\t                                      : CongestionAlgorithmPhase::CONGESTION_AVOIDANCE;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tListener* listener;\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t\tconst SctpOptions sctpOptions;\n\t\t\t// If the peer supports RFC3758 \"SCTP Partial Reliability Extension\".\n\t\t\tbool supportsPartialReliability;\n\t\t\t// The length of the data chunk (DATA/I-DATA) header that is used.\n\t\t\tconst size_t dataChunkHeaderLength;\n\t\t\t// The retransmission timer.\n\t\t\tBackoffTimerHandleInterface* t3RtxTimer;\n\t\t\t// Unwraps TSNs.\n\t\t\tTypes::UnwrappedTsn::Unwrapper tsnUnwrapper;\n\t\t\t// Congestion Window. Number of bytes that may be in-flight (sent, not\n\t\t\t// acked).\n\t\t\tsize_t cwnd;\n\t\t\t// Receive Window. Number of bytes available in the receiver's RX buffer.\n\t\t\tsize_t rwnd;\n\t\t\t// Slow Start Threshold. See RFC 9260.\n\t\t\tsize_t ssthresh;\n\t\t\t// Partial Bytes Acked. See RFC 9260.\n\t\t\tsize_t partialBytesAcked{ 0 };\n\t\t\t// See `AssociationMetrics`.\n\t\t\tsize_t rtxPacketsCount{ 0 };\n\t\t\tuint64_t rtxBytesCount{ 0 };\n\t\t\t// If set, fast recovery is enabled until this TSN has been cumulative\n\t\t\t// acked.\n\t\t\tstd::optional<Types::UnwrappedTsn> fastRecoveryExitTsn{ std::nullopt };\n\t\t\t// The send queue.\n\t\t\tSendQueueInterface& sendQueue;\n\t\t\t// All the outstanding data Chunks that are in-flight and that have not\n\t\t\t// been cumulative acked. Note that it also contains chunks that have been\n\t\t\t// acked in gap-ack-blocks.\n\t\t\tOutstandingData outstandingData;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/RetransmissionTimeout.hpp",
    "content": "#ifndef MS_RTC_SCTP_RETRANSMISSION_TIMEOUT_HPP\n#define MS_RTC_SCTP_RETRANSMISSION_TIMEOUT_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * Manages updating of the Retransmission Timeout (RTO) SCTP variable, which\n\t\t * is used directly as the base timeout for T3-RTX and for other timers, such\n\t\t * as delayed ack.\n\t\t *\n\t\t * When a round-trip-time (RTT) is calculated (outside this class), the\n\t\t * `ObserveRttMs()` method is called, which calculates the retransmission\n\t\t * timeout (RTO) value. The RTO value will become larger if the RTT is high\n\t\t * and/or the RTT values are varying a lot, which is an indicator of a bad\n\t\t * connection.\n\t\t */\n\t\tclass RetransmissionTimeout\n\t\t{\n\t\tpublic:\n\t\t\texplicit RetransmissionTimeout(const SctpOptions& sctpOptions);\n\n\t\t\t~RetransmissionTimeout();\n\n\t\tpublic:\n\t\t\tvoid Dump(int indentation = 0) const;\n\n\t\t\t/**\n\t\t\t * To be called when a RTT (ms) has been measured, to update the RTO\n\t\t\t * value.\n\t\t\t */\n\t\t\tvoid ObserveRttMs(uint64_t rttMs);\n\n\t\t\t/**\n\t\t\t * Returns the Retransmission Timeout (RTO) value.\n\t\t\t */\n\t\t\tuint64_t GetRtoMs() const\n\t\t\t{\n\t\t\t\treturn this->rtoMs;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Returns the smoothed RTT value (ms)..\n\t\t\t */\n\t\t\tuint64_t GetSrttMs() const\n\t\t\t{\n\t\t\t\treturn this->srttMs;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tuint64_t minRtoMs;\n\t\t\tuint64_t maxRtoMs;\n\t\t\tuint64_t maxRttMs;\n\t\t\tuint64_t minRttVarianceMs;\n\t\t\tdouble srttMs;\n\t\t\tdouble rtoMs;\n\t\t\tdouble rttVarMs{ 0 };\n\t\t\tbool firstMeasurement{ false };\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/RoundRobinSendQueue.hpp",
    "content": "#ifndef MS_RTC_SCTP_ROUND_ROBIN_SEND_QUEUE_HPP\n#define MS_RTC_SCTP_ROUND_ROBIN_SEND_QUEUE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include \"RTC/SCTP/tx/StreamScheduler.hpp\"\n#include <deque>\n#include <map>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * The Round Robin send queue holds all messages that the client wants to\n\t\t * send, but that haven't yet been split into chunks and fully sent on the\n\t\t * wire.\n\t\t *\n\t\t * As defined in https://datatracker.ietf.org/doc/html/rfc8260#section-3.2,\n\t\t * it will cycle to send messages from different streams. It will send all\n\t\t * fragments from one message before continuing with a different message on\n\t\t * possibly a different stream, until support for message interleaving has\n\t\t * been implemented.\n\t\t *\n\t\t * As messages can be (requested to be) sent before the connection is\n\t\t * properly established, this send queue is always present - even for closed\n\t\t * connections.\n\t\t *\n\t\t * The send queue may trigger callbacks. `OnAssociationStreamBufferedAmountLow()`\n\t\t * and `OnAssociationTotalBufferedAmountLow()` will be triggered as defined in\n\t\t * their documentation. `OnAssociationLifecycleMessageExpired()` with\n\t\t * `maybeDelivered=false` and `OnAssociationLifecycleMessageEnd()` will be\n\t\t * triggered when messages have been expired, abandoned or discarded from the\n\t\t * send queue. If a message is fully produced, meaning that the last fragment\n\t\t * has been produced, the responsibility to send lifecycle events is then\n\t\t * transferred to the retransmission queue, which is the one asking to\n\t\t * produce the message.\n\t\t */\n\t\tclass RoundRobinSendQueue : public SendQueueInterface\n\t\t{\n\t\tprivate:\n\t\t\tstruct MessageAttributes\n\t\t\t{\n\t\t\t\tbool isUnordered;\n\t\t\t\tuint16_t maxRetransmissions;\n\t\t\t\tuint64_t expiresAtMs;\n\t\t\t\tstd::optional<uint64_t> lifecycleId;\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Represents a value and a \"low threshold\" that when the value reaches or\n\t\t\t * goes under the \"low threshold\", will trigger `onThresholdReached()`\n\t\t\t * callback.\n\t\t\t */\n\t\t\tclass ThresholdWatcher\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\texplicit ThresholdWatcher(std::function<void()> onThresholdReached)\n\t\t\t\t  : onThresholdReached(std::move(onThresholdReached))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Increases the value.\n\t\t\t\t */\n\t\t\t\tvoid Increase(size_t bytes)\n\t\t\t\t{\n\t\t\t\t\tthis->value += bytes;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Decreases the value and triggers `onThresholdReached()` if it's at\n\t\t\t\t * or below `this->lowThreshold`.\n\t\t\t\t */\n\t\t\t\tvoid Decrease(size_t bytes);\n\n\t\t\t\tsize_t GetValue() const\n\t\t\t\t{\n\t\t\t\t\treturn this->value;\n\t\t\t\t}\n\n\t\t\t\tsize_t GetLowThreshold() const\n\t\t\t\t{\n\t\t\t\t\treturn this->lowThreshold;\n\t\t\t\t}\n\n\t\t\t\tvoid SetLowThreshold(size_t lowThreshold);\n\n\t\t\tprivate:\n\t\t\t\tconst std::function<void()> onThresholdReached;\n\t\t\t\tsize_t value{ 0 };\n\t\t\t\tsize_t lowThreshold{ 0 };\n\t\t\t};\n\n\t\tprivate:\n\t\t\t/**\n\t\t\t * Per-stream information.\n\t\t\t */\n\t\t\tclass OutgoingStream : public StreamScheduler::StreamProducer\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tOutgoingStream(\n\t\t\t\t  RoundRobinSendQueue* parent,\n\t\t\t\t  StreamScheduler* scheduler,\n\t\t\t\t  uint16_t streamId,\n\t\t\t\t  uint16_t priority,\n\t\t\t\t  std::function<void()> onBufferedAmountLow)\n\t\t\t\t  : parent(*parent),\n\t\t\t\t    schedulerStream(scheduler->CreateStream(this, streamId, priority)),\n\t\t\t\t    bufferedAmountThresholdWatcher(std::move(onBufferedAmountLow))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tuint16_t GetStreamId() const\n\t\t\t\t{\n\t\t\t\t\treturn this->schedulerStream->GetStreamId();\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Enqueues a message to this stream.\n\t\t\t\t */\n\t\t\t\tvoid AddMessage(Message message, MessageAttributes attributes);\n\n\t\t\t\t// Implementing `StreamScheduler::StreamProducer`.\n\n\t\t\t\tstd::optional<SendQueueInterface::DataToSend> Produce(uint64_t nowMs, size_t maxLength) override;\n\n\t\t\t\tsize_t GetBytesToSendInNextMessage() const override;\n\n\t\t\t\tconst ThresholdWatcher& GetBufferedAmount() const\n\t\t\t\t{\n\t\t\t\t\treturn bufferedAmountThresholdWatcher;\n\t\t\t\t}\n\n\t\t\t\tThresholdWatcher& GetBufferedAmount()\n\t\t\t\t{\n\t\t\t\t\treturn bufferedAmountThresholdWatcher;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * Discards a partially sent message, see `SendQueue::Discard()`.\n\t\t\t\t */\n\t\t\t\tbool Discard(uint32_t outgoingMessageId);\n\n\t\t\t\t/**\n\t\t\t\t * Pauses this stream, which is used before resetting it.\n\t\t\t\t */\n\t\t\t\tvoid Pause();\n\n\t\t\t\t/**\n\t\t\t\t * Resumes a paused stream.\n\t\t\t\t */\n\t\t\t\tvoid Resume();\n\n\t\t\t\tbool IsReadyToBeReset() const\n\t\t\t\t{\n\t\t\t\t\treturn this->pauseState == PauseState::PAUSED;\n\t\t\t\t}\n\n\t\t\t\tbool IsResetting() const\n\t\t\t\t{\n\t\t\t\t\treturn this->pauseState == PauseState::RESETTING;\n\t\t\t\t}\n\n\t\t\t\tvoid SetAsResetting();\n\n\t\t\t\t/**\n\t\t\t\t * Resets this stream, meaning MIDs and SSNs are set to zero.\n\t\t\t\t */\n\t\t\t\tvoid Reset();\n\n\t\t\t\t/**\n\t\t\t\t * Indicates if this stream has a partially sent message in it.\n\t\t\t\t */\n\t\t\t\tbool HasPartiallySentMessage() const;\n\n\t\t\t\tuint16_t GetPriority() const\n\t\t\t\t{\n\t\t\t\t\treturn this->schedulerStream->GetPriority();\n\t\t\t\t}\n\n\t\t\t\tvoid SetPriority(uint16_t priority)\n\t\t\t\t{\n\t\t\t\t\tthis->schedulerStream->SetPriority(priority);\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t/**\n\t\t\t\t * Streams are paused before they can be reset. To reset a stream, the\n\t\t\t\t * socket sends an outgoing stream reset command with the TSN of the last\n\t\t\t\t * fragment of the last message, so that receivers and senders can agree\n\t\t\t\t * on when it stopped. And if the send queue is in the middle of sending\n\t\t\t\t * a message, and without fragments not yet sent and without TSNs\n\t\t\t\t * allocated to them, it will keep sending data until that message has\n\t\t\t\t * ended.\n\t\t\t\t */\n\t\t\t\tenum class PauseState : uint8_t\n\t\t\t\t{\n\t\t\t\t\t/**\n\t\t\t\t\t * The stream is not paused, and not scheduled to be reset.\n\t\t\t\t\t */\n\t\t\t\t\tNOT_PAUSED,\n\n\t\t\t\t\t/**\n\t\t\t\t\t * The stream has requested to be reset/paused but is still producing\n\t\t\t\t\t * fragments of a message that hasn't ended yet. When it does, it will\n\t\t\t\t\t * transition to the `PAUSED` state.\n\t\t\t\t\t */\n\t\t\t\t\tPENDING,\n\n\t\t\t\t\t/**\n\t\t\t\t\t * The stream is fully paused and can be reset.\n\t\t\t\t\t */\n\t\t\t\t\tPAUSED,\n\n\t\t\t\t\t/**\n\t\t\t\t\t * The stream has been added to an outgoing stream reset request and a\n\t\t\t\t\t * response from the peer hasn't been received yet.\n\t\t\t\t\t */\n\t\t\t\t\tRESETTING,\n\t\t\t\t};\n\n\t\t\t\t// An enqueued message and metadata.\n\t\t\t\tstruct Item\n\t\t\t\t{\n\t\t\t\t\texplicit Item(uint32_t outgoingMessageId, Message msg, MessageAttributes attributes)\n\t\t\t\t\t  : outgoingMessageId(outgoingMessageId),\n\t\t\t\t\t    message(std::move(msg)),\n\t\t\t\t\t    attributes(attributes),\n\t\t\t\t\t    remainingLength(message.GetPayloadLength())\n\t\t\t\t\t{\n\t\t\t\t\t}\n\n\t\t\t\t\tuint32_t outgoingMessageId;\n\t\t\t\t\tMessage message;\n\t\t\t\t\tMessageAttributes attributes;\n\t\t\t\t\t// The remaining payload (offset and length) to be sent, when it has\n\t\t\t\t\t// been fragmented.\n\t\t\t\t\tsize_t remainingOffset{ 0 };\n\t\t\t\t\tsize_t remainingLength;\n\t\t\t\t\t// If set, an allocated Message ID and SSN. Will be allocated when the\n\t\t\t\t\t// first fragment is sent.\n\t\t\t\t\tstd::optional<uint32_t> mid{ std::nullopt };\n\t\t\t\t\tstd::optional<uint16_t> ssn{ std::nullopt };\n\t\t\t\t\t// The current Fragment Sequence Number, incremented for each fragment.\n\t\t\t\t\tuint32_t currentFsn{ 0 };\n\t\t\t\t};\n\n\t\t\t\tvoid HandleMessageExpired(OutgoingStream::Item& item);\n\n\t\t\t\tvoid AssertIsConsistent() const;\n\n\t\t\tprivate:\n\t\t\t\tRoundRobinSendQueue& parent;\n\t\t\t\tconst std::unique_ptr<StreamScheduler::Stream> schedulerStream;\n\t\t\t\t// The current amount of buffered data.\n\t\t\t\tThresholdWatcher bufferedAmountThresholdWatcher;\n\t\t\t\tPauseState pauseState = PauseState::NOT_PAUSED;\n\t\t\t\t// MIDs are different for unordered and ordered messages sent on a\n\t\t\t\t// stream.\n\t\t\t\tuint32_t nextUnorderedMid{ 0 };\n\t\t\t\tuint32_t nextOrderedMid{ 0 };\n\t\t\t\tuint16_t nextSsn{ 0 };\n\t\t\t\t// Enqueued messages, and metadata.\n\t\t\t\tstd::deque<Item> items;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tRoundRobinSendQueue(\n\t\t\t  AssociationListenerInterface& associationListener,\n\t\t\t  size_t mtu,\n\t\t\t  uint16_t defaultPriority,\n\t\t\t  size_t totalBufferedAmountLowThreshold);\n\n\t\t\t~RoundRobinSendQueue() override;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Indicates if the buffer is empty.\n\t\t\t */\n\t\t\tbool IsEmpty() const\n\t\t\t{\n\t\t\t\treturn GetTotalBufferedAmount() == 0;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Adds the message to be sent using the `sendMessageOptions` provided.\n\t\t\t * The current time should be in `nowMs`. Note that it's the responsibility\n\t\t\t * of the caller to ensure that the buffer is not full (by calling\n\t\t\t * `IsFull()`) before adding messages to it.\n\t\t\t */\n\t\t\tvoid AddMessage(\n\t\t\t  uint64_t nowMs, Message message, const SendMessageOptions& sendMessageOptions = {});\n\n\t\t\tuint16_t GetStreamPriority(uint16_t streamId) const;\n\n\t\t\tvoid SetStreamPriority(uint16_t streamId, uint16_t priority);\n\n\t\t\t// Methods implementing `SendQueueInterface`.\n\t\tpublic:\n\t\t\tvoid EnableMessageInterleaving(bool enabled) override\n\t\t\t{\n\t\t\t\tthis->scheduler.EnableMessageInterleaving(enabled);\n\t\t\t}\n\n\t\t\tstd::optional<SendQueueInterface::DataToSend> Produce(uint64_t nowMs, size_t maxLength) override;\n\n\t\t\tbool Discard(uint16_t streamId, uint32_t outgoingMessageId) override;\n\n\t\t\tvoid PrepareResetStream(uint16_t streamId) override;\n\n\t\t\tbool HasStreamsReadyToBeReset() const override;\n\n\t\t\tstd::vector<uint16_t> GetStreamsReadyToBeReset() override;\n\n\t\t\tvoid CommitResetStreams() override;\n\n\t\t\tvoid RollbackResetStreams() override;\n\n\t\t\tvoid Reset() override;\n\n\t\t\tsize_t GetStreamBufferedAmount(uint16_t streamId) const override;\n\n\t\t\tsize_t GetTotalBufferedAmount() const override\n\t\t\t{\n\t\t\t\treturn this->totalBufferedAmountThresholdWatcher.GetValue();\n\t\t\t}\n\n\t\t\tsize_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const override;\n\n\t\t\tvoid SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) override;\n\n\t\tprivate:\n\t\t\tOutgoingStream& GetOrCreateStreamInfo(uint16_t streamId);\n\n\t\t\tvoid AssertIsConsistent() const;\n\n\t\tprivate:\n\t\t\tAssociationListenerInterface& associationListener;\n\t\t\tconst uint16_t defaultPriority;\n\t\t\tStreamScheduler scheduler;\n\t\t\t// The total amount of buffer data, for all streams.\n\t\t\tThresholdWatcher totalBufferedAmountThresholdWatcher;\n\t\t\tuint32_t currentOutgoingMessageId{ 0 };\n\t\t\t// All streams, and messages added to those.\n\t\t\tstd::map<uint16_t, OutgoingStream> streams;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/SendQueueInterface.hpp",
    "content": "#ifndef MS_RTC_SCTP_SEND_QUEUE_INTERFACE_HPP\n#define MS_RTC_SCTP_SEND_QUEUE_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tclass SendQueueInterface\n\t\t{\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Container for a data chunk that is produced by the send queue.\n\t\t\t */\n\t\t\tstruct DataToSend\n\t\t\t{\n\t\t\t\tDataToSend(uint32_t outgoingMessageId, UserData data)\n\t\t\t\t  : outgoingMessageId(outgoingMessageId), data(std::move(data))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tuint32_t outgoingMessageId;\n\n\t\t\t\t/**\n\t\t\t\t * The data to send, including all parameters.\n\t\t\t\t */\n\t\t\t\tUserData data;\n\n\t\t\t\t/**\n\t\t\t\t * Partial reliability (RFC 3758).\n\t\t\t\t */\n\t\t\t\tuint16_t maxRetransmissions{ Types::MaxRetransmitsNoLimit };\n\n\t\t\t\t/**\n\t\t\t\t * Time when it expires.\n\t\t\t\t */\n\t\t\t\tuint64_t expiresAtMs{ Types::ExpiresAtMsInfinite };\n\n\t\t\t\t/**\n\t\t\t\t * Lifecycle. Set for the last fragment and `std::nullopt` for all\n\t\t\t\t * other fragments.\n\t\t\t\t */\n\t\t\t\tstd::optional<uint64_t> lifecycleId;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tvirtual ~SendQueueInterface() = default;\n\n\t\t\t/**\n\t\t\t * Configures the send queue to support interleaved message sending as\n\t\t\t * described in RFC 8260. Every send queue starts with this value set as\n\t\t\t * disabled, but can later change it when the capabilities of the\n\t\t\t * connection have been negotiated. This affects the behavior of the\n\t\t\t * `Produce()` method.\n\t\t\t */\n\t\t\tvirtual void EnableMessageInterleaving(bool enabled) = 0;\n\n\t\t\t/**\n\t\t\t * Produce a chunk to be sent. `maxLength` refers to how many payload bytes\n\t\t\t * that may be produced, not including any headers.\n\t\t\t *\n\t\t\t * @todo\n\t\t\t * - As in dcsctp, this interface is obviously missing an \"AddMessage()\"\n\t\t\t *   method, but that is postponed a bit until the story around how to\n\t\t\t *   model message prioritization, which is important for any advanced\n\t\t\t *   stream scheduler, is further clarified.\n\t\t\t */\n\t\t\tvirtual std::optional<DataToSend> Produce(uint64_t nowMs, size_t maxLength) = 0;\n\n\t\t\t/**\n\t\t\t * Discards a partially sent message identified by the parameters `streamId`\n\t\t\t * and `outgoingMessageId`. The `outgoingMessageId` comes from the returned\n\t\t\t * information when having called `Produce()`. A partially sent message\n\t\t\t * means that it has had at least one fragment of it returned when\n\t\t\t * `Produce()` was called prior to calling this method.\n\t\t\t *\n\t\t\t * This is used when a message has been found to be expired (by the partial\n\t\t\t * reliability extension), and the retransmission queue will signal the\n\t\t\t * receiver that any partially received message fragments should be\n\t\t\t * skipped. This means that any remaining fragments in the send queue must\n\t\t\t * be removed as well so that they are not sent.\n\t\t\t *\n\t\t\t * This method returns true if this message had unsent fragments still in\n\t\t\t * the queue that were discarded, and false if there were no such\n\t\t\t * fragments.\n\t\t\t */\n\t\t\tvirtual bool Discard(uint16_t streamId, uint32_t outgoingMessageId) = 0;\n\n\t\t\t/**\n\t\t\t * Prepares the stream to be reset. This is used to close a SCTP stream\n\t\t\t * and will be signaled to the other side.\n\t\t\t *\n\t\t\t * Concretely, it discards all whole (not partly sent) messages in the\n\t\t\t * given stream and pauses that stream so that future added messages are\n\t\t\t * not produced until resumed.\n\t\t\t *\n\t\t\t * This method can be called multiple times to add more streams to be\n\t\t\t * reset, and paused while they are resetting. This is the first part of\n\t\t\t * the two-phase commit protocol to reset streams, where the caller\n\t\t\t * completes the procedure by either calling `CommitResetStreams()` or\n\t\t\t * `RollbackResetStreams()`.\n\t\t\t *\n\t\t\t * @todo\n\t\t\t * - As in dcsctp, investigate if it really should discard any message at\n\t\t\t *   all. RFC 8831 only mentions that \"RFC 6525 also guarantees that all\n\t\t\t *   the messages are delivered (or abandoned) before the stream is reset\".\n\t\t\t */\n\t\t\tvirtual void PrepareResetStream(uint16_t streamId) = 0;\n\n\t\t\t/**\n\t\t\t * Indicates if there are any streams that are ready to be reset.\n\t\t\t */\n\t\t\tvirtual bool HasStreamsReadyToBeReset() const = 0;\n\n\t\t\t/**\n\t\t\t * Returns a list of streams that are ready to be included in an outgoing\n\t\t\t * stream reset request. Any streams that are returned here must be\n\t\t\t * included in an outgoing stream reset request, and there must not be\n\t\t\t * concurrent requests.\n\t\t\t */\n\t\t\tvirtual std::vector<uint16_t> GetStreamsReadyToBeReset() = 0;\n\n\t\t\t/**\n\t\t\t * Called to commit to reset the streams returned by\n\t\t\t * `GetStreamsReadyToBeReset()`. It will reset the stream sequence numbers\n\t\t\t * (SSNs) and message identifiers (MIDs) and resume the paused streams.\n\t\t\t */\n\t\t\tvirtual void CommitResetStreams() = 0;\n\n\t\t\t/**\n\t\t\t * Called to abort the resetting of streams returned by\n\t\t\t * `GetStreamsReadyToBeReset()`. Will resume the paused streams without\n\t\t\t * resetting the stream sequence numbers (SSNs) or message identifiers\n\t\t\t * (MIDs). Note that the non-partial messages that were discarded when\n\t\t\t * calling `PrepareResetStreams()` will not be recovered, to better match\n\t\t\t * the intention from the sender to \"close the channel\".\n\t\t\t */\n\t\t\tvirtual void RollbackResetStreams() = 0;\n\n\t\t\t/**\n\t\t\t * Resets all message identifier counters (MID, SSN) and makes all\n\t\t\t * partially messages be ready to be re-sent in full. This is used when\n\t\t\t * the peer has been detected to have restarted and is used to try to\n\t\t\t * minimize the amount of data loss. However, data loss cannot be\n\t\t\t * completely guaranteed when a peer restarts.\n\t\t\t */\n\t\t\tvirtual void Reset() = 0;\n\n\t\t\t/**\n\t\t\t * Returns the amount of buffered data. This doesn't include packets that\n\t\t\t * are e.g. inflight.\n\t\t\t */\n\t\t\tvirtual size_t GetStreamBufferedAmount(uint16_t streamId) const = 0;\n\n\t\t\t/**\n\t\t\t * Returns the total amount of buffer data, for all streams.\n\t\t\t */\n\t\t\tvirtual size_t GetTotalBufferedAmount() const = 0;\n\n\t\t\t/**\n\t\t\t * Returns the limit for the `OnAssociationStreamBufferedAmountLow()`\n\t\t\t * event. Default value is 0.\n\t\t\t */\n\t\t\tvirtual size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const = 0;\n\n\t\t\t/**\n\t\t\t * Sets a limit for the `OnAssociationStreamBufferedAmountLow()` event.\n\t\t\t * @param streamId [description]\n\t\t\t * @param bytes    [description]\n\t\t\t */\n\t\t\tvirtual void SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) = 0;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SCTP/tx/StreamScheduler.hpp",
    "content": "#ifndef MS_RTC_SCTP_STREAM_SCHEDULER_HPP\n#define MS_RTC_SCTP_STREAM_SCHEDULER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include <set>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/**\n\t\t * A parameterized stream scheduler. Currently, it implements the round robin\n\t\t *  scheduling algorithm using virtual finish time. It is to be used as a\n\t\t *  part of a send queue and will track all active streams (streams that have\n\t\t *  any data that can be sent).\n\t\t *\n\t\t * The stream scheduler works with the concept of associating active streams\n\t\t * with a \"virtual finish time\", which is the time when a stream is allowed\n\t\t * to produce data. Streams are ordered by their virtual finish time, and\n\t\t * the \"current virtual time\" will advance to the next following virtual\n\t\t * finish time whenever a chunk is to be produced.\n\t\t *\n\t\t * In the round robin scheduling algorithm, a stream's virtual finish time\n\t\t * will just increment by one (1) after having produced a chunk, which\n\t\t * results in a round-robin scheduling.\n\t\t *\n\t\t * In WFQ scheduling algorithm, a stream's virtual finish time will be\n\t\t * defined as the number of bytes in the next fragment to be sent, multiplied\n\t\t * by theinverse of the stream's priority, meaning that a high priority - or\n\t\t * a smaller fragment - results in a closer virtual finish time, compared to\n\t\t * a stream with either a lower priority or a larger fragment to be sent.\n\t\t *\n\t\t * @remarks\n\t\t * - When message interleaving is enabled, the WFQ (Weighted Fair Queueing)\n\t\t * \t scheduling algorithm will be used. And when it's not, round-robin\n\t\t * \t scheduling will be used instead.\n\t\t */\n\t\tclass StreamScheduler\n\t\t{\n\t\tpublic:\n\t\t\tclass StreamProducer\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tvirtual ~StreamProducer() = default;\n\n\t\t\tpublic:\n\t\t\t\t/**\n\t\t\t\t * Produces a fragment of data to send. The current wall time is specified\n\t\t\t\t * as `nowMs` and should be used to skip chunks with expired limited\n\t\t\t\t * lifetime. The parameter `maxLength` specifies the maximum amount of\n\t\t\t\t * actual payload that may be returned. If these constraints prevents the\n\t\t\t\t * stream from sending some data, `std::nullopt` should be returned.\n\t\t\t\t */\n\t\t\t\tvirtual std::optional<SendQueueInterface::DataToSend> Produce(\n\t\t\t\t  uint64_t nowMs, size_t maxLength) = 0;\n\n\t\t\t\t/**\n\t\t\t\t * Returns the number of payload bytes that is scheduled to be sent in the\n\t\t\t\t * next enqueued message, or zero if there are no enqueued messages or if\n\t\t\t\t * the stream has been actively paused.\n\t\t\t\t */\n\t\t\t\tvirtual size_t GetBytesToSendInNextMessage() const = 0;\n\t\t\t};\n\n\t\tpublic:\n\t\t\tclass Stream\n\t\t\t{\n\t\t\tprivate:\n\t\t\t\tfriend class StreamScheduler;\n\n\t\t\tprivate:\n\t\t\t\tStream(StreamScheduler* parent, StreamProducer* producer, uint16_t streamId, uint16_t priority)\n\t\t\t\t  : parent(*parent),\n\t\t\t\t    producer(*producer),\n\t\t\t\t    streamId(streamId),\n\t\t\t\t    priority(priority),\n\t\t\t\t    inverseWeight(1.0 / std::max(static_cast<double>(priority), 1e-6))\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\tpublic:\n\t\t\t\tuint16_t GetStreamId() const\n\t\t\t\t{\n\t\t\t\t\treturn this->streamId;\n\t\t\t\t}\n\n\t\t\t\tuint16_t GetPriority() const\n\t\t\t\t{\n\t\t\t\t\treturn this->priority;\n\t\t\t\t}\n\n\t\t\t\tvoid SetPriority(uint16_t priority);\n\n\t\t\t\t/**\n\t\t\t\t * Will activate the stream if it has any data to send. That is, if the\n\t\t\t\t * callback to `GetBytesToSendInNextMessage()` returns non-zero. If the\n\t\t\t\t * callback returns zero, the stream will not be made active.\n\t\t\t\t */\n\t\t\t\tvoid MayMakeActive();\n\n\t\t\t\t/**\n\t\t\t\t * Will remove the stream from the list of active streams, and will not\n\t\t\t\t * try to produce data from it. To make it active again, call\n\t\t\t\t * `MayMakeActive()`.\n\t\t\t\t */\n\t\t\t\tvoid MakeInactive();\n\n\t\t\t\t/**\n\t\t\t\t * Make the scheduler move to another message, or another stream. This\n\t\t\t\t * is used to abort the scheduler from continuing producing fragments\n\t\t\t\t * for the current message in case it's deleted.\n\t\t\t\t */\n\t\t\t\tvoid ForceReschedule()\n\t\t\t\t{\n\t\t\t\t\tthis->parent.ForceReschedule();\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t/**\n\t\t\t\t * Produces a message from this stream. This will only be called on\n\t\t\t\t * streams that have data.\n\t\t\t\t */\n\t\t\t\tstd::optional<SendQueueInterface::DataToSend> Produce(uint64_t nowMs, size_t maxLength);\n\n\t\t\t\tvoid MakeActive(size_t bytesToSendNext);\n\n\t\t\t\tvoid ForceMarkInactive();\n\n\t\t\t\tdouble GetCurrentTime() const\n\t\t\t\t{\n\t\t\t\t\treturn this->currentVirtualTime;\n\t\t\t\t}\n\n\t\t\t\tdouble GetNextFinishTime() const\n\t\t\t\t{\n\t\t\t\t\treturn this->nextFinishTime;\n\t\t\t\t}\n\n\t\t\t\tsize_t GetBytesToSendInNextMessage() const\n\t\t\t\t{\n\t\t\t\t\treturn this->producer.GetBytesToSendInNextMessage();\n\t\t\t\t}\n\n\t\t\t\tdouble CalculateFinishTime(size_t bytesToSendNext) const;\n\n\t\t\tprivate:\n\t\t\t\tStreamScheduler& parent;\n\t\t\t\tStreamProducer& producer;\n\t\t\t\tconst uint16_t streamId;\n\t\t\t\tuint16_t priority;\n\t\t\t\tdouble inverseWeight;\n\t\t\t\t// This outgoing stream's \"current\" virtual time.\n\t\t\t\tdouble currentVirtualTime{ 0.0 };\n\t\t\t\tdouble nextFinishTime{ 0.0 };\n\t\t\t};\n\n\t\tprivate:\n\t\t\tstruct ActiveStreamComparator\n\t\t\t{\n\t\t\t\t// Ordered by virtual finish time (primary), stream-id (secondary).\n\t\t\t\tbool operator()(Stream* a, Stream* b) const\n\t\t\t\t{\n\t\t\t\t\tconst double aVft = a->GetNextFinishTime();\n\t\t\t\t\tconst double bVft = b->GetNextFinishTime();\n\n\t\t\t\t\tif (aVft == bVft)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn a->GetStreamId() < b->GetStreamId();\n\t\t\t\t\t}\n\n\t\t\t\t\treturn aVft < bVft;\n\t\t\t\t}\n\t\t\t};\n\n\t\tpublic:\n\t\t\texplicit StreamScheduler(size_t mtu)\n\t\t\t  : maxPayloadBytes(mtu - Packet::CommonHeaderLength - IDataChunk::IDataChunkHeaderLength)\n\t\t\t{\n\t\t\t}\n\n\t\tpublic:\n\t\t\tstd::unique_ptr<Stream> CreateStream(StreamProducer* producer, uint16_t streamId, uint16_t priority)\n\t\t\t{\n\t\t\t\treturn std::unique_ptr<Stream>(new Stream(this, producer, streamId, priority));\n\t\t\t}\n\n\t\t\tvoid EnableMessageInterleaving(bool enabled)\n\t\t\t{\n\t\t\t\tthis->enableMessageInterleaving = enabled;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Makes the scheduler stop producing message from the current stream and\n\t\t\t * re-evaluates which stream to produce from.\n\t\t\t */\n\t\t\tvoid ForceReschedule()\n\t\t\t{\n\t\t\t\tthis->currentlySendingAMessage = false;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Produces a fragment of data to send. The current wall time is specified\n\t\t\t * as `nowMs` and will be used to skip chunks with expired limited\n\t\t\t * lifetime. The parameter `maxLength` specifies the maximum amount of\n\t\t\t * actual payload that may be returned. If no data can be produced,\n\t\t\t * `std::nullopt` is returned.\n\t\t\t */\n\t\t\tstd::optional<SendQueueInterface::DataToSend> Produce(uint64_t nowMs, size_t maxLength);\n\n\t\t\tstd::set<uint16_t> GetActiveStreamsForTesting() const;\n\n\t\tprivate:\n\t\t\tvoid AssertIsConsistent() const;\n\n\t\tprivate:\n\t\t\tconst size_t maxPayloadBytes;\n\t\t\t// The current virtual time, as defined in the WFQ algorithm.\n\t\t\tdouble virtualTime{ 0.0 };\n\t\t\t// The current stream to send chunks from.\n\t\t\tStream* currentStream{ nullptr };\n\t\t\tbool enableMessageInterleaving{ false };\n\t\t\t// Indicates if the streams is currently sending a message, and should\n\t\t\t// then (if message interleaving is not enabled) continue sending from\n\t\t\t// this stream until that message has been sent in full.\n\t\t\tbool currentlySendingAMessage{ false };\n\t\t\t// The currently active streams, ordered by virtual finish time.\n\t\t\tstd::set<Stream*, ActiveStreamComparator> activeStreams;\n\t\t};\n\t} // namespace SCTP\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SctpAssociation.hpp",
    "content": "#ifndef MS_RTC_OLD_SCTP_ASSOCIATION_HPP\n#define MS_RTC_OLD_SCTP_ASSOCIATION_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/DataConsumer.hpp\"\n#include \"RTC/DataProducer.hpp\"\n#include <usrsctp.h>\n\nnamespace RTC\n{\n\tclass SctpAssociation\n\t{\n\tpublic:\n\t\tenum class SctpState : uint8_t\n\t\t{\n\t\t\tNEW = 1,\n\t\t\tCONNECTING,\n\t\t\tCONNECTED,\n\t\t\tFAILED,\n\t\t\tCLOSED\n\t\t};\n\n\tprivate:\n\t\tenum class StreamDirection : uint8_t\n\t\t{\n\t\t\tINCOMING = 1,\n\t\t\tOUTGOING\n\t\t};\n\n\tprotected:\n\t\tusing onQueuedCallback = const std::function<void(bool queued, bool sctpSendBufferFull)>;\n\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) = 0;\n\t\t\tvirtual void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation)  = 0;\n\t\t\tvirtual void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation)     = 0;\n\t\t\tvirtual void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation)     = 0;\n\t\t\tvirtual void OnSctpAssociationSendData(\n\t\t\t  RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) = 0;\n\t\t\tvirtual void OnSctpAssociationMessageReceived(\n\t\t\t  RTC::SctpAssociation* sctpAssociation,\n\t\t\t  uint16_t streamId,\n\t\t\t  const uint8_t* msg,\n\t\t\t  size_t len,\n\t\t\t  uint32_t ppid) = 0;\n\t\t\tvirtual void OnSctpAssociationBufferedAmount(\n\t\t\t  RTC::SctpAssociation* sctpAssociation, uint32_t len) = 0;\n\t\t};\n\n\tpublic:\n\t\tstatic bool IsSctp(const uint8_t* data, size_t len)\n\t\t{\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\t(len >= 12) &&\n\t\t\t\t// Must have Source Port Number and Destination Port Number set to 5000 (hack).\n\t\t\t\t(Utils::Byte::Get2Bytes(data, 0) == 5000) &&\n\t\t\t\t(Utils::Byte::Get2Bytes(data, 2) == 5000)\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\n\tpublic:\n\t\tSctpAssociation(\n\t\t  Listener* listener,\n\t\t  uint16_t os,\n\t\t  uint16_t mis,\n\t\t  size_t maxSctpMessageSize,\n\t\t  size_t sctpSendBufferSize,\n\t\t  bool isDataChannel);\n\t\t~SctpAssociation();\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tSctpState GetState() const\n\t\t{\n\t\t\treturn this->state;\n\t\t}\n\t\tsize_t GetSctpBufferedAmount() const\n\t\t{\n\t\t\treturn this->sctpBufferedAmount;\n\t\t}\n\t\tvoid ProcessSctpData(const uint8_t* data, size_t len);\n\t\tvoid SendSctpMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr);\n\t\tvoid HandleDataProducer(RTC::DataProducer* dataProducer);\n\t\tvoid HandleDataConsumer(RTC::DataConsumer* dataConsumer);\n\t\tvoid DataProducerClosed(RTC::DataProducer* dataProducer);\n\t\tvoid DataConsumerClosed(RTC::DataConsumer* dataConsumer);\n\n\tprivate:\n\t\tvoid MayConnect();\n\t\tvoid ResetSctpStream(uint16_t streamId, StreamDirection direction) const;\n\t\tvoid AddOutgoingStreams(bool force = false);\n\n\t\t/* Callbacks fired by usrsctp events. */\n\tpublic:\n\t\tvoid OnUsrSctpSendSctpData(void* buffer, size_t len);\n\t\tvoid OnUsrSctpReceiveSctpData(\n\t\t  uint16_t streamId, uint16_t ssn, uint32_t ppid, int flags, const uint8_t* data, size_t len);\n\t\tvoid OnUsrSctpReceiveSctpNotification(union sctp_notification* notification, size_t len);\n\t\tvoid OnUsrSctpSentData(uint32_t freeBuffer);\n\n\tpublic:\n\t\tuintptr_t id{ 0u };\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tuint16_t os{ 1024u };\n\t\tuint16_t mis{ 1024u };\n\t\tsize_t maxSctpMessageSize{ 262144u };\n\t\tsize_t sctpSendBufferSize{ 262144u };\n\t\tsize_t sctpBufferedAmount{ 0u };\n\t\tbool isDataChannel{ false };\n\t\t// Allocated by this.\n\t\tuint8_t* messageBuffer{ nullptr };\n\t\t// Others.\n\t\tSctpState state{ SctpState::NEW };\n\t\tstruct socket* socket{ nullptr };\n\t\tuint16_t desiredOs{ 0u };\n\t\tsize_t messageBufferLen{ 0u };\n\t\tbool transportConnected{ false };\n\t\t// Whether at least one SCTP stream (AKA DataProducer or DataConsumer) has\n\t\t// already been created, no matter it's still alive.\n\t\tbool firstStreamCreated{ false };\n\t\t// Whether we have received STCP data from the remote peer.\n\t\tbool sctpDataReceived{ false };\n\t\tuint16_t lastSsnReceived{ 0u }; // Valid for us since no SCTP I-DATA support.\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SctpDictionaries.hpp",
    "content": "#ifndef MS_RTC_SCTP_DICTIONARIES_HPP\n#define MS_RTC_SCTP_DICTIONARIES_HPP\n\n#include \"common.hpp\"\n#include <FBS/sctpParameters.h>\n\nnamespace RTC\n{\n\tclass SctpStreamParameters\n\t{\n\tpublic:\n\t\tSctpStreamParameters() = default;\n\t\texplicit SctpStreamParameters(const FBS::SctpParameters::SctpStreamParameters* data);\n\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpStreamParameters> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\n\tpublic:\n\t\tuint16_t streamId{ 0u };\n\t\tbool ordered{ true };\n\t\tuint16_t maxPacketLifeTime{ 0u };\n\t\tuint16_t maxRetransmits{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SctpListener.hpp",
    "content": "#ifndef MS_RTC_SCTP_LISTENER_HPP\n#define MS_RTC_SCTP_LISTENER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/DataProducer.hpp\"\n#include <unordered_map>\n\nnamespace RTC\n{\n\tclass SctpListener\n\t{\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Transport::SctpListener> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid AddDataProducer(RTC::DataProducer* dataProducer);\n\t\tvoid RemoveDataProducer(RTC::DataProducer* dataProducer);\n\t\tRTC::DataProducer* GetDataProducer(uint16_t streamId);\n\n\tpublic:\n\t\t// Table of streamId / DataProducer pairs.\n\t\tstd::unordered_map<uint16_t, RTC::DataProducer*> streamIdTable;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SenderBandwidthEstimator.hpp",
    "content": "#ifndef MS_RTC_SENDER_BANDWIDTH_ESTIMATOR_HPP\n#define MS_RTC_SENDER_BANDWIDTH_ESTIMATOR_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include \"RTC/TrendCalculator.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tclass SenderBandwidthEstimator\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnSenderBandwidthEstimatorAvailableBitrate(\n\t\t\t  RTC::SenderBandwidthEstimator* senderBwe,\n\t\t\t  uint32_t availableBitrate,\n\t\t\t  uint32_t previousAvailableBitrate) = 0;\n\t\t};\n\n\tpublic:\n\t\tstruct SentInfo\n\t\t{\n\t\t\tuint16_t wideSeq{ 0u };\n\t\t\tsize_t size{ 0u };\n\t\t\tbool isProbation{ false };\n\t\t\tuint64_t sendingAtMs{ 0u };\n\t\t\tuint64_t sentAtMs{ 0u };\n\t\t};\n\n\tprivate:\n\t\tclass CummulativeResult\n\t\t{\n\t\tpublic:\n\t\t\tCummulativeResult() = default;\n\n\t\tpublic:\n\t\t\tuint64_t GetStartedAtMs() const\n\t\t\t{\n\t\t\t\treturn this->firstPacketSentAtMs;\n\t\t\t}\n\t\t\tsize_t GetNumPackets() const\n\t\t\t{\n\t\t\t\treturn this->numPackets;\n\t\t\t}\n\t\t\tsize_t GetTotalSize() const\n\t\t\t{\n\t\t\t\treturn this->totalSize;\n\t\t\t}\n\t\t\tuint32_t GetSendBitrate() const\n\t\t\t{\n\t\t\t\tauto sendIntervalMs =\n\t\t\t\t  std::max<uint64_t>(this->lastPacketSentAtMs - this->firstPacketSentAtMs, 1u);\n\n\t\t\t\treturn static_cast<uint32_t>(this->totalSize / sendIntervalMs) * 8 * 1000;\n\t\t\t}\n\t\t\tuint32_t GetReceiveBitrate() const\n\t\t\t{\n\t\t\t\tauto recvIntervalMs =\n\t\t\t\t  std::max<uint64_t>(this->lastPacketReceivedAtMs - this->firstPacketReceivedAtMs, 1u);\n\n\t\t\t\treturn static_cast<uint32_t>(this->totalSize / recvIntervalMs) * 8 * 1000;\n\t\t\t}\n\t\t\tvoid AddPacket(size_t size, int64_t sentAtMs, int64_t receivedAtMs);\n\t\t\tvoid Reset();\n\n\t\tprivate:\n\t\t\tsize_t numPackets{ 0u };\n\t\t\tsize_t totalSize{ 0u };\n\t\t\tint64_t firstPacketSentAtMs{ 0u };\n\t\t\tint64_t lastPacketSentAtMs{ 0u };\n\t\t\tint64_t firstPacketReceivedAtMs{ 0u };\n\t\t\tint64_t lastPacketReceivedAtMs{ 0u };\n\t\t};\n\n\tpublic:\n\t\tSenderBandwidthEstimator(\n\t\t  RTC::SenderBandwidthEstimator::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  uint32_t initialAvailableBitrate);\n\t\tvirtual ~SenderBandwidthEstimator();\n\n\tpublic:\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tvoid RtpPacketSent(const SentInfo& sentInfo);\n\t\tvoid ReceiveRtcpTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket* feedback);\n\t\tvoid EstimateAvailableBitrate(CummulativeResult& cummulativeResult);\n\t\tvoid UpdateRtt(float rtt);\n\t\tuint32_t GetAvailableBitrate() const;\n\t\tvoid RescheduleNextAvailableBitrateEvent();\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Others.\n\t\tuint32_t initialAvailableBitrate{ 0u };\n\t\tuint32_t availableBitrate{ 0u };\n\t\tuint64_t lastAvailableBitrateEventAtMs{ 0u };\n\t\tstd::map<uint16_t, SentInfo, RTC::SeqManager<uint16_t>::SeqLowerThan> sentInfos;\n\t\tfloat rtt{ 0 }; // Round trip time in ms.\n\t\tCummulativeResult cummulativeResult;\n\t\tCummulativeResult probationCummulativeResult;\n\t\tRTC::RateCalculator sendTransmission;\n\t\tRTC::TrendCalculator sendTransmissionTrend;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SeqManager.hpp",
    "content": "#ifndef RTC_SEQ_MANAGER_HPP\n#define RTC_SEQ_MANAGER_HPP\n\n#include \"common.hpp\"\n#include <limits> // std::numeric_limits\n#include <set>\n\nnamespace RTC\n{\n\t// T is the base type (uint16_t, uint32_t, ...).\n\t// N is the max number of bits used in T.\n\ttemplate<typename T, uint8_t N = 0>\n\tclass SeqManager\n\t{\n\tpublic:\n\t\tstatic constexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\tpublic:\n\t\tstruct SeqLowerThan\n\t\t{\n\t\t\tbool operator()(T lhs, T rhs) const;\n\t\t};\n\n\t\tstruct SeqHigherThan\n\t\t{\n\t\t\tbool operator()(T lhs, T rhs) const;\n\t\t};\n\n\tpublic:\n\t\tstatic bool IsSeqHigherThan(T lhs, T rhs);\n\t\tstatic bool IsSeqLowerThan(T lhs, T rhs);\n\n\tpublic:\n\t\tSeqManager() = default;\n\t\texplicit SeqManager(T initialOutput);\n\n\tpublic:\n\t\tvoid Sync(T input);\n\t\tvoid Drop(T input);\n\t\tbool Input(T input, T& output);\n\t\tT GetMaxInput() const;\n\t\tT GetMaxOutput() const;\n\n\tprivate:\n\t\tvoid ClearDropped();\n\n\tprivate:\n\t\t// Whether at least a sequence number has been inserted.\n\t\tbool started{ false };\n\t\tT initialOutput{ 0 };\n\t\tT base{ 0 };\n\t\tT maxOutput{ 0 };\n\t\tT maxInput{ 0 };\n\t\tT maxDropped{ 0 };\n\t\tT maxForwarded{ 0 };\n\t\tstd::set<T, SeqLowerThan> dropped;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Serializable.hpp",
    "content": "#ifndef MS_RTC_SERIALIZABLE_HPP\n#define MS_RTC_SERIALIZABLE_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\t/**\n\t * Class holding serializable content.\n\t *\n\t * @remarks\n\t * - ICE, RTP, RTCP, SCTP packets and some items in those packets inherit\n\t *   from this class (or will inherit).\n\t * - Typically many of those packets and items may include padding bytes to\n\t *   be multiple of 4 bytes. However it's up to each packet or item to deal\n\t *   with padding. From the point of view of the Serializable class, the\n\t *   length of a Serializable packet or item must include its padding bytes\n\t *   (if any).\n\t */\n\tclass Serializable\n\t{\n\tpublic:\n\t\tusing BufferReleasedListener = std::function<void(const Serializable*, uint8_t* buffer)>;\n\n\tprotected:\n\t\tusing ConsolidatedListener = std::function<void()>;\n\n\tpublic:\n\t\t/**\n\t\t * @param buffer - The buffer holding the packet.\n\t\t * @param bufferLength - Buffer length.\n\t\t *\n\t\t * @remarks\n\t\t * - In same cases, `bufferLength` is the exact length of the Serializable\n\t\t *   and in other cases `bufferLength` is the maximum length that the\n\t\t *   Serializable can take.\n\t\t * - Always use `GetLength()` to obtain the exact length of the\n\t\t *   Serializable.\n\t\t */\n\t\tSerializable(const uint8_t* buffer, size_t bufferLength);\n\n\t\tvirtual ~Serializable();\n\n\tpublic:\n\t\t/**\n\t\t * Print Serializable state with given indentation.\n\t\t */\n\t\tvirtual void Dump(int indentation = 0) const = 0;\n\n\t\t/**\n\t\t * Get a buffer containing the serialized content. Combined with the\n\t\t * `GetLength()` method, the application can obtain the full sequence of\n\t\t * bytes of the Serializable in network byte order.\n\t\t */\n\t\tvirtual const uint8_t* GetBuffer() const final\n\t\t{\n\t\t\treturn this->buffer;\n\t\t}\n\n\t\t/**\n\t\t * Maximum length the Serializable can take. It's guaranteed to be equal or\n\t\t * greater than value returned by `GetLength()`.\n\t\t */\n\t\tvirtual size_t GetBufferLength() const final\n\t\t{\n\t\t\treturn this->bufferLength;\n\t\t}\n\n\t\t/**\n\t\t * Current exact length of the Serializable, including padding bytes (if\n\t\t * any).\n\t\t */\n\t\tvirtual size_t GetLength() const final\n\t\t{\n\t\t\treturn this->length;\n\t\t}\n\n\t\t/**\n\t\t * Current available length of the Serializable.\n\t\t */\n\t\tvirtual size_t GetAvailableLength() const final\n\t\t{\n\t\t\treturn this->bufferLength - this->length;\n\t\t}\n\n\t\t/**\n\t\t * Serialize the Serializable into a new buffer. This method copies the\n\t\t * bytes of the internal buffer into the new buffer and makes `GetBuffer()`\n\t\t * point to the new one.\n\t\t *\n\t\t * @param buffer - The new buffer in which the Serializable will be\n\t\t *   serialized.\n\t\t * @param bufferLength - New buffer length.\n\t\t *\n\t\t * @remarks\n\t\t * - In addition to call this method in Serializable parent class, the\n\t\t *   `Serialize()` implementation in the subclass must also reassign any\n\t\t *   pointers it holds and make them point to the proper position in the\n\t\t *   new buffer.\n\t\t *\n\t\t * @throw MediaSoupError - If given `bufferLength` is lower than the\n\t\t *   current exact length of the Serializable.\n\t\t */\n\t\tvirtual void Serialize(uint8_t* buffer, size_t bufferLength);\n\n\t\t/**\n\t\t * Clone the Serializable into a new buffer. This method returns a new\n\t\t * instance of the Serializable subclass which doesn't share any memory\n\t\t * with the original one.\n\t\t *\n\t\t * @param buffer - The buffer for the cloned Serializable.\n\t\t * @param bufferLength - Buffer length.\n\t\t *\n\t\t * @throw MediaSoupError - If given `bufferLength` is lower than the\n\t\t *   current exact length of the Serializable.\n\t\t */\n\t\tvirtual Serializable* Clone(uint8_t* buffer, size_t bufferLength) const = 0;\n\n\t\t/**\n\t\t * Set a listener that will be invoked when the current buffer is released,\n\t\t * meaning that this Serializable no longer uses it.\n\t\t *\n\t\t * @remarks\n\t\t * - The caller should call this method with `nullptr` as argument in case\n\t\t *   the lifetime of the previously passed `listener` ends before the\n\t\t *   Serializable is destroyed or serialized. Otherwise the Serializable\n\t\t *   will invoke a listener that was already destroyed.\n\t\t */\n\t\tvirtual void SetBufferReleasedListener(BufferReleasedListener* listener) final;\n\n\t\t/**\n\t\t * The application must call this method on a Serializable when it's been\n\t\t * constructed within a parent Serializable object that needs to know when\n\t\t * this Serializable is done to recompute its total length and internal\n\t\t * pointers.\n\t\t *\n\t\t * @throw MediaSoupError - If `SetConsolidatedListener()` was not called\n\t\t *   first.\n\t\t *\n\t\t * @see SetConsolidatedListener()\n\t\t */\n\t\tvirtual void Consolidate() const final;\n\n\t\t/**\n\t\t * Methods to be used by classes inheriting from Serializable.\n\t\t */\n\tprotected:\n\t\t/**\n\t\t * Change the buffer of the Serializable.\n\t\t */\n\t\tvirtual void SetBuffer(uint8_t* buffer) final;\n\n\t\t/**\n\t\t * Update the buffer length of the Serializable.\n\t\t **\n\t\t * @remarks\n\t\t * - The child class must invoke this method after parsing completes in\n\t\t *   case it couldn't anticipate its expected exact length. Specially\n\t\t *   useful when parsing variable-length items within a packet.\n\t\t *\n\t\t * @throw\n\t\t * - MediaSoupError - If given `bufferLength` is lower than the current\n\t\t *   exact length of the Serializable.\n\t\t * - MediaSoupError - If 0 is given.\n\t\t */\n\t\tvirtual void SetBufferLength(size_t bufferLength) final;\n\n\t\t/**\n\t\t * Method to be called by the child class to update the current exact\n\t\t * length of the Serializable.\n\t\t *\n\t\t * @remarks\n\t\t * - The child class must invoke this method after parsing completes and\n\t\t *   after every change in the Serializable content that affects its\n\t\t *   current length.\n\t\t *\n\t\t * @throw\n\t\t * - MediaSoupError - If given `length` is larger than the buffer length of\n\t\t *   the Serializable.\n\t\t * - MediaSoupError - If 0 is given.\n\t\t */\n\t\tvirtual void SetLength(size_t length) final;\n\n\t\t/**\n\t\t * Clone the Serializable into the given Serializable.\n\t\t *\n\t\t * @remarks\n\t\t * - If this method throws (due to the buffer length of the given\n\t\t *   Serializable being too small), then this method deletes the given\n\t\t *   `serializable` pointer and throws, meaning that the subclass must not\n\t\t *   delete it in case it captured the error.\n\t\t *\n\t\t * @throw MediaSoupError - If the buffer length of the given `serializable`\n\t\t *   is too small.\n\t\t */\n\t\tvirtual void CloneInto(Serializable* serializable) const;\n\n\t\t/**\n\t\t * Fill the last given `padding` number of bytes of the buffer with zeros.\n\t\t *\n\t\t * @remarks\n\t\t * - This method does NOT add bytes to the buffer.\n\t\t */\n\t\tvirtual void FillPadding(uint8_t padding) final;\n\n\t\t/**\n\t\t * Set a listener that will be invoked when calling `Consolidate()` on this\n\t\t * Serializable.\n\t\t *\n\t\t * @see Consolidate()\n\t\t */\n\t\tvirtual void SetConsolidatedListener(ConsolidatedListener&& listener) final;\n\n\tprivate:\n\t\t// Buffer holding the Serializable content.\n\t\tuint8_t* buffer;\n\t\t// Length of the buffer. This is the maximum length the Serializable can\n\t\t// take.\n\t\tsize_t bufferLength;\n\t\t// Serializable current exact length (includes padding bytes).\n\t\tsize_t length{ 0u };\n\t\t// Event listener invoked when the current buffer is released (no longer\n\t\t// used by this Serializable)-\n\t\tBufferReleasedListener* bufferReleasedListener{ nullptr };\n\t\t// Event listener invoked when the Serializable is consolidated.\n\t\tConsolidatedListener consolidatedListener{ nullptr };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SimpleConsumer.hpp",
    "content": "#ifndef MS_RTC_SIMPLE_CONSUMER_HPP\n#define MS_RTC_SIMPLE_CONSUMER_HPP\n\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tclass SimpleConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tpublic:\n\t\tSimpleConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& producerId,\n\t\t  RTC::Consumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeRequest* data);\n\t\t~SimpleConsumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Consumer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> FillBufferScore(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const override;\n\t\tbool IsActive() const override\n\t\t{\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\tRTC::Consumer::IsActive() &&\n\t\t\t\tthis->producerRtpStream &&\n\t\t\t\t// If there is no RTP inactivity check do not consider the stream\n\t\t\t\t// inactive despite it has score 0.\n\t\t\t\t(this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled())\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tvoid ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerRtpStreamScore(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override;\n\t\tuint8_t GetBitratePriority() const override;\n\t\tuint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override;\n\t\tvoid ApplyLayers() override;\n\t\tuint32_t GetDesiredBitrate() const override;\n\t\tvoid SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override;\n\t\tconst std::vector<RTC::RTP::RtpStreamSend*>& GetRtpStreams() const override\n\t\t{\n\t\t\treturn this->rtpStreams;\n\t\t}\n\t\tbool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override;\n\t\tvoid NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;\n\t\tvoid ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override;\n\t\tvoid ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override;\n\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override;\n\t\tvoid ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override;\n\t\tuint32_t GetTransmissionRate(uint64_t nowMs) override;\n\t\tfloat GetRtt() const override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tvoid UserOnTransportConnected() override;\n\t\tvoid UserOnTransportDisconnected() override;\n\t\tvoid UserOnPaused() override;\n\t\tvoid UserOnResumed() override;\n\t\tvoid CreateRtpStream();\n\t\tvoid RequestKeyFrame();\n\t\tvoid StorePacketInTargetLayerRetransmissionBuffer(\n\t\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket);\n\t\tvoid EmitScore() const;\n\n\t\t/* Pure virtual methods inherited from RtpStreamSend::Listener. */\n\tpublic:\n\t\tvoid OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tRTC::RTP::RtpStreamSend* rtpStream{ nullptr };\n\t\t// Others.\n\t\tstd::vector<RTC::RTP::RtpStreamSend*> rtpStreams;\n\t\tRTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr };\n\t\tbool keyFrameSupported{ false };\n\t\tbool syncRequired{ false };\n\t\tRTC::SeqManager<uint16_t> rtpSeqManager;\n\t\tbool managingBitrate{ false };\n\t\tstd::unique_ptr<RTC::RTP::Codecs::EncodingContext> encodingContext;\n\t\t// Buffer to store packets that arrive earlier than the first packet of the\n\t\t// video key frame.\n\t\tstd::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>\n\t\t  targetLayerRetransmissionBuffer;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SimulcastConsumer.hpp",
    "content": "#ifndef MS_RTC_SIMULCAST_CONSUMER_HPP\n#define MS_RTC_SIMULCAST_CONSUMER_HPP\n\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tclass SimulcastConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tpublic:\n\t\tSimulcastConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& producerId,\n\t\t  RTC::Consumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeRequest* data);\n\t\t~SimulcastConsumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Consumer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> FillBufferScore(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const override;\n\t\tRTC::ConsumerTypes::VideoLayers GetPreferredLayers() const override\n\t\t{\n\t\t\tRTC::ConsumerTypes::VideoLayers layers;\n\n\t\t\tlayers.spatial  = this->preferredLayers.spatial;\n\t\t\tlayers.temporal = this->preferredLayers.temporal;\n\n\t\t\treturn layers;\n\t\t}\n\t\tbool IsActive() const override\n\t\t{\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\tRTC::Consumer::IsActive() &&\n\t\t\t\tstd::any_of(\n\t\t\t\t\tthis->producerRtpStreams.begin(),\n\t\t\t\t\tthis->producerRtpStreams.end(),\n\t\t\t\t\t[](const RTC::RTP::RtpStreamRecv* rtpStream)\n\t\t\t\t\t{\n\t\t\t\t\t\t// If there is no RTP inactivity check do not consider the stream\n\t\t\t\t\t\t// inactive despite it has score 0.\n\t\t\t\t\t\treturn (rtpStream != nullptr && (rtpStream->GetScore() > 0u || !rtpStream->HasRtpInactivityCheckEnabled()));\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tvoid ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerRtpStreamScore(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override;\n\t\tuint8_t GetBitratePriority() const override;\n\t\tuint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override;\n\t\tvoid ApplyLayers() override;\n\t\tuint32_t GetDesiredBitrate() const override;\n\t\tvoid SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override;\n\t\tbool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override;\n\t\tconst std::vector<RTC::RTP::RtpStreamSend*>& GetRtpStreams() const override\n\t\t{\n\t\t\treturn this->rtpStreams;\n\t\t}\n\t\tvoid NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;\n\t\tvoid ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override;\n\t\tvoid ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override;\n\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override;\n\t\tvoid ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override;\n\t\tuint32_t GetTransmissionRate(uint64_t nowMs) override;\n\t\tfloat GetRtt() const override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tvoid UserOnTransportConnected() override;\n\t\tvoid UserOnTransportDisconnected() override;\n\t\tvoid UserOnPaused() override;\n\t\tvoid UserOnResumed() override;\n\t\tvoid CreateRtpStream();\n\t\tvoid RequestKeyFrames();\n\t\tvoid RequestKeyFrameForTargetSpatialLayer();\n\t\tvoid RequestKeyFrameForCurrentSpatialLayer();\n\t\tvoid MayChangeLayers(bool force = false);\n\t\tbool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const;\n\t\tvoid UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer);\n\t\tbool CanSwitchToSpatialLayer(int16_t spatialLayer) const;\n\t\tvoid EmitScore() const;\n\t\tvoid StorePacketInTargetLayerRetransmissionBuffer(\n\t\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket);\n\t\tvoid EmitLayersChange() const;\n\t\tRTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const;\n\t\tRTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const;\n\t\tRTC::RTP::RtpStreamRecv* GetProducerTsReferenceRtpStream() const;\n\n\t\t/* Pure virtual methods inherited from RtpStreamSend::Listener. */\n\tpublic:\n\t\tvoid OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tRTC::RTP::RtpStreamSend* rtpStream{ nullptr };\n\t\t// Others.\n\t\tabsl::flat_hash_map<uint32_t, int16_t> mapMappedSsrcSpatialLayer;\n\t\tstd::vector<RTC::RTP::RtpStreamSend*> rtpStreams;\n\t\tstd::vector<RTC::RTP::RtpStreamRecv*> producerRtpStreams; // Indexed by spatial layer.\n\t\tbool syncRequired{ false };\n\t\tint16_t spatialLayerToSync{ -1 };\n\t\tbool lastSentPacketHasMarker{ false };\n\t\tRTC::SeqManager<uint16_t> rtpSeqManager;\n\t\tRTC::ConsumerTypes::VideoLayers preferredLayers;\n\t\tRTC::ConsumerTypes::VideoLayers provisionalTargetLayers;\n\t\tRTC::ConsumerTypes::VideoLayers targetLayers;\n\t\tint16_t currentSpatialLayer{ -1 };\n\t\tint16_t tsReferenceSpatialLayer{ -1 }; // Used for RTP TS sync.\n\t\tuint16_t snReferenceSpatialLayer{ 0 };\n\t\tbool checkingForOldPacketsInSpatialLayer{ false };\n\t\tstd::unique_ptr<RTC::RTP::Codecs::EncodingContext> encodingContext;\n\t\tuint32_t tsOffset{ 0u }; // RTP Timestamp offset.\n\t\tbool keyFrameForTsOffsetRequested{ false };\n\t\t// Last time we moved to lower spatial layer due to BWE.\n\t\tuint64_t lastBweDowngradeAtMs{ 0u };\n\t\t// Buffer to store packets that arrive earlier than the first packet of the\n\t\t// video key frame.\n\t\tstd::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>\n\t\t  targetLayerRetransmissionBuffer;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SrtpSession.hpp",
    "content": "#ifndef MS_RTC_SRTP_SESSION_HPP\n#define MS_RTC_SRTP_SESSION_HPP\n\n#include \"common.hpp\"\n#include \"FBS/srtpParameters.h\"\n#include <srtp.h>\n\nnamespace RTC\n{\n\tclass SrtpSession\n\t{\n\tpublic:\n\t\tenum class CryptoSuite : uint8_t\n\t\t{\n\t\t\tAEAD_AES_256_GCM = 0,\n\t\t\tAEAD_AES_128_GCM,\n\t\t\tAES_CM_128_HMAC_SHA1_80,\n\t\t\tAES_CM_128_HMAC_SHA1_32,\n\t\t};\n\n\tpublic:\n\t\tenum class Type : uint8_t\n\t\t{\n\t\t\tINBOUND = 1,\n\t\t\tOUTBOUND\n\t\t};\n\n\tpublic:\n\t\tstatic void ClassInit();\n\t\tstatic FBS::SrtpParameters::SrtpCryptoSuite CryptoSuiteToFbs(CryptoSuite cryptoSuite);\n\t\tstatic CryptoSuite CryptoSuiteFromFbs(FBS::SrtpParameters::SrtpCryptoSuite cryptoSuite);\n\n\tprivate:\n\t\tstatic void OnSrtpEvent(srtp_event_data_t* data);\n\n\tpublic:\n\t\tSrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen);\n\t\t~SrtpSession();\n\n\tpublic:\n\t\tbool EncryptRtp(const uint8_t** data, size_t* len);\n\t\tbool DecryptSrtp(uint8_t* data, size_t* len);\n\t\tbool EncryptRtcp(const uint8_t** data, size_t* len);\n\t\tbool DecryptSrtcp(uint8_t* data, size_t* len);\n\t\tvoid RemoveStream(uint32_t ssrc)\n\t\t{\n\t\t\tsrtp_stream_remove(this->session, htonl(ssrc));\n\t\t}\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tsrtp_t session{ nullptr };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/SvcConsumer.hpp",
    "content": "#ifndef MS_RTC_SVC_CONSUMER_HPP\n#define MS_RTC_SVC_CONSUMER_HPP\n\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tclass SvcConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tpublic:\n\t\tSvcConsumer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const std::string& producerId,\n\t\t  RTC::Consumer::Listener* listener,\n\t\t  const FBS::Transport::ConsumeRequest* data);\n\t\t~SvcConsumer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::Consumer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> FillBufferScore(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const override;\n\t\tRTC::ConsumerTypes::VideoLayers GetPreferredLayers() const override\n\t\t{\n\t\t\tRTC::ConsumerTypes::VideoLayers layers;\n\n\t\t\tlayers.spatial  = this->preferredLayers.spatial;\n\t\t\tlayers.temporal = this->preferredLayers.temporal;\n\n\t\t\treturn layers;\n\t\t}\n\t\tbool IsActive() const override\n\t\t{\n\t\t\t// clang-format off\n\t\t\treturn (\n\t\t\t\tRTC::Consumer::IsActive() &&\n\t\t\t\tthis->producerRtpStream &&\n\t\t\t\t// If there is no RTP inactivity check do not consider the stream\n\t\t\t\t// inactive despite it has score 0.\n\t\t\t\t(this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled())\n\t\t\t);\n\t\t\t// clang-format on\n\t\t}\n\t\tvoid ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid ProducerRtpStreamScore(\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override;\n\t\tuint8_t GetBitratePriority() const override;\n\t\tuint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override;\n\t\tvoid ApplyLayers() override;\n\t\tuint32_t GetDesiredBitrate() const override;\n\t\tvoid SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override;\n\t\tbool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override;\n\t\tconst std::vector<RTC::RTP::RtpStreamSend*>& GetRtpStreams() const override\n\t\t{\n\t\t\treturn this->rtpStreams;\n\t\t}\n\t\tvoid NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;\n\t\tvoid ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override;\n\t\tvoid ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override;\n\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override;\n\t\tvoid ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override;\n\t\tuint32_t GetTransmissionRate(uint64_t nowMs) override;\n\t\tfloat GetRtt() const override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tvoid UserOnTransportConnected() override;\n\t\tvoid UserOnTransportDisconnected() override;\n\t\tvoid UserOnPaused() override;\n\t\tvoid UserOnResumed() override;\n\t\tvoid CreateRtpStream();\n\t\tvoid RequestKeyFrame();\n\t\tvoid MayChangeLayers(bool force = false);\n\t\tbool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const;\n\t\tvoid UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer);\n\t\tvoid EmitScore() const;\n\t\tvoid StorePacketInTargetLayerRetransmissionBuffer(\n\t\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket);\n\t\tvoid EmitLayersChange() const;\n\n\t\t/* Pure virtual methods inherited from RtpStreamSend::Listener. */\n\tpublic:\n\t\tvoid OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override;\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override;\n\n\tprivate:\n\t\t// Allocated by this.\n\t\tRTC::RTP::RtpStreamSend* rtpStream{ nullptr };\n\t\t// Others.\n\t\tstd::vector<RTC::RTP::RtpStreamSend*> rtpStreams;\n\t\tRTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr };\n\t\tbool syncRequired{ false };\n\t\tRTC::SeqManager<uint16_t> rtpSeqManager;\n\t\tRTC::ConsumerTypes::VideoLayers preferredLayers;\n\t\tRTC::ConsumerTypes::VideoLayers provisionalTargetLayers;\n\t\tstd::unique_ptr<RTC::RTP::Codecs::EncodingContext> encodingContext;\n\t\t// Last time we moved to lower spatial layer due to BWE.\n\t\tuint64_t lastBweDowngradeAtMs{ 0u };\n\t\t// Buffer to store packets that arrive earlier than the first packet of the\n\t\t// video key frame.\n\t\tstd::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>\n\t\t  targetLayerRetransmissionBuffer;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TcpConnection.hpp",
    "content": "#ifndef MS_RTC_TCP_CONNECTION_HPP\n#define MS_RTC_TCP_CONNECTION_HPP\n\n#include \"common.hpp\"\n#include \"handles/TcpConnectionHandle.hpp\"\n\nnamespace RTC\n{\n\tclass TcpConnection : public ::TcpConnectionHandle\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnTcpConnectionPacketReceived(\n\t\t\t  RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) = 0;\n\t\t};\n\n\tpublic:\n\t\tTcpConnection(Listener* listener, size_t bufferSize);\n\t\t~TcpConnection() override;\n\n\tpublic:\n\t\tvoid Send(const uint8_t* data, size_t len, ::TcpConnectionHandle::onSendCallback* cb);\n\n\t\t/* Pure virtual methods inherited from ::TcpConnectionHandle. */\n\tpublic:\n\t\tvoid UserOnTcpConnectionRead() override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\t// Others.\n\t\tsize_t frameStart{ 0u }; // Where the latest frame starts.\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TcpServer.hpp",
    "content": "#ifndef MS_RTC_TCP_SERVER_HPP\n#define MS_RTC_TCP_SERVER_HPP\n\n#include \"common.hpp\"\n#include \"RTC/TcpConnection.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"handles/TcpConnectionHandle.hpp\"\n#include \"handles/TcpServerHandle.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tclass TcpServer : public ::TcpServerHandle\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnRtcTcpConnectionClosed(\n\t\t\t  RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) = 0;\n\t\t};\n\n\tpublic:\n\t\tTcpServer(\n\t\t  Listener* listener,\n\t\t  RTC::TcpConnection::Listener* connListener,\n\t\t  std::string& ip,\n\t\t  uint16_t port,\n\t\t  RTC::Transport::SocketFlags& flags);\n\t\tTcpServer(\n\t\t  Listener* listener,\n\t\t  RTC::TcpConnection::Listener* connListener,\n\t\t  std::string& ip,\n\t\t  uint16_t minPort,\n\t\t  uint16_t maxPort,\n\t\t  RTC::Transport::SocketFlags& flags,\n\t\t  uint64_t& portRangeHash);\n\t\t~TcpServer() override;\n\n\t\t/* Pure virtual methods inherited from ::TcpServerHandle. */\n\tpublic:\n\t\tvoid UserOnTcpConnectionAlloc() override;\n\t\tvoid UserOnTcpConnectionClosed(::TcpConnectionHandle* connection) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tRTC::TcpConnection::Listener* connListener{ nullptr };\n\t\tbool fixedPort{ false };\n\t\tuint64_t portRangeHash{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/Transport.hpp",
    "content": "#ifndef MS_RTC_TRANSPORT_HPP\n#define MS_RTC_TRANSPORT_HPP\n\n// #define ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\n#include \"common.hpp\"\n#include \"Channel/ChannelNotification.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"FBS/transport.h\"\n#include \"RTC/Consumer.hpp\"\n#include \"RTC/DataConsumer.hpp\"\n#include \"RTC/DataProducer.hpp\"\n#include \"RTC/Producer.hpp\"\n#include \"RTC/RTCP/CompoundPacket.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n#include \"RTC/RtpListener.hpp\"\n#include \"RTC/SCTP/public/AssociationInterface.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n// TODO: SCTP: Remove once we only use built-in SCTP stack.\n#include \"SharedInterface.hpp\"\n#include \"RTC/SctpAssociation.hpp\"\n#include \"RTC/SctpListener.hpp\"\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n#include \"RTC/SenderBandwidthEstimator.hpp\"\n#endif\n#include \"RTC/TransportCongestionControlClient.hpp\"\n#include \"RTC/TransportCongestionControlServer.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass Transport : public RTC::Producer::Listener,\n\t                  public RTC::Consumer::Listener,\n\t                  public RTC::DataProducer::Listener,\n\t                  public RTC::DataConsumer::Listener,\n\t                  public RTC::SCTP::AssociationListenerInterface,\n\t                  // TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t                  public RTC::SctpAssociation::Listener,\n\t                  public RTC::TransportCongestionControlClient::Listener,\n\t                  public RTC::TransportCongestionControlServer::Listener,\n\t                  public Channel::ChannelSocket::RequestHandler,\n\t                  public Channel::ChannelSocket::NotificationHandler,\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t                  public RTC::SenderBandwidthEstimator::Listener,\n#endif\n\t                  public TimerHandleInterface::Listener\n\t{\n\tprotected:\n\t\tusing onSendCallback   = const std::function<void(bool sent)>;\n\t\tusing onQueuedCallback = const std::function<void(bool queued, bool sctpSendBufferFull)>;\n\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnTransportNewProducer(RTC::Transport* transport, RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnTransportProducerClosed(RTC::Transport* transport, RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnTransportProducerPaused(RTC::Transport* transport, RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnTransportProducerResumed(RTC::Transport* transport, RTC::Producer* producer) = 0;\n\t\t\tvirtual void OnTransportProducerNewRtpStream(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::Producer* producer,\n\t\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t\t  uint32_t mappedSsrc) = 0;\n\t\t\tvirtual void OnTransportProducerRtpStreamScore(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::Producer* producer,\n\t\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t\t  uint8_t score,\n\t\t\t  uint8_t previousScore) = 0;\n\t\t\tvirtual void OnTransportProducerRtcpSenderReport(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::Producer* producer,\n\t\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t\t  bool first) = 0;\n\t\t\tvirtual void OnTransportProducerRtpPacketReceived(\n\t\t\t  RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::Packet* packet) = 0;\n\t\t\tvirtual void OnTransportNeedWorstRemoteFractionLost(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::Producer* producer,\n\t\t\t  uint32_t mappedSsrc,\n\t\t\t  uint8_t& worstRemoteFractionLost) = 0;\n\t\t\tvirtual void OnTransportNewConsumer(\n\t\t\t  RTC::Transport* transport, RTC::Consumer* consumer, const std::string& producerId) = 0;\n\t\t\tvirtual void OnTransportConsumerClosed(RTC::Transport* transport, RTC::Consumer* consumer) = 0;\n\t\t\tvirtual void OnTransportConsumerProducerClosed(\n\t\t\t  RTC::Transport* transport, RTC::Consumer* consumer) = 0;\n\t\t\tvirtual void OnTransportDataProducerPaused(\n\t\t\t  RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0;\n\t\t\tvirtual void OnTransportDataProducerResumed(\n\t\t\t  RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0;\n\t\t\tvirtual void OnTransportConsumerKeyFrameRequested(\n\t\t\t  RTC::Transport* transport, RTC::Consumer* consumer, uint32_t mappedSsrc) = 0;\n\t\t\tvirtual void OnTransportNewDataProducer(\n\t\t\t  RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0;\n\t\t\tvirtual void OnTransportDataProducerClosed(\n\t\t\t  RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0;\n\t\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\t\tvirtual void OnTransportDataProducerMessageReceived(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::DataProducer* dataProducer,\n\t\t\t  const uint8_t* msg,\n\t\t\t  size_t len,\n\t\t\t  uint32_t ppid,\n\t\t\t  std::vector<uint16_t>& subchannels,\n\t\t\t  std::optional<uint16_t> requiredSubchannel) = 0;\n\t\t\tvirtual void OnTransportDataProducerMessageReceived(\n\t\t\t  RTC::Transport* transport,\n\t\t\t  RTC::DataProducer* dataProducer,\n\t\t\t  RTC::SCTP::Message message,\n\t\t\t  std::vector<uint16_t>& subchannels,\n\t\t\t  std::optional<uint16_t> requiredSubchannel) = 0;\n\t\t\tvirtual void OnTransportNewDataConsumer(\n\t\t\t  RTC::Transport* transport, RTC::DataConsumer* dataConsumer, std::string& dataProducerId) = 0;\n\t\t\tvirtual void OnTransportDataConsumerClosed(\n\t\t\t  RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0;\n\t\t\tvirtual void OnTransportDataConsumerDataProducerClosed(\n\t\t\t  RTC::Transport* transport, RTC::DataConsumer* dataConsumer)         = 0;\n\t\t\tvirtual void OnTransportListenServerClosed(RTC::Transport* transport) = 0;\n\t\t};\n\n\tpublic:\n\t\tstruct SocketFlags\n\t\t{\n\t\t\tbool ipv6Only{ false };\n\t\t\tbool udpReusePort{ false };\n\t\t};\n\n\t\tstruct PortRange\n\t\t{\n\t\t\tuint16_t min{ 0u };\n\t\t\tuint16_t max{ 0u };\n\t\t};\n\n\t\tstruct ListenInfo\n\t\t{\n\t\t\tstd::string ip;\n\t\t\tstd::string announcedAddress;\n\t\t\tuint16_t port{ 0u };\n\t\t\tPortRange portRange;\n\t\t\tSocketFlags flags;\n\t\t\tuint32_t sendBufferSize{ 0u };\n\t\t\tuint32_t recvBufferSize{ 0u };\n\t\t};\n\n\tprivate:\n\t\tstruct TraceEventTypes\n\t\t{\n\t\t\tbool probation{ false };\n\t\t\tbool bwe{ false };\n\t\t};\n\n\tpublic:\n\t\tTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  const FBS::Transport::Options* options);\n\t\t~Transport() override;\n\n\tpublic:\n\t\tvoid CloseProducersAndConsumers();\n\t\tvoid ListenServerClosed();\n\t\t// Subclasses must also invoke the parent Close().\n\t\tflatbuffers::Offset<FBS::Transport::Stats> FillBufferStats(flatbuffers::FlatBufferBuilder& builder);\n\t\tflatbuffers::Offset<FBS::Transport::Dump> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tprotected:\n\t\t// Must be called from the subclass.\n\t\tvirtual void SetDestroying() final;\n\t\tvirtual void Connected() final;\n\t\tvirtual void Disconnected() final;\n\t\tvirtual void DataReceived(size_t len) final\n\t\t{\n\t\t\tthis->recvTransmission.Update(len, this->shared->GetTimeMs());\n\t\t}\n\t\tvirtual void DataSent(size_t len) final\n\t\t{\n\t\t\tthis->sendTransmission.Update(len, this->shared->GetTimeMs());\n\t\t}\n\t\tvirtual void ReceiveRtpPacket(RTC::RTP::Packet* packet) final;\n\t\tvirtual void ReceiveRtcpPacket(RTC::RTCP::Packet* packet) final;\n\t\tvirtual void ReceiveSctpData(const uint8_t* data, size_t len) final;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvirtual void SendSctpMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) final;\n\t\tvirtual void SendSctpMessage(\n\t\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) final;\n\t\tvirtual RTC::Producer* GetProducerById(const std::string& producerId) const final;\n\t\tvirtual RTC::Consumer* GetConsumerById(const std::string& consumerId) const final;\n\t\tvirtual RTC::Consumer* GetConsumerByMediaSsrc(uint32_t ssrc) const final;\n\t\tvirtual RTC::Consumer* GetConsumerByRtxSsrc(uint32_t ssrc) const final;\n\t\tvirtual RTC::DataProducer* GetDataProducerById(const std::string& dataProducerId) const final;\n\t\tvirtual RTC::DataConsumer* GetDataConsumerById(const std::string& dataConsumerId) const final;\n\n\tprivate:\n\t\tvirtual bool IsConnected() const = 0;\n\t\tvirtual void SendRtpPacket(\n\t\t  RTC::Consumer* consumer, RTC::RTP::Packet* packet, const onSendCallback* cb = nullptr) = 0;\n\t\tvirtual void HandleRtcpPacket(RTC::RTCP::Packet* packet) final;\n\t\tvirtual void SendRtcp(uint64_t nowMs) final;\n\t\tvirtual void SendRtcpPacket(RTC::RTCP::Packet* packet)                 = 0;\n\t\tvirtual void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) = 0;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvirtual void SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) = 0;\n\t\tvirtual void SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) = 0;\n\t\tvirtual bool SendData(const uint8_t* data, size_t len) = 0;\n\t\tvirtual void RecvStreamClosed(uint32_t ssrc)           = 0;\n\t\tvirtual void SendStreamClosed(uint32_t ssrc)           = 0;\n\t\tvirtual void DistributeAvailableOutgoingBitrate() final;\n\t\tvirtual void ComputeOutgoingDesiredBitrate(bool forceBitrate = false) final;\n\t\tvirtual void EmitTraceEventProbationType(RTC::RTP::Packet* packet) const final;\n\t\tvirtual void EmitTraceEventBweType(\n\t\t  RTC::TransportCongestionControlClient::Bitrates& bitrates) const final;\n\t\tvirtual void CheckNoDataProducer(const std::string& dataProducerId) const final;\n\t\tvirtual void CheckNoDataConsumer(const std::string& dataConsumerId) const final;\n\n\t\t/* Pure virtual methods inherited from RTC::Producer::Listener. */\n\tpublic:\n\t\tvoid OnProducerReceiveData(RTC::Producer* /*producer*/, size_t len) override\n\t\t{\n\t\t\tthis->DataReceived(len);\n\t\t}\n\t\tvoid OnProducerReceiveRtpPacket(RTC::Producer* /*producer*/, RTC::RTP::Packet* packet) override\n\t\t{\n\t\t\tthis->ReceiveRtpPacket(packet);\n\t\t}\n\t\tvoid OnProducerPaused(RTC::Producer* producer) override;\n\t\tvoid OnProducerResumed(RTC::Producer* producer) override;\n\t\tvoid OnProducerNewRtpStream(\n\t\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override;\n\t\tvoid OnProducerRtpStreamScore(\n\t\t  RTC::Producer* producer,\n\t\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t\t  uint8_t score,\n\t\t  uint8_t previousScore) override;\n\t\tvoid OnProducerRtcpSenderReport(\n\t\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) override;\n\t\tvoid OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet) override;\n\t\tvoid OnProducerSendRtcpPacket(RTC::Producer* producer, RTC::RTCP::Packet* packet) override;\n\t\tvoid OnProducerNeedWorstRemoteFractionLost(\n\t\t  RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override;\n\n\t\t/* Pure virtual methods inherited from RTC::Consumer::Listener. */\n\tpublic:\n\t\tvoid OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) override;\n\t\tvoid OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) override;\n\t\tvoid OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) override;\n\t\tvoid OnConsumerNeedBitrateChange(RTC::Consumer* consumer) override;\n\t\tvoid OnConsumerNeedZeroBitrate(RTC::Consumer* consumer) override;\n\t\tvoid OnConsumerProducerClosed(RTC::Consumer* consumer) override;\n\n\t\t/* Pure virtual methods inherited from RTC::DataProducer::Listener. */\n\tpublic:\n\t\tvoid OnDataProducerReceiveData(RTC::DataProducer* /*dataProducer*/, size_t len) override\n\t\t{\n\t\t\tthis->DataReceived(len);\n\t\t}\n\t\tvoid OnDataProducerMessageReceived(\n\t\t  RTC::DataProducer* dataProducer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel) override;\n\t\tvoid OnDataProducerMessageReceived(\n\t\t  RTC::DataProducer* dataProducer,\n\t\t  RTC::SCTP::Message message,\n\t\t  std::vector<uint16_t>& subchannels,\n\t\t  std::optional<uint16_t> requiredSubchannel) override;\n\t\tvoid OnDataProducerPaused(RTC::DataProducer* dataProducer) override;\n\t\tvoid OnDataProducerResumed(RTC::DataProducer* dataProducer) override;\n\n\t\t/* Pure virtual methods inherited from RTC::DataConsumer::Listener. */\n\tpublic:\n\t\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\t\tvoid OnDataConsumerSendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tvoid OnDataConsumerSendMessage(\n\t\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) override;\n\t\tvoid OnDataConsumerNeedBufferedAmount(\n\t\t  RTC::DataConsumer* dataConsumer, uint32_t& bufferedAmount) override;\n\t\tvoid OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer) override;\n\n\t\t/* Pure virtual methods inherited from RTC::SCTP::AssociationListenerInterface. */\n\tpublic:\n\t\tbool OnAssociationSendData(const uint8_t* data, size_t len) override;\n\t\tvoid OnAssociationConnecting() override;\n\t\tvoid OnAssociationConnected() override;\n\t\tvoid OnAssociationFailed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\t\tvoid OnAssociationClosed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\t\tvoid OnAssociationRestarted() override;\n\t\tvoid OnAssociationError(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override;\n\t\tvoid OnAssociationMessageReceived(RTC::SCTP::Message message) override;\n\t\tvoid OnAssociationStreamsResetPerformed(std::span<const uint16_t> outboundStreamIds) override;\n\t\tvoid OnAssociationStreamsResetFailed(\n\t\t  std::span<const uint16_t> outboundStreamIds, std::string_view errorMessage) override;\n\t\tvoid OnAssociationInboundStreamsReset(std::span<const uint16_t> inboundStreamIds) override;\n\t\tvoid OnAssociationStreamBufferedAmountLow(uint16_t streamId) override;\n\t\tvoid OnAssociationTotalBufferedAmountLow() override;\n\t\tbool OnAssociationIsTransportReadyForSctp() override;\n\t\t// TODO: SCTP: Add OnAssociationLifecycleMessageXxxxxx() methods.\n\n\t\t/* Pure virtual methods inherited from RTC::SctpAssociation::Listener. */\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tpublic:\n\t\tvoid OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override;\n\t\tvoid OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override;\n\t\tvoid OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override;\n\t\tvoid OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override;\n\t\tvoid OnSctpAssociationSendData(\n\t\t  RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override;\n\t\tvoid OnSctpAssociationMessageReceived(\n\t\t  RTC::SctpAssociation* sctpAssociation,\n\t\t  uint16_t streamId,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid) override;\n\t\tvoid OnSctpAssociationBufferedAmount(\n\t\t  RTC::SctpAssociation* sctpAssociation, uint32_t bufferedAmount) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TransportCongestionControlClient::Listener. */\n\tpublic:\n\t\tvoid OnTransportCongestionControlClientBitrates(\n\t\t  RTC::TransportCongestionControlClient* tccClient,\n\t\t  RTC::TransportCongestionControlClient::Bitrates& bitrates) override;\n\t\tvoid OnTransportCongestionControlClientSendRtpPacket(\n\t\t  RTC::TransportCongestionControlClient* tccClient,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  const webrtc::PacedPacketInfo& pacingInfo) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TransportCongestionControlServer::Listener. */\n\tpublic:\n\t\tvoid OnTransportCongestionControlServerSendRtcpPacket(\n\t\t  RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) override;\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t/* Pure virtual methods inherited from RTC::SenderBandwidthEstimator::Listener. */\n\tpublic:\n\t\tvoid OnSenderBandwidthEstimatorAvailableBitrate(\n\t\t  RTC::SenderBandwidthEstimator* senderBwe,\n\t\t  uint32_t availableBitrate,\n\t\t  uint32_t previousAvailableBitrate) override;\n#endif\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tpublic:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\n\tprotected:\n\t\tSharedInterface* shared{ nullptr };\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\t// Allocated by this.\n\t\tabsl::flat_hash_map<std::string, RTC::Producer*> mapProducers;\n\t\tabsl::flat_hash_map<std::string, RTC::Consumer*> mapConsumers;\n\t\tabsl::flat_hash_map<std::string, RTC::DataProducer*> mapDataProducers;\n\t\tabsl::flat_hash_map<std::string, RTC::DataConsumer*> mapDataConsumers;\n\t\tabsl::flat_hash_map<uint32_t, RTC::Consumer*> mapSsrcConsumer;\n\t\tabsl::flat_hash_map<uint32_t, RTC::Consumer*> mapRtxSsrcConsumer;\n\t\tTimerHandleInterface* rtcpTimer{ nullptr };\n\t\t// Allocated by this.\n\t\tstd::unique_ptr<RTC::SCTP::AssociationInterface> sctpAssociation{ nullptr };\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tRTC::SctpAssociation* oldSctpAssociation{ nullptr };\n\t\tstd::shared_ptr<RTC::TransportCongestionControlClient> tccClient{ nullptr };\n\t\tstd::shared_ptr<RTC::TransportCongestionControlServer> tccServer{ nullptr };\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\tstd::shared_ptr<RTC::SenderBandwidthEstimator> senderBwe{ nullptr };\n#endif\n\t\t// Others.\n\t\tbool direct{ false }; // Whether this Transport allows direct communication.\n\t\tbool isDestroying{ false };\n\t\tstruct RTC::RTP::HeaderExtensionIds recvRtpHeaderExtensionIds;\n\t\tRTC::RtpListener rtpListener;\n\t\tRTC::SctpListener sctpListener;\n\t\tRTC::RateCalculator recvTransmission;\n\t\tRTC::RateCalculator sendTransmission;\n\t\tRTC::RtpDataCounter recvRtpTransmission;\n\t\tRTC::RtpDataCounter sendRtpTransmission;\n\t\tRTC::RtpDataCounter recvRtxTransmission;\n\t\tRTC::RtpDataCounter sendRtxTransmission;\n\t\tRTC::RtpDataCounter sendProbationTransmission;\n\t\tuint16_t transportWideCcSeq{ 0u };\n\t\tuint32_t initialAvailableOutgoingBitrate{ 600000u };\n\t\tuint32_t maxIncomingBitrate{ 0u };\n\t\tuint32_t maxOutgoingBitrate{ 0u };\n\t\tuint32_t minOutgoingBitrate{ 0u };\n\t\tsize_t maxMessageSize{ 262144u };\n\t\tstruct TraceEventTypes traceEventTypes;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TransportCongestionControlClient.hpp",
    "content": "#ifndef MS_RTC_TRANSPORT_CONGESTION_CONTROL_CLIENT_HPP\n#define MS_RTC_TRANSPORT_CONGESTION_CONTROL_CLIENT_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/BweType.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/ProbationGenerator.hpp\"\n#include \"RTC/TrendCalculator.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <libwebrtc/api/transport/goog_cc_factory.h>\n#include <libwebrtc/api/transport/network_types.h>\n#include <libwebrtc/call/rtp_transport_controller_send.h>\n#include <libwebrtc/modules/pacing/packet_router.h>\n#include <deque>\n\nnamespace RTC\n{\n\tconstexpr uint32_t TransportCongestionControlMinOutgoingBitrate{ 30000u };\n\n\tclass TransportCongestionControlClient : public webrtc::PacketRouter,\n\t                                         public webrtc::TargetTransferRateObserver,\n\t                                         public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tstruct Bitrates\n\t\t{\n\t\t\tuint32_t desiredBitrate{ 0u };\n\t\t\tuint32_t effectiveDesiredBitrate{ 0u };\n\t\t\tuint32_t minBitrate{ 0u };\n\t\t\tuint32_t maxBitrate{ 0u };\n\t\t\tuint32_t startBitrate{ 0u };\n\t\t\tuint32_t maxPaddingBitrate{ 0u };\n\t\t\tuint32_t availableBitrate{ 0u };\n\t\t};\n\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnTransportCongestionControlClientBitrates(\n\t\t\t  RTC::TransportCongestionControlClient* tccClient,\n\t\t\t  RTC::TransportCongestionControlClient::Bitrates& bitrates) = 0;\n\t\t\tvirtual void OnTransportCongestionControlClientSendRtpPacket(\n\t\t\t  RTC::TransportCongestionControlClient* tccClient,\n\t\t\t  RTC::RTP::Packet* packet,\n\t\t\t  const webrtc::PacedPacketInfo& pacingInfo) = 0;\n\t\t};\n\n\tpublic:\n\t\tTransportCongestionControlClient(\n\t\t  RTC::TransportCongestionControlClient::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  RTC::BweType bweType,\n\t\t  uint32_t initialAvailableBitrate,\n\t\t  uint32_t maxOutgoingBitrate,\n\t\t  uint32_t minOutgoingBitrate);\n\t\t~TransportCongestionControlClient() override;\n\n\tpublic:\n\t\tRTC::BweType GetBweType() const\n\t\t{\n\t\t\treturn this->bweType;\n\t\t}\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tvoid InsertPacket(webrtc::RtpPacketSendInfo& packetInfo);\n\t\twebrtc::PacedPacketInfo GetPacingInfo();\n\t\tvoid PacketSent(const webrtc::RtpPacketSendInfo& packetInfo, int64_t nowMs);\n\t\tvoid ReceiveEstimatedBitrate(uint32_t bitrate);\n\t\tvoid ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReportPacket* packet, float rtt, int64_t nowMs);\n\t\tvoid ReceiveRtcpTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket* feedback);\n\t\tvoid SetDesiredBitrate(uint32_t desiredBitrate, bool force);\n\t\tvoid SetMaxOutgoingBitrate(uint32_t maxBitrate);\n\t\tvoid SetMinOutgoingBitrate(uint32_t minBitrate);\n\t\tconst Bitrates& GetBitrates() const\n\t\t{\n\t\t\treturn this->bitrates;\n\t\t}\n\t\tuint32_t GetAvailableBitrate() const;\n\t\tdouble GetPacketLoss() const;\n\t\tvoid RescheduleNextAvailableBitrateEvent();\n\n\tprivate:\n\t\tvoid MayEmitAvailableBitrateEvent(uint32_t previousAvailableBitrate);\n\t\tvoid UpdatePacketLoss(double packetLoss);\n\t\tvoid ApplyBitrateUpdates();\n\n\t\tvoid InitializeController();\n\t\tvoid DestroyController();\n\n\t\t// jmillan: missing.\n\t\t// void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override;\n\n\t\t/* Pure virtual methods inherited from webrtc::TargetTransferRateObserver. */\n\tpublic:\n\t\tvoid OnTargetTransferRate(webrtc::TargetTransferRate targetTransferRate) override;\n\n\t\t/* Pure virtual methods inherited from webrtc::PacketRouter. */\n\tpublic:\n\t\tvoid SendPacket(RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) override;\n\t\tRTC::RTP::Packet* GeneratePadding(size_t size) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TimerHandleInterface. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Allocated by this.\n\t\twebrtc::NetworkControllerFactoryInterface* controllerFactory{ nullptr };\n\t\twebrtc::RtpTransportControllerSend* rtpTransportControllerSend{ nullptr };\n\t\tRTC::RTP::ProbationGenerator* probationGenerator{ nullptr };\n\t\tTimerHandleInterface* processTimer{ nullptr };\n\t\t// Others.\n\t\tRTC::BweType bweType;\n\t\tuint32_t initialAvailableBitrate{ 0u };\n\t\tuint32_t maxOutgoingBitrate{ 0u };\n\t\tuint32_t minOutgoingBitrate{ 0u };\n\t\tBitrates bitrates;\n\t\tbool availableBitrateEventCalled{ false };\n\t\tuint64_t lastAvailableBitrateEventAtMs{ 0u };\n\t\tRTC::TrendCalculator desiredBitrateTrend;\n\t\tstd::deque<double> packetLossHistory;\n\t\tdouble packetLoss{ 0 };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TransportCongestionControlServer.hpp",
    "content": "#ifndef MS_RTC_TRANSPORT_CONGESTION_CONTROL_SERVER_HPP\n#define MS_RTC_TRANSPORT_CONGESTION_CONTROL_SERVER_HPP\n\n#include \"common.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"RTC/BweType.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h>\n#include <deque>\n\nnamespace RTC\n{\n\tclass TransportCongestionControlServer : public webrtc::RemoteBitrateEstimator::Listener,\n\t                                         public TimerHandleInterface::Listener\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnTransportCongestionControlServerSendRtcpPacket(\n\t\t\t  RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) = 0;\n\t\t};\n\n\tpublic:\n\t\tTransportCongestionControlServer(\n\t\t  RTC::TransportCongestionControlServer::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  RTC::BweType bweType,\n\t\t  size_t maxRtcpPacketLen);\n\t\t~TransportCongestionControlServer() override;\n\n\tpublic:\n\t\tRTC::BweType GetBweType() const\n\t\t{\n\t\t\treturn this->bweType;\n\t\t}\n\t\tvoid TransportConnected();\n\t\tvoid TransportDisconnected();\n\t\tuint32_t GetAvailableBitrate() const\n\t\t{\n\t\t\tswitch (this->bweType)\n\t\t\t{\n\t\t\t\tcase RTC::BweType::REMB:\n\t\t\t\t\treturn this->rembServer->GetAvailableBitrate();\n\n\t\t\t\tdefault:\n\t\t\t\t\treturn 0u;\n\t\t\t}\n\t\t}\n\t\tdouble GetPacketLoss() const;\n\t\tvoid IncomingPacket(uint64_t nowMs, const RTC::RTP::Packet* packet);\n\t\tvoid SetMaxIncomingBitrate(uint32_t bitrate);\n\t\tvoid FillAndSendTransportCcFeedback();\n\n\tprivate:\n\t\t// Returns true if a feedback packet was sent.\n\t\tbool SendTransportCcFeedback();\n\t\tvoid MayDropOldPacketArrivalTimes(uint16_t seqNum, uint64_t nowMs);\n\t\tvoid MaySendLimitationRembFeedback(uint64_t nowMs);\n\t\tvoid UpdatePacketLoss(double packetLoss);\n\t\tvoid ResetTransportCcFeedback(uint8_t feedbackPacketCount);\n\n\t\t/* Pure virtual methods inherited from webrtc::RemoteBitrateEstimator::Listener. */\n\tpublic:\n\t\tvoid OnRembServerAvailableBitrate(\n\t\t  const webrtc::RemoteBitrateEstimator* remoteBitrateEstimator,\n\t\t  const std::vector<uint32_t>& ssrcs,\n\t\t  uint32_t availableBitrate) override;\n\n\t\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnTimer(TimerHandleInterface* timer) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Allocated by this.\n\t\tTimerHandleInterface* transportCcFeedbackSendPeriodicTimer{ nullptr };\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> transportCcFeedbackPacket;\n\t\twebrtc::RemoteBitrateEstimatorAbsSendTime* rembServer{ nullptr };\n\t\t// Others.\n\t\tRTC::BweType bweType;\n\t\tsize_t maxRtcpPacketLen{ 0u };\n\t\tuint8_t transportCcFeedbackPacketCount{ 0u };\n\t\tuint32_t transportCcFeedbackSenderSsrc{ 0u };\n\t\tuint32_t transportCcFeedbackMediaSsrc{ 0u };\n\t\tuint32_t maxIncomingBitrate{ 0u };\n\t\tuint64_t limitationRembSentAtMs{ 0u };\n\t\tuint8_t unlimitedRembCounter{ 0u };\n\t\tstd::deque<double> packetLossHistory;\n\t\tdouble packetLoss{ 0 };\n\t\t// Whether any packet with transport wide sequence number was received.\n\t\tbool transportWideSeqNumberReceived{ false };\n\t\tuint16_t transportCcFeedbackWideSeqNumStart{ 0u };\n\t\t// Map of arrival timestamp (ms) indexed by wide seq number.\n\t\tstd::map<uint16_t, uint64_t, RTC::SeqManager<uint16_t>::SeqLowerThan> mapPacketArrivalTimes;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TransportTuple.hpp",
    "content": "#ifndef MS_RTC_TRANSPORT_TUPLE_HPP\n#define MS_RTC_TRANSPORT_TUPLE_HPP\n\n#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"FBS/transport.h\"\n#include \"RTC/TcpConnection.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include <flatbuffers/flatbuffers.h>\n#include <string>\n\nnamespace RTC\n{\n\tclass TransportTuple\n\t{\n\tprotected:\n\t\tusing onSendCallback = const std::function<void(bool sent)>;\n\n\tpublic:\n\t\tenum class Protocol : uint8_t\n\t\t{\n\t\t\tUDP = 1,\n\t\t\tTCP = 2\n\t\t};\n\n\t\tstatic Protocol ProtocolFromFbs(FBS::Transport::Protocol protocol);\n\t\tstatic FBS::Transport::Protocol ProtocolToFbs(Protocol protocol);\n\t\tstatic uint64_t GenerateFnv1aHash(const uint8_t* data, size_t size);\n\n\tpublic:\n\t\tTransportTuple(RTC::UdpSocket* udpSocket, const struct sockaddr* udpRemoteAddr)\n\t\t  : udpSocket(udpSocket),\n\t\t    udpRemoteAddr(const_cast<struct sockaddr*>(udpRemoteAddr)),\n\t\t    protocol(Protocol::UDP)\n\t\t{\n\t\t\tGenerateHash();\n\t\t}\n\n\t\texplicit TransportTuple(RTC::TcpConnection* tcpConnection)\n\t\t  : tcpConnection(tcpConnection), protocol(Protocol::TCP)\n\t\t{\n\t\t\tGenerateHash();\n\t\t}\n\n\t\texplicit TransportTuple(const TransportTuple* tuple)\n\t\t  : hash(tuple->hash),\n\t\t    udpSocket(tuple->udpSocket),\n\t\t    udpRemoteAddr(tuple->udpRemoteAddr),\n\t\t    tcpConnection(tuple->tcpConnection),\n\t\t    localAnnouncedAddress(tuple->localAnnouncedAddress),\n\t\t    protocol(tuple->protocol)\n\t\t{\n\t\t\tif (protocol == TransportTuple::Protocol::UDP)\n\t\t\t{\n\t\t\t\tStoreUdpRemoteAddress();\n\t\t\t}\n\t\t}\n\n\tpublic:\n\t\tvoid CloseTcpConnection();\n\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\n\t\tvoid Dump(int indentation = 0) const;\n\n\t\tvoid StoreUdpRemoteAddress()\n\t\t{\n\t\t\t// Clone the given address into our address storage and make the sockaddr\n\t\t\t// pointer point to it.\n\t\t\tthis->udpRemoteAddrStorage = Utils::IP::CopyAddress(this->udpRemoteAddr);\n\t\t\tthis->udpRemoteAddr =\n\t\t\t  reinterpret_cast<struct sockaddr*>(std::addressof(this->udpRemoteAddrStorage));\n\t\t}\n\n\t\tbool Compare(const TransportTuple* tuple) const\n\t\t{\n\t\t\treturn this->hash == tuple->hash;\n\t\t}\n\n\t\tvoid SetLocalAnnouncedAddress(std::string& localAnnouncedAddress)\n\t\t{\n\t\t\tthis->localAnnouncedAddress = localAnnouncedAddress;\n\t\t}\n\n\t\tvoid Send(const uint8_t* data, size_t len, RTC::TransportTuple::onSendCallback* cb = nullptr)\n\t\t{\n\t\t\tif (this->protocol == Protocol::UDP)\n\t\t\t{\n\t\t\t\tthis->udpSocket->Send(data, len, this->udpRemoteAddr, cb);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->tcpConnection->Send(data, len, cb);\n\t\t\t}\n\t\t}\n\n\t\tProtocol GetProtocol() const\n\t\t{\n\t\t\treturn this->protocol;\n\t\t}\n\n\t\tconst struct sockaddr* GetLocalAddress() const\n\t\t{\n\t\t\tif (this->protocol == Protocol::UDP)\n\t\t\t{\n\t\t\t\treturn this->udpSocket->GetLocalAddress();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn this->tcpConnection->GetLocalAddress();\n\t\t\t}\n\t\t}\n\n\t\tconst struct sockaddr* GetRemoteAddress() const\n\t\t{\n\t\t\tif (this->protocol == Protocol::UDP)\n\t\t\t{\n\t\t\t\treturn static_cast<const struct sockaddr*>(this->udpRemoteAddr);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn this->tcpConnection->GetPeerAddress();\n\t\t\t}\n\t\t}\n\n\t\tsize_t GetRecvBytes() const\n\t\t{\n\t\t\tif (this->protocol == Protocol::UDP)\n\t\t\t{\n\t\t\t\treturn this->udpSocket->GetRecvBytes();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn this->tcpConnection->GetRecvBytes();\n\t\t\t}\n\t\t}\n\n\t\tsize_t GetSentBytes() const\n\t\t{\n\t\t\tif (this->protocol == Protocol::UDP)\n\t\t\t{\n\t\t\t\treturn this->udpSocket->GetSentBytes();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn this->tcpConnection->GetSentBytes();\n\t\t\t}\n\t\t}\n\n\tprivate:\n\t\tvoid SetHash();\n\t\tvoid GenerateHash();\n\n\tpublic:\n\t\tuint64_t hash{ 0u };\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tRTC::UdpSocket* udpSocket{ nullptr };\n\t\tstruct sockaddr* udpRemoteAddr{ nullptr };\n\t\tRTC::TcpConnection* tcpConnection{ nullptr };\n\t\tstd::string localAnnouncedAddress;\n\t\t// Others.\n\t\tstruct sockaddr_storage udpRemoteAddrStorage{};\n\t\tProtocol protocol;\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/TrendCalculator.hpp",
    "content": "#ifndef TREND_CALCULATOR_HPP\n#define TREND_CALCULATOR_HPP\n\n#include \"common.hpp\"\n\nnamespace RTC\n{\n\tclass TrendCalculator\n\t{\n\tpublic:\n\t\tstatic constexpr float DecreaseFactor{ 0.05f }; // per second.\n\n\tpublic:\n\t\texplicit TrendCalculator(float decreaseFactor = DecreaseFactor);\n\n\tpublic:\n\t\tuint32_t GetValue() const\n\t\t{\n\t\t\treturn this->value;\n\t\t}\n\t\tvoid Update(uint32_t value, uint64_t nowMs);\n\t\tvoid ForceUpdate(uint32_t value, uint64_t nowMs);\n\n\tprivate:\n\t\tfloat decreaseFactor{ DecreaseFactor };\n\t\tuint32_t value{ 0u };\n\t\tuint32_t highestValue{ 0u };\n\t\tuint64_t highestValueUpdatedAtMs{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/UdpSocket.hpp",
    "content": "#ifndef MS_RTC_UDP_SOCKET_HPP\n#define MS_RTC_UDP_SOCKET_HPP\n\n#include \"common.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"handles/UdpSocketHandle.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tclass UdpSocket : public ::UdpSocketHandle\n\t{\n\tpublic:\n\t\tclass Listener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~Listener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnUdpSocketPacketReceived(\n\t\t\t  RTC::UdpSocket* socket,\n\t\t\t  const uint8_t* data,\n\t\t\t  size_t len,\n\t\t\t  size_t bufferLen,\n\t\t\t  const struct sockaddr* remoteAddr) = 0;\n\t\t};\n\n\tpublic:\n\t\tUdpSocket(Listener* listener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags);\n\t\tUdpSocket(\n\t\t  Listener* listener,\n\t\t  std::string& ip,\n\t\t  uint16_t minPort,\n\t\t  uint16_t maxPort,\n\t\t  RTC::Transport::SocketFlags& flags,\n\t\t  uint64_t& portRangeHash);\n\t\t~UdpSocket() override;\n\n\t\t/* Pure virtual methods inherited from ::UdpSocketHandle. */\n\tpublic:\n\t\tvoid UserOnUdpDatagramReceived(\n\t\t  const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tListener* listener{ nullptr };\n\t\tbool fixedPort{ false };\n\t\tuint64_t portRangeHash{ 0u };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/WebRtcServer.hpp",
    "content": "#ifndef MS_RTC_WEBRTC_SERVER_HPP\n#define MS_RTC_WEBRTC_SERVER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"RTC/ICE/IceCandidate.hpp\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include \"RTC/TcpConnection.hpp\"\n#include \"RTC/TcpServer.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include \"RTC/WebRtcTransport.hpp\"\n#include <flatbuffers/flatbuffers.h>\n#include <absl/container/flat_hash_map.h>\n#include <absl/container/flat_hash_set.h>\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tclass WebRtcServer : public RTC::UdpSocket::Listener,\n\t                     public RTC::TcpServer::Listener,\n\t                     public RTC::TcpConnection::Listener,\n\t                     public RTC::WebRtcTransport::WebRtcTransportListener,\n\t                     public Channel::ChannelSocket::RequestHandler\n\t{\n\tprivate:\n\t\tstruct UdpSocketOrTcpServer\n\t\t{\n\t\t\t// Expose a constructor to use vector.emplace_back().\n\t\t\tUdpSocketOrTcpServer(\n\t\t\t  RTC::UdpSocket* udpSocket,\n\t\t\t  RTC::TcpServer* tcpServer,\n\t\t\t  std::string& announcedAddress,\n\t\t\t  bool exposeInternalIp)\n\t\t\t  : udpSocket(udpSocket),\n\t\t\t    tcpServer(tcpServer),\n\t\t\t    announcedAddress(announcedAddress),\n\t\t\t    exposeInternalIp(exposeInternalIp)\n\t\t\t{\n\t\t\t}\n\n\t\t\tRTC::UdpSocket* udpSocket;\n\t\t\tRTC::TcpServer* tcpServer;\n\t\t\tstd::string announcedAddress;\n\t\t\tbool exposeInternalIp;\n\t\t};\n\n\tprivate:\n\t\tstatic std::string GetLocalIceUsernameFragmentFromReceivedStunPacket(\n\t\t  const RTC::ICE::StunPacket* packet);\n\n\tpublic:\n\t\tWebRtcServer(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  const flatbuffers::Vector<flatbuffers::Offset<FBS::Transport::ListenInfo>>* listenInfos);\n\t\t~WebRtcServer() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::WebRtcServer::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tstd::vector<RTC::ICE::IceCandidate> GetIceCandidates(\n\t\t  bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp) const;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\tprivate:\n\t\tvoid OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid OnNonStunDataReceived(\n\t\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\n\t\t/* Pure virtual methods inherited from RTC::WebRtcTransport::WebRtcTransportListener. */\n\tpublic:\n\t\tvoid OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) override;\n\t\tvoid OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) override;\n\t\tvoid OnWebRtcTransportLocalIceUsernameFragmentAdded(\n\t\t  RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override;\n\t\tvoid OnWebRtcTransportLocalIceUsernameFragmentRemoved(\n\t\t  RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override;\n\t\tvoid OnWebRtcTransportTransportTupleAdded(\n\t\t  RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override;\n\t\tvoid OnWebRtcTransportTransportTupleRemoved(\n\t\t  RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override;\n\n\t\t/* Pure virtual methods inherited from RTC::UdpSocket::Listener. */\n\tpublic:\n\t\tvoid OnUdpSocketPacketReceived(\n\t\t  RTC::UdpSocket* socket,\n\t\t  const uint8_t* data,\n\t\t  size_t len,\n\t\t  size_t bufferLen,\n\t\t  const struct sockaddr* remoteAddr) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TcpServer::Listener. */\n\tpublic:\n\t\tvoid OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TcpConnection::Listener. */\n\tpublic:\n\t\tvoid OnTcpConnectionPacketReceived(\n\t\t  RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) override;\n\t\tconst std::string& GetId() const\n\t\t{\n\t\t\treturn this->id;\n\t\t}\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tstd::string id;\n\t\t// Passed by argument.\n\t\tSharedInterface* shared{ nullptr };\n\t\t// Vector of UdpSockets and TcpServers in the user given order.\n\t\tstd::vector<UdpSocketOrTcpServer> udpSocketOrTcpServers;\n\t\t// Set of WebRtcTransports.\n\t\tabsl::flat_hash_set<RTC::WebRtcTransport*> webRtcTransports;\n\t\t// Map of WebRtcTransports indexed by local ICE usernameFragment.\n\t\tabsl::flat_hash_map<std::string, RTC::WebRtcTransport*> mapLocalIceUsernameFragmentWebRtcTransport;\n\t\t// Map of WebRtcTransports indexed by TransportTuple.hash.\n\t\tabsl::flat_hash_map<uint64_t, RTC::WebRtcTransport*> mapTupleWebRtcTransport;\n\t\t// Whether the destructor has been called.\n\t\tbool closing{ false };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/RTC/WebRtcTransport.hpp",
    "content": "#ifndef MS_RTC_WEBRTC_TRANSPORT_HPP\n#define MS_RTC_WEBRTC_TRANSPORT_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"RTC/DtlsTransport.hpp\"\n#include \"RTC/ICE/IceCandidate.hpp\"\n#include \"RTC/ICE/IceServer.hpp\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include \"RTC/SrtpSession.hpp\"\n#include \"RTC/TcpConnection.hpp\"\n#include \"RTC/TcpServer.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tclass WebRtcTransport : public RTC::Transport,\n\t                        public RTC::UdpSocket::Listener,\n\t                        public RTC::TcpServer::Listener,\n\t                        public RTC::TcpConnection::Listener,\n\t                        public RTC::ICE::IceServer::Listener,\n\t                        public RTC::DtlsTransport::Listener\n\t{\n\tpublic:\n\t\tclass WebRtcTransportListener\n\t\t{\n\t\tpublic:\n\t\t\tvirtual ~WebRtcTransportListener() = default;\n\n\t\tpublic:\n\t\t\tvirtual void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) = 0;\n\t\t\tvirtual void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport)  = 0;\n\t\t\tvirtual void OnWebRtcTransportLocalIceUsernameFragmentAdded(\n\t\t\t  RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0;\n\t\t\tvirtual void OnWebRtcTransportLocalIceUsernameFragmentRemoved(\n\t\t\t  RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0;\n\t\t\tvirtual void OnWebRtcTransportTransportTupleAdded(\n\t\t\t  RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0;\n\t\t\tvirtual void OnWebRtcTransportTransportTupleRemoved(\n\t\t\t  RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0;\n\t\t};\n\n\tpublic:\n\t\tWebRtcTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  const FBS::WebRtcTransport::WebRtcTransportOptions* options);\n\t\tWebRtcTransport(\n\t\t  SharedInterface* shared,\n\t\t  const std::string& id,\n\t\t  RTC::Transport::Listener* listener,\n\t\t  WebRtcTransportListener* webRtcTransportListener,\n\t\t  const std::vector<RTC::ICE::IceCandidate>& iceCandidates,\n\t\t  const FBS::WebRtcTransport::WebRtcTransportOptions* options);\n\t\t~WebRtcTransport() override;\n\n\tpublic:\n\t\tflatbuffers::Offset<FBS::WebRtcTransport::GetStatsResponse> FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder);\n\t\tflatbuffers::Offset<FBS::WebRtcTransport::DumpResponse> FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const;\n\t\tvoid ProcessStunPacketFromWebRtcServer(\n\t\t  RTC::TransportTuple* tuple, const RTC::ICE::StunPacket* packet);\n\t\tvoid ProcessNonStunPacketFromWebRtcServer(\n\t\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid RemoveTuple(RTC::TransportTuple* tuple);\n\n\t\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\n\tpublic:\n\t\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\n\t\t/* Methods inherited from Channel::ChannelSocket::NotificationHandler. */\n\tpublic:\n\t\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\tprivate:\n\t\tbool IsConnected() const override;\n\t\tvoid MayRunDtlsTransport();\n\t\tvoid SendRtpPacket(\n\t\t  RTC::Consumer* consumer,\n\t\t  RTC::RTP::Packet* packet,\n\t\t  RTC::Transport::onSendCallback* cb = nullptr) override;\n\t\tvoid SendRtcpPacket(RTC::RTCP::Packet* packet) override;\n\t\tvoid SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override;\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  const uint8_t* msg,\n\t\t  size_t len,\n\t\t  uint32_t ppid,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tvoid SendMessage(\n\t\t  RTC::DataConsumer* dataConsumer,\n\t\t  RTC::SCTP::Message message,\n\t\t  onQueuedCallback* cb = nullptr) override;\n\t\tbool SendData(const uint8_t* data, size_t len) override;\n\t\tvoid RecvStreamClosed(uint32_t ssrc) override;\n\t\tvoid SendStreamClosed(uint32_t ssrc) override;\n\t\tvoid OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid OnDtlsDataReceived(const RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\t\tvoid OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen);\n\t\tvoid OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len);\n\n\t\t/* Pure virtual methods inherited from RTC::UdpSocket::Listener. */\n\tpublic:\n\t\tvoid OnUdpSocketPacketReceived(\n\t\t  RTC::UdpSocket* socket,\n\t\t  const uint8_t* data,\n\t\t  size_t len,\n\t\t  size_t bufferLen,\n\t\t  const struct sockaddr* remoteAddr) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TcpServer::Listener. */\n\tpublic:\n\t\tvoid OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) override;\n\n\t\t/* Pure virtual methods inherited from RTC::TcpConnection::Listener. */\n\tpublic:\n\t\tvoid OnTcpConnectionPacketReceived(\n\t\t  RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) override;\n\n\t\t/* Pure virtual methods inherited from RTC::ICE::IceServer::Listener. */\n\tpublic:\n\t\tvoid OnIceServerSendStunPacket(\n\t\t  const RTC::ICE::IceServer* iceServer,\n\t\t  const RTC::ICE::StunPacket* packet,\n\t\t  RTC::TransportTuple* tuple) override;\n\t\tvoid OnIceServerLocalUsernameFragmentAdded(\n\t\t  const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) override;\n\t\tvoid OnIceServerLocalUsernameFragmentRemoved(\n\t\t  const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) override;\n\t\tvoid OnIceServerTupleAdded(const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override;\n\t\tvoid OnIceServerTupleRemoved(const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override;\n\t\tvoid OnIceServerSelectedTuple(\n\t\t  const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override;\n\t\tvoid OnIceServerConnected(const RTC::ICE::IceServer* iceServer) override;\n\t\tvoid OnIceServerCompleted(const RTC::ICE::IceServer* iceServer) override;\n\t\tvoid OnIceServerDisconnected(const RTC::ICE::IceServer* iceServer) override;\n\n\t\t/* Pure virtual methods inherited from RTC::DtlsTransport::Listener. */\n\tpublic:\n\t\tvoid OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportConnected(\n\t\t  const RTC::DtlsTransport* dtlsTransport,\n\t\t  RTC::SrtpSession::CryptoSuite srtpCryptoSuite,\n\t\t  uint8_t* srtpLocalKey,\n\t\t  size_t srtpLocalKeyLen,\n\t\t  uint8_t* srtpRemoteKey,\n\t\t  size_t srtpRemoteKeyLen,\n\t\t  std::string& remoteCert) override;\n\t\tvoid OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) override;\n\t\tvoid OnDtlsTransportSendData(\n\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override;\n\t\tvoid OnDtlsTransportApplicationDataReceived(\n\t\t  const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override;\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tWebRtcTransportListener* webRtcTransportListener{ nullptr };\n\t\t// Allocated by this.\n\t\tRTC::ICE::IceServer* iceServer{ nullptr };\n\t\t// Map of UdpSocket/TcpServer and local announced address (if any).\n\t\tabsl::flat_hash_map<RTC::UdpSocket*, std::string> udpSockets;\n\t\tabsl::flat_hash_map<RTC::TcpServer*, std::string> tcpServers;\n\t\tRTC::DtlsTransport* dtlsTransport{ nullptr };\n\t\tRTC::SrtpSession* srtpRecvSession{ nullptr };\n\t\tRTC::SrtpSession* srtpSendSession{ nullptr };\n\t\t// Others.\n\t\t// Whether connect() was succesfully called.\n\t\tbool connectCalled{ false };\n\t\tstd::vector<RTC::ICE::IceCandidate> iceCandidates;\n\t\tRTC::DtlsTransport::Role dtlsRole{ RTC::DtlsTransport::Role::AUTO };\n\t};\n} // namespace RTC\n\n#endif\n"
  },
  {
    "path": "worker/include/Settings.hpp",
    "content": "#ifndef MS_SETTINGS_HPP\n#define MS_SETTINGS_HPP\n\n#include \"common.hpp\"\n#include \"LogLevel.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <string>\n#include <vector>\n\nclass Settings\n{\npublic:\n\tstruct LogTags\n\t{\n\t\tbool info{ false };\n\t\tbool ice{ false };\n\t\tbool dtls{ false };\n\t\tbool rtp{ false };\n\t\tbool srtp{ false };\n\t\tbool rtcp{ false };\n\t\tbool rtx{ false };\n\t\tbool bwe{ false };\n\t\tbool score{ false };\n\t\tbool simulcast{ false };\n\t\tbool svc{ false };\n\t\tbool sctp{ false };\n\t\tbool message{ false };\n\t};\n\npublic:\n\t// Struct holding the configuration.\n\tstruct Configuration\n\t{\n\t\tLogLevel logLevel{ LogLevel::LOG_ERROR };\n\t\tstruct LogTags logTags;\n\t\tuint16_t rtcMinPort{ 10000u };\n\t\tuint16_t rtcMaxPort{ 59999u };\n\t\tstd::string dtlsCertificateFile;\n\t\tstd::string dtlsPrivateKeyFile;\n\t\tstd::string libwebrtcFieldTrials{ \"WebRTC-Bwe-AlrLimitedBackoff/Enabled/\" };\n\t\tbool disableLiburing{ false };\n\t\tbool useBuiltInSctpStack{ false };\n\t};\n\npublic:\n\tstatic void SetConfiguration(int argc, char* argv[]);\n\tstatic void SetLogLevel(std::string& level);\n\tstatic void SetLogTags(const std::vector<std::string>& tags);\n\tstatic void PrintConfiguration();\n\tstatic void HandleRequest(Channel::ChannelRequest* request);\n\nprivate:\n\tstatic void SetDtlsCertificateAndPrivateKeyFiles();\n\npublic:\n\tstatic thread_local struct Configuration configuration;\n\nprivate:\n\tstatic const absl::flat_hash_map<std::string, LogLevel> String2LogLevel;\n\tstatic const absl::flat_hash_map<LogLevel, std::string> LogLevel2String;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/Shared.hpp",
    "content": "#ifndef MS_SHARED_HPP\n#define MS_SHARED_HPP\n\n#include \"DepLibUV.hpp\"\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelMessageRegistrator.hpp\"\n#include \"Channel/ChannelNotifier.hpp\"\n\nclass Shared : public SharedInterface\n{\npublic:\n\texplicit Shared(\n\t  Channel::ChannelMessageRegistrator* channelMessageRegistrator,\n\t  Channel::ChannelNotifier* channelNotifier);\n\n\t~Shared() override;\n\npublic:\n\tChannel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() override\n\t{\n\t\treturn this->channelMessageRegistrator.get();\n\t}\n\n\tChannel::ChannelNotifier* GetChannelNotifier() override\n\t{\n\t\treturn this->channelNotifier.get();\n\t}\n\n\tTimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) override;\n\n\tBackoffTimerHandleInterface* CreateBackoffTimer(\n\t  const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) override;\n\n\tuint64_t GetTimeMs() override\n\t{\n\t\treturn DepLibUV::GetTimeMs();\n\t}\n\n\tuint64_t GetTimeUs() override\n\t{\n\t\treturn DepLibUV::GetTimeUs();\n\t}\n\n\tuint64_t GetTimeNs() override\n\t{\n\t\treturn DepLibUV::GetTimeNs();\n\t}\n\n\tint64_t GetTimeMsInt64() override\n\t{\n\t\treturn DepLibUV::GetTimeMsInt64();\n\t}\n\n\tint64_t GetTimeUsInt64() override\n\t{\n\t\treturn DepLibUV::GetTimeUsInt64();\n\t}\n\nprivate:\n\tstd::unique_ptr<Channel::ChannelMessageRegistrator> channelMessageRegistrator;\n\tstd::unique_ptr<Channel::ChannelNotifier> channelNotifier;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/SharedInterface.hpp",
    "content": "#ifndef MS_SHARED_INTERFACE_HPP\n#define MS_SHARED_INTERFACE_HPP\n\n#include \"Channel/ChannelMessageRegistratorInterface.hpp\"\n// TODO: We should have a ChannelNotifierInterface class instead.\n#include \"Channel/ChannelNotifier.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n\nclass SharedInterface\n{\npublic:\n\tvirtual ~SharedInterface() = default;\n\npublic:\n\t/**\n\t * @todo We should have a ChannelMessageRegistratorInterface class instead.\n\t */\n\tvirtual Channel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() = 0;\n\n\t/**\n\t * @todo We should have a ChannelNotifierInterface class instead.\n\t */\n\tvirtual Channel::ChannelNotifier* GetChannelNotifier() = 0;\n\n\t/**\n\t * Creates a TimerHandle timer.\n\t *\n\t * @remarks\n\t * - The caller is responsible for freeing it.\n\t */\n\tvirtual TimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) = 0;\n\n\t/**\n\t * Creates a BackoffTimerHandle timer.\n\t *\n\t * @remarks\n\t * - The caller is responsible for freeing it.\n\t */\n\tvirtual BackoffTimerHandleInterface* CreateBackoffTimer(\n\t  const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) = 0;\n\n\t/**\n\t * Get current time in milliseconds.\n\t */\n\tvirtual uint64_t GetTimeMs() = 0;\n\n\t/**\n\t * Get current time in microseconds.\n\t */\n\tvirtual uint64_t GetTimeUs() = 0;\n\n\t/**\n\t * Get current time in nanoseconds.\n\t */\n\tvirtual uint64_t GetTimeNs() = 0;\n\n\t/**\n\t * @remarks\n\t * - Used within libwebrtc dependency which uses int64_t values for time\n\t *   representation.\n\t *\n\t * @todo Remove once not needed.\n\t */\n\tvirtual int64_t GetTimeMsInt64() = 0;\n\n\t/**\n\t * @remarks\n\t * - Used within libwebrtc dependency which uses int64_t values for time\n\t *   representation.\n\t *\n\t * @todo Remove once not needed.\n\t */\n\tvirtual int64_t GetTimeUsInt64() = 0;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/Utils/UnwrappedSequenceNumber.hpp",
    "content": "#ifndef MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP\n#define MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP\n\n#include \"common.hpp\"\n#include <limits>\n#include <ostream>\n#include <typeinfo>\n\nnamespace Utils\n{\n\t/**\n\t * UnwrappedSequenceNumber handles wrapping sequence numbers and unwraps\n\t * them to an int64_t value space, to allow wrapped sequence numbers to be\n\t * easily compared for ordering.\n\t *\n\t * Sequence numbers are expected to be monotonically increasing, but they\n\t * do not need to be unwrapped in order, as long as the difference to the\n\t * previous one is not larger than half the range of the wrapped sequence\n\t * number.\n\t */\n\ttemplate<typename T>\n\tclass UnwrappedSequenceNumber\n\t{\n\tpublic:\n\t\tstatic_assert(!std::numeric_limits<T>::is_signed, \"the wrapped type must be unsigned\");\n\t\tstatic_assert(\n\t\t  std::numeric_limits<T>::max() < std::numeric_limits<int64_t>::max(),\n\t\t  \"the wrapped type must be less than the int64_t value space\");\n\n\t\t/**\n\t\t * The unwrapper is a sort of factory and converts wrapped sequence\n\t\t * numbers to unwrapped ones.\n\t\t */\n\t\tclass Unwrapper\n\t\t{\n\t\tpublic:\n\t\t\tUnwrapper() = default;\n\n\t\t\tUnwrapper(const Unwrapper&) = default;\n\n\t\t\tUnwrapper& operator=(const Unwrapper&) = default;\n\n\t\tpublic:\n\t\t\t/**\n\t\t\t * Given a wrapped `value`, and with knowledge of its current last seen\n\t\t\t * largest number, will return a value that can be compared using normal\n\t\t\t * operators, such as less-than, greater-than etc.\n\t\t\t *\n\t\t\t * This will also update the Unwrapper's state, to track the last seen\n\t\t\t * largest value.\n\t\t\t */\n\t\t\tUnwrappedSequenceNumber<T> Unwrap(T value)\n\t\t\t{\n\t\t\t\tif (!this->lastValue)\n\t\t\t\t{\n\t\t\t\t\tthis->lastUnwrapped = value;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tconst uint64_t prev = this->lastValue->GetValue();\n\t\t\t\t\tconst uint64_t curr = value;\n\t\t\t\t\tauto delta          = static_cast<int64_t>(curr - prev);\n\t\t\t\t\tconst auto half     = static_cast<int64_t>(1) << (sizeof(T) * 8 - 1);\n\n\t\t\t\t\tif (delta < -half)\n\t\t\t\t\t{\n\t\t\t\t\t\tdelta += static_cast<int64_t>(1) << (sizeof(T) * 8);\n\t\t\t\t\t}\n\t\t\t\t\telse if (delta > half)\n\t\t\t\t\t{\n\t\t\t\t\t\tdelta -= static_cast<int64_t>(1) << (sizeof(T) * 8);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->lastUnwrapped += delta;\n\t\t\t\t}\n\n\t\t\t\tthis->lastValue = UnwrappedSequenceNumber<T>(value);\n\n\t\t\t\treturn UnwrappedSequenceNumber<T>(this->lastUnwrapped);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Similar to `Unwrap()`, but will not update the Unwrappers's internal\n\t\t\t * state.\n\t\t\t */\n\t\t\tUnwrappedSequenceNumber<T> PeekUnwrap(T value) const\n\t\t\t{\n\t\t\t\tif (!this->lastValue)\n\t\t\t\t{\n\t\t\t\t\treturn UnwrappedSequenceNumber<T>(value);\n\t\t\t\t}\n\n\t\t\t\tconst uint64_t prev = this->lastValue->GetValue();\n\t\t\t\tconst uint64_t curr = value;\n\t\t\t\tauto delta          = static_cast<int64_t>(curr - prev);\n\t\t\t\tconst auto half     = static_cast<int64_t>(1) << (sizeof(T) * 8 - 1);\n\n\t\t\t\tif (delta < -half)\n\t\t\t\t{\n\t\t\t\t\tdelta += static_cast<int64_t>(1) << (sizeof(T) * 8);\n\t\t\t\t}\n\t\t\t\telse if (delta > half)\n\t\t\t\t{\n\t\t\t\t\tdelta -= static_cast<int64_t>(1) << (sizeof(T) * 8);\n\t\t\t\t}\n\n\t\t\t\treturn UnwrappedSequenceNumber<T>(this->lastUnwrapped + delta);\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Resets the Unwrapper to its pristine state. Used when a sequence number\n\t\t\t * is to be reset to zero.\n\t\t\t */\n\t\t\tvoid Reset()\n\t\t\t{\n\t\t\t\tthis->lastUnwrapped = 0;\n\t\t\t\tthis->lastValue.reset();\n\t\t\t}\n\n\t\tprivate:\n\t\t\tint64_t lastUnwrapped{ 0 };\n\t\t\tstd::optional<UnwrappedSequenceNumber<T>> lastValue;\n\t\t};\n\n\tpublic:\n\t\t/**\n\t\t * Returns a new sequence number based on `value`, and adding `delta`\n\t\t * (which may be negative).\n\t\t */\n\t\tstatic UnwrappedSequenceNumber<T> AddTo(UnwrappedSequenceNumber<T> value, int64_t delta)\n\t\t{\n\t\t\treturn UnwrappedSequenceNumber<T>(value.value + delta);\n\t\t}\n\n\t\t/**\n\t\t * Returns the absolute difference between `lhs` and `rhs`.\n\t\t */\n\t\tstatic T Difference(UnwrappedSequenceNumber<T> lhs, UnwrappedSequenceNumber<T> rhs)\n\t\t{\n\t\t\treturn (lhs.value > rhs.value) ? (lhs.value - rhs.value) : (rhs.value - lhs.value);\n\t\t}\n\n\tprivate:\n\t\tstatic constexpr auto ValueLimit = static_cast<int64_t>(1) << std::numeric_limits<T>::digits;\n\n\tpublic:\n\t\texplicit UnwrappedSequenceNumber(int64_t value) : value(value)\n\t\t{\n\t\t}\n\n\tpublic:\n\t\t/**\n\t\t * Returns the wrapped value this type represents.\n\t\t */\n\t\tT Wrap() const\n\t\t{\n\t\t\treturn static_cast<T>(this->value % UnwrappedSequenceNumber::ValueLimit);\n\t\t}\n\n\t\tbool operator==(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value == other.value;\n\t\t}\n\n\t\tbool operator!=(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value != other.value;\n\t\t}\n\n\t\tbool operator<(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value < other.value;\n\t\t}\n\n\t\tbool operator>(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value > other.value;\n\t\t}\n\n\t\tbool operator>=(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value >= other.value;\n\t\t}\n\n\t\tbool operator<=(const UnwrappedSequenceNumber<T>& other) const\n\t\t{\n\t\t\treturn this->value <= other.value;\n\t\t}\n\n\t\t// Const accessors for underlying value.\n\n\t\tconstexpr const int64_t* operator->() const\n\t\t{\n\t\t\treturn std::addressof(this->value);\n\t\t}\n\n\t\tconstexpr const int64_t& operator*() const&\n\t\t{\n\t\t\treturn this->value;\n\t\t}\n\n\t\tconstexpr const int64_t&& operator*() const&&\n\t\t{\n\t\t\treturn std::move(this->value);\n\t\t}\n\n\t\tconstexpr const int64_t& GetValue() const&\n\t\t{\n\t\t\treturn this->value;\n\t\t}\n\n\t\tconstexpr const int64_t&& GetValue() const&&\n\t\t{\n\t\t\treturn std::move(this->value);\n\t\t}\n\n\t\tconstexpr explicit operator const int64_t&() const&\n\t\t{\n\t\t\treturn this->value;\n\t\t}\n\n\t\t/**\n\t\t * Increments the value.\n\t\t */\n\t\tvoid Increment()\n\t\t{\n\t\t\t++this->value;\n\t\t}\n\n\t\t/**\n\t\t * Returns the next value relative to this sequence number.\n\t\t */\n\t\tUnwrappedSequenceNumber<T> GetNextValue() const\n\t\t{\n\t\t\treturn UnwrappedSequenceNumber<T>(this->value + 1);\n\t\t}\n\n\tprivate:\n\t\tint64_t value{ 0 };\n\t};\n\n\t/**\n\t * For logging purposes in Catch2 tests.\n\t */\n\ttemplate<typename T>\n\tinline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<T>& s)\n\t{\n\t\treturn os << \"{T:\" << typeid(T).name() << \", wrapped:\" << s.Wrap();\n\t}\n\n\ttemplate<>\n\tinline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint8_t>& s)\n\t{\n\t\treturn os << \"{T:uint8_t, wrapped:\" << s.Wrap() << \"}\";\n\t}\n\n\ttemplate<>\n\tinline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint16_t>& s)\n\t{\n\t\treturn os << \"{T:uint16_t, wrapped:\" << s.Wrap() << \"}\";\n\t}\n\n\ttemplate<>\n\tinline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint32_t>& s)\n\t{\n\t\treturn os << \"{T:uint32_t, wrapped:\" << s.Wrap() << \"}\";\n\t}\n} // namespace Utils\n\n#endif\n"
  },
  {
    "path": "worker/include/Utils.hpp",
    "content": "#ifndef MS_UTILS_HPP\n#define MS_UTILS_HPP\n\n#include \"common.hpp\"\n#include \"RTC/Consts.hpp\"\n#include <openssl/evp.h>\n#include <cassert>\n#include <cmath>\n#include <cstring> // std::memcmp(), std::memcpy()\n#include <limits>  // std::numeric_limits\n#include <random>  // std::mt19937_64, std::uniform_int_distribution, std::random_device\n#include <string>\n#include <type_traits> // std::enable_if, std::is_same_v, std::is_unsigned\n#ifdef _WIN32\n#include <ws2ipdef.h>\n// https://stackoverflow.com/a/24550632/2085408\n#include <intrin.h>\n#define __builtin_popcount __popcnt\n#endif\n\nnamespace Utils\n{\n\tclass IP\n\t{\n\tpublic:\n\t\tstatic int GetFamily(const std::string& ip);\n\n\t\tstatic void GetAddressInfo(const struct sockaddr* addr, int& family, std::string& ip, uint16_t& port);\n\n\t\tstatic size_t GetAddressLen(const struct sockaddr* addr);\n\n\t\tstatic bool CompareAddresses(const struct sockaddr* addr1, const struct sockaddr* addr2)\n\t\t{\n\t\t\t// Compare family.\n\t\t\tif (\n\t\t\t  addr1->sa_family != addr2->sa_family ||\n\t\t\t  (addr1->sa_family != AF_INET && addr1->sa_family != AF_INET6) ||\n\t\t\t  (addr2->sa_family != AF_INET && addr2->sa_family != AF_INET6))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Compare port.\n\t\t\tif (\n\t\t\t  reinterpret_cast<const struct sockaddr_in*>(addr1)->sin_port !=\n\t\t\t  reinterpret_cast<const struct sockaddr_in*>(addr2)->sin_port)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Compare IP.\n\t\t\tswitch (addr1->sa_family)\n\t\t\t{\n\t\t\t\tcase AF_INET:\n\t\t\t\t{\n\t\t\t\t\treturn (\n\t\t\t\t\t  reinterpret_cast<const struct sockaddr_in*>(addr1)->sin_addr.s_addr ==\n\t\t\t\t\t  reinterpret_cast<const struct sockaddr_in*>(addr2)->sin_addr.s_addr);\n\t\t\t\t}\n\n\t\t\t\tcase AF_INET6:\n\t\t\t\t{\n\t\t\t\t\treturn (\n\t\t\t\t\t  std::memcmp(\n\t\t\t\t\t    std::addressof(reinterpret_cast<const struct sockaddr_in6*>(addr1)->sin6_addr),\n\t\t\t\t\t    std::addressof(reinterpret_cast<const struct sockaddr_in6*>(addr2)->sin6_addr),\n\t\t\t\t\t    16) == 0);\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstatic struct sockaddr_storage CopyAddress(const struct sockaddr* addr)\n\t\t{\n\t\t\tstruct sockaddr_storage copiedAddr{};\n\n\t\t\tswitch (addr->sa_family)\n\t\t\t{\n\t\t\t\tcase AF_INET:\n\t\t\t\t\tstd::memcpy(std::addressof(copiedAddr), addr, sizeof(struct sockaddr_in));\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase AF_INET6:\n\t\t\t\t\tstd::memcpy(std::addressof(copiedAddr), addr, sizeof(struct sockaddr_in6));\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:;\n\t\t\t}\n\n\t\t\treturn copiedAddr;\n\t\t}\n\n\t\tstatic std::string NormalizeIp(std::string& ip);\n\t};\n\n\tclass File\n\t{\n\tpublic:\n\t\tstatic void CheckFile(const char* file);\n\t};\n\n\tclass Byte\n\t{\n\tpublic:\n\t\t/**\n\t\t * Getters below get value in Host Byte Order.\n\t\t * Setters below set value in Network Byte Order.\n\t\t */\n\t\tstatic uint8_t Get1Byte(const uint8_t* data, size_t i)\n\t\t{\n\t\t\treturn data[i];\n\t\t}\n\n\t\tstatic uint16_t Get2Bytes(const uint8_t* data, size_t i)\n\t\t{\n\t\t\treturn uint16_t{ data[i + 1] } | uint16_t{ data[i] } << 8;\n\t\t}\n\n\t\tstatic uint32_t Get3Bytes(const uint8_t* data, size_t i)\n\t\t{\n\t\t\treturn uint32_t{ data[i + 2] } | uint32_t{ data[i + 1] } << 8 | uint32_t{ data[i] } << 16;\n\t\t}\n\n\t\tstatic int32_t Get3BytesSigned(const uint8_t* data, size_t i)\n\t\t{\n\t\t\tauto byte2 = data[i]; // The most significant byte.\n\t\t\tauto byte1 = data[i + 1];\n\t\t\tauto byte0 = data[i + 2]; // The less significant byte.\n\n\t\t\t// Check bit 7 (sign).\n\t\t\tconst uint8_t extension = byte2 & 0b10000000 ? 0b11111111 : 0b00000000;\n\n\t\t\treturn int32_t{ byte0 } | (int32_t{ byte1 } << 8) | (int32_t{ byte2 } << 16) |\n\t\t\t       (int32_t{ extension } << 24);\n\t\t}\n\n\t\tstatic uint32_t Get4Bytes(const uint8_t* data, size_t i)\n\t\t{\n\t\t\treturn uint32_t{ data[i + 3] } | uint32_t{ data[i + 2] } << 8 |\n\t\t\t       uint32_t{ data[i + 1] } << 16 | uint32_t{ data[i] } << 24;\n\t\t}\n\n\t\tstatic uint64_t Get8Bytes(const uint8_t* data, size_t i)\n\t\t{\n\t\t\treturn uint64_t{ Byte::Get4Bytes(data, i) } << 32 | Byte::Get4Bytes(data, i + 4);\n\t\t}\n\n\t\tstatic void Set1Byte(uint8_t* data, size_t i, uint8_t value)\n\t\t{\n\t\t\tdata[i] = value;\n\t\t}\n\n\t\tstatic void Set2Bytes(uint8_t* data, size_t i, uint16_t value)\n\t\t{\n\t\t\tdata[i + 1] = static_cast<uint8_t>(value);\n\t\t\tdata[i]     = static_cast<uint8_t>(value >> 8);\n\t\t}\n\n\t\tstatic void Set3Bytes(uint8_t* data, size_t i, uint32_t value)\n\t\t{\n\t\t\tdata[i + 2] = static_cast<uint8_t>(value);\n\t\t\tdata[i + 1] = static_cast<uint8_t>(value >> 8);\n\t\t\tdata[i]     = static_cast<uint8_t>(value >> 16);\n\t\t}\n\n\t\tstatic void Set3BytesSigned(uint8_t* data, size_t i, int32_t value)\n\t\t{\n\t\t\tdata[i + 2] = static_cast<int8_t>(value);\n\t\t\tdata[i + 1] = static_cast<uint8_t>(value >> 8);\n\t\t\tdata[i]     = static_cast<uint8_t>(value >> 16);\n\t\t}\n\n\t\tstatic void Set4Bytes(uint8_t* data, size_t i, uint32_t value)\n\t\t{\n\t\t\tdata[i + 3] = static_cast<uint8_t>(value);\n\t\t\tdata[i + 2] = static_cast<uint8_t>(value >> 8);\n\t\t\tdata[i + 1] = static_cast<uint8_t>(value >> 16);\n\t\t\tdata[i]     = static_cast<uint8_t>(value >> 24);\n\t\t}\n\n\t\tstatic void Set8Bytes(uint8_t* data, size_t i, uint64_t value)\n\t\t{\n\t\t\tdata[i + 7] = static_cast<uint8_t>(value);\n\t\t\tdata[i + 6] = static_cast<uint8_t>(value >> 8);\n\t\t\tdata[i + 5] = static_cast<uint8_t>(value >> 16);\n\t\t\tdata[i + 4] = static_cast<uint8_t>(value >> 24);\n\t\t\tdata[i + 3] = static_cast<uint8_t>(value >> 32);\n\t\t\tdata[i + 2] = static_cast<uint8_t>(value >> 40);\n\t\t\tdata[i + 1] = static_cast<uint8_t>(value >> 48);\n\t\t\tdata[i]     = static_cast<uint8_t>(value >> 56);\n\t\t}\n\n\t\ttemplate<typename T>\n\t\ttypename std::enable_if<std::is_unsigned<T>::value, bool>::type static IsPaddedTo4Bytes(T size)\n\t\t{\n\t\t\treturn (size & 0x03) == 0u;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\ttypename std::enable_if<std::is_unsigned<T>::value, bool>::type static IsPaddedTo8Bytes(T size)\n\t\t{\n\t\t\treturn (size & 0x07) == 0u;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\ttypename std::enable_if<std::is_unsigned<T>::value, T>::type static PadTo4Bytes(T size)\n\t\t{\n\t\t\treturn (size + 3) & ~static_cast<T>(0x03);\n\t\t}\n\n\t\ttemplate<typename T>\n\t\ttypename std::enable_if<std::is_unsigned<T>::value, T>::type static PadDownTo4Bytes(T size)\n\t\t{\n\t\t\treturn size & ~static_cast<T>(0x03);\n\t\t}\n\n\t\ttemplate<typename T>\n\t\ttypename std::enable_if<std::is_unsigned<T>::value, T>::type static PadTo8Bytes(T size)\n\t\t{\n\t\t\treturn (size + 7) & ~static_cast<T>(0x07);\n\t\t}\n\t};\n\n\tclass Bits\n\t{\n\tpublic:\n\t\tstatic size_t CountSetBits(const uint16_t mask)\n\t\t{\n\t\t\treturn static_cast<size_t>(__builtin_popcount(mask));\n\t\t}\n\t};\n\n\tclass Crypto\n\t{\n\tpublic:\n\t\tstatic void ClassInit();\n\n\t\tstatic void ClassDestroy();\n\n\t\ttemplate<typename T>\n\t\tstatic T GetRandomUInt(T min, T max)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> || std::is_same_v<T, uint64_t> ||\n\t\t\t    std::is_same_v<T, size_t>,\n\t\t\t  \"T must be uint16_t, uint32_t, uint64_t, size_t\");\n\n\t\t\tstd::uniform_int_distribution<T> dist(min, max);\n\n\t\t\treturn dist(Crypto::rng);\n\t\t}\n\n\t\tstatic std::string GetRandomString(size_t len);\n\n\t\tstatic uint32_t GetCRC32(const uint8_t* data, size_t size);\n\n\t\tstatic uint32_t GetCRC32c(const uint8_t* data, size_t size);\n\n\t\tstatic const uint8_t* GetHmacSha1(const char* key, size_t keyLen, const uint8_t* data, size_t len);\n\n\t\tstatic void WriteRandomBytes(uint8_t* buffer, size_t len);\n\n\tprivate:\n\t\tstatic thread_local std::mt19937_64 rng;\n\t\tstatic thread_local EVP_MAC* mac;\n\t\tstatic thread_local EVP_MAC_CTX* hmacSha1Ctx;\n\t\tstatic thread_local uint8_t hmacSha1Buffer[];\n\t\tstatic const uint32_t Crc32Table[256];\n\t\tstatic const uint32_t Crc32cTable[256];\n\t};\n\n\tclass String\n\t{\n\tpublic:\n\t\tstatic void ToLowerCase(std::string& str)\n\t\t{\n\t\t\tstd::transform(str.begin(), str.end(), str.begin(), ::tolower);\n\t\t}\n\n\t\tstatic std::string Base64Encode(const uint8_t* data, size_t len);\n\n\t\tstatic std::string Base64Encode(const std::string& str);\n\n\t\tstatic uint8_t* Base64Decode(const uint8_t* data, size_t len, size_t& outLen);\n\n\t\tstatic uint8_t* Base64Decode(const std::string& str, size_t& outLen);\n\t};\n\n\tclass Number\n\t{\n\tpublic:\n\t\t// T is the base type (uint16_t, uint32_t, ...).\n\t\t// N is the max number of bits used in T.\n\t\ttemplate<typename T, uint8_t N = 0>\n\t\tstatic bool IsEqualThan(T lhs, T rhs)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||\n\t\t\t    std::is_same_v<T, uint64_t>,\n\t\t\t  \"T must be uint8_t, uint16_t, uint32_t or uint64_t\");\n\n\t\t\tconstexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\t\t\tlhs &= MaxValue;\n\t\t\trhs &= MaxValue;\n\n\t\t\treturn (lhs == rhs);\n\t\t}\n\n\t\t// T is the base type (uint16_t, uint32_t, ...).\n\t\t// N is the max number of bits used in T.\n\t\ttemplate<typename T, uint8_t N = 0>\n\t\tstatic bool IsHigherThan(T lhs, T rhs)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||\n\t\t\t    std::is_same_v<T, uint64_t>,\n\t\t\t  \"T must be uint8_t, uint16_t, uint32_t or uint64_t\");\n\n\t\t\tconstexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\t\t\tlhs &= MaxValue;\n\t\t\trhs &= MaxValue;\n\n\t\t\treturn ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) ||\n\t\t\t       ((rhs > lhs) && (rhs - lhs > MaxValue / 2));\n\t\t}\n\n\t\t// T is the base type (uint16_t, uint32_t, ...).\n\t\t// N is the max number of bits used in T.\n\t\ttemplate<typename T, uint8_t N = 0>\n\t\tstatic bool IsLowerThan(T lhs, T rhs)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||\n\t\t\t    std::is_same_v<T, uint64_t>,\n\t\t\t  \"T must be uint8_t, uint16_t, uint32_t or uint64_t\");\n\n\t\t\tconstexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\t\t\tlhs &= MaxValue;\n\t\t\trhs &= MaxValue;\n\n\t\t\treturn ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) ||\n\t\t\t       ((lhs > rhs) && (lhs - rhs > MaxValue / 2));\n\t\t}\n\n\t\t// T is the base type (uint16_t, uint32_t, ...).\n\t\t// N is the max number of bits used in T.\n\t\ttemplate<typename T, uint8_t N = 0>\n\t\tstatic bool IsHigherOrEqualThan(T lhs, T rhs)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||\n\t\t\t    std::is_same_v<T, uint64_t>,\n\t\t\t  \"T must be uint8_t, uint16_t, uint32_t or uint64_t\");\n\n\t\t\tconstexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\t\t\tlhs &= MaxValue;\n\t\t\trhs &= MaxValue;\n\n\t\t\treturn (lhs == rhs) || ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) ||\n\t\t\t       ((rhs > lhs) && (rhs - lhs > MaxValue / 2));\n\t\t}\n\n\t\t// T is the base type (uint16_t, uint32_t, ...).\n\t\t// N is the max number of bits used in T.\n\t\ttemplate<typename T, uint8_t N = 0>\n\t\tstatic bool IsLowerOrEqualThan(T lhs, T rhs)\n\t\t{\n\t\t\tstatic_assert(\n\t\t\t  std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||\n\t\t\t    std::is_same_v<T, uint64_t>,\n\t\t\t  \"T must be uint8_t, uint16_t, uint32_t or uint64_t\");\n\n\t\t\tconstexpr T MaxValue = (N == 0) ? std::numeric_limits<T>::max() : ((1 << N) - 1);\n\n\t\t\tlhs &= MaxValue;\n\t\t\trhs &= MaxValue;\n\n\t\t\treturn (lhs == rhs) || ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) ||\n\t\t\t       ((lhs > rhs) && (lhs - rhs > MaxValue / 2));\n\t\t}\n\n\t\t/**\n\t\t * Calculates the forward difference between two wrapping numbers.\n\t\t *\n\t\t * Example:\n\t\t * ```c++\n\t\t * uint8_t x = 253;\n\t\t * uint8_t y = 2;\n\t\t *\n\t\t * ForwardDiff(x, y) == 5\n\t\t * ```\n\t\t *\n\t\t *   252   253   254   255    0     1     2     3\n\t\t * #################################################\n\t\t * |     |  x  |     |     |     |     |  y  |     |\n\t\t * #################################################\n\t\t *          |----->----->----->----->----->\n\t\t *\n\t\t * ForwardDiff(y, x) == 251\n\t\t *\n\t\t *   252   253   254   255    0     1     2     3\n\t\t * #################################################\n\t\t * |     |  x  |     |     |     |     |  y  |     |\n\t\t * #################################################\n\t\t * -->----->                              |----->---\n\t\t *\n\t\t * If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the\n\t\t * largest value representable by T.\n\t\t */\n\t\ttemplate<typename T, T M>\n\t\tstatic T ForwardDiff(T a, T b) requires(M > 0)\n\t\t{\n\t\t\tstatic_assert(std::is_unsigned<T>::value, \"type must be an unsigned integer\");\n\n\t\t\tassert(a < M);\n\t\t\tassert(b < M);\n\n\t\t\treturn a <= b ? b - a : M - (a - b);\n\t\t}\n\n\t\ttemplate<typename T, T M>\n\t\tstatic T ForwardDiff(T a, T b) requires(M == 0)\n\t\t{\n\t\t\tstatic_assert(std::is_unsigned<T>::value, \"type must be an unsigned integer\");\n\n\t\t\treturn b - a;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tstatic T ForwardDiff(T a, T b)\n\t\t{\n\t\t\treturn ForwardDiff<T, 0>(a, b);\n\t\t}\n\n\t\t/**\n\t\t * Calculates the reverse difference between two wrapping numbers.\n\t\t *\n\t\t * Example:\n\t\t * ```c++\n\t\t * uint8_t x = 253;\n\t\t * uint8_t y = 2;\n\t\t *\n\t\t * ReverseDiff(y, x) == 5\n\t\t *\n\t\t *   252   253   254   255    0     1     2     3\n\t\t * #################################################\n\t\t * |     |  x  |     |     |     |     |  y  |     |\n\t\t * #################################################\n\t\t *          <-----<-----<-----<-----<-----|\n\t\t *\n\t\t * ReverseDiff(x, y) == 251\n\t\t *\n\t\t *   252   253   254   255    0     1     2     3\n\t\t * #################################################\n\t\t * |     |  x  |     |     |     |     |  y  |     |\n\t\t * #################################################\n\t\t * ---<-----|                             |<-----<--\n\t\t *\n\t\t * If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the\n\t\t * largest value representable by T.\n\t\t */\n\t\ttemplate<typename T, T M>\n\t\tstatic T ReverseDiff(T a, T b) requires(M > 0)\n\t\t{\n\t\t\tstatic_assert(std::is_unsigned<T>::value, \"type must be an unsigned integer\");\n\n\t\t\tassert(a < M);\n\t\t\tassert(b < M);\n\n\t\t\treturn b <= a ? a - b : M - (b - a);\n\t\t}\n\n\t\ttemplate<typename T, T M>\n\t\tstatic T ReverseDiff(T a, T b) requires(M == 0)\n\t\t{\n\t\t\tstatic_assert(std::is_unsigned<T>::value, \"type must be an unsigned integer\");\n\t\t\treturn a - b;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tstatic T ReverseDiff(T a, T b)\n\t\t{\n\t\t\treturn ReverseDiff<T, 0>(a, b);\n\t\t}\n\t};\n\n\tclass Time\n\t{\n\tprivate:\n\t\t// Seconds from Jan 1, 1900 to Jan 1, 1970.\n\t\tstatic constexpr uint32_t UnixNtpOffset{ 0x83AA7E80 };\n\t\t// NTP fractional unit.\n\t\tstatic constexpr uint64_t NtpFractionalUnit{ 1LL << 32 };\n\n\tpublic:\n\t\tstruct Ntp\n\t\t{\n\t\t\tuint32_t seconds;\n\t\t\tuint32_t fractions;\n\t\t};\n\n\t\tstatic Time::Ntp TimeMs2Ntp(uint64_t ms)\n\t\t{\n\t\t\tTime::Ntp ntp{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\t\tntp.seconds = ms / 1000;\n\t\t\tntp.fractions =\n\t\t\t  static_cast<uint32_t>((static_cast<double>(ms % 1000) / 1000) * NtpFractionalUnit);\n\n\t\t\treturn ntp;\n\t\t}\n\n\t\tstatic uint64_t Ntp2TimeMs(Time::Ntp ntp)\n\t\t{\n\t\t\treturn (\n\t\t\t  (static_cast<uint64_t>(ntp.seconds) * 1000) +\n\t\t\t  static_cast<uint64_t>(\n\t\t\t    std::round((static_cast<double>(ntp.fractions) * 1000) / NtpFractionalUnit)));\n\t\t}\n\n\t\tstatic uint32_t TimeMsToAbsSendTime(uint64_t ms)\n\t\t{\n\t\t\treturn static_cast<uint32_t>(((ms << 18) + 500) / 1000) & 0x00FFFFFF;\n\t\t}\n\t};\n\n\tclass BitStream\n\t{\n\tpublic:\n\t\tBitStream(uint8_t* data, size_t len);\n\t\t~BitStream() = default;\n\n\t\tconst uint8_t* GetData() const;\n\t\tsize_t GetLength() const;\n\t\tuint32_t GetOffset() const;\n\t\tvoid Reset();\n\t\tuint8_t GetBit();\n\t\tuint32_t GetBits(size_t count);\n\t\tuint32_t GetLeftBits() const;\n\t\tuint32_t GetNumBits(uint32_t n) const;\n\t\tstd::optional<uint32_t> ReadNs(uint32_t n);\n\t\tvoid SkipBits(size_t count);\n\t\tvoid Write(uint32_t offset, uint32_t n, uint32_t v);\n\t\tvoid PutBit(uint8_t bit);\n\t\tvoid PutBits(uint32_t count, uint32_t bits);\n\n\tprivate:\n\t\tvoid PutBit(uint32_t offset, uint8_t bit);\n\t\tvoid PutBits(uint32_t offset, uint32_t count, uint32_t bits);\n\n\tprivate:\n\t\tuint8_t data[RTC::Consts::TwoBytesRtpExtensionMaxLength];\n\t\tuint32_t len{ 0 };\n\t\tuint32_t offset{ 0 };\n\t};\n\n} // namespace Utils\n\n#endif\n"
  },
  {
    "path": "worker/include/Worker.hpp",
    "content": "#ifndef MS_WORKER_HPP\n#define MS_WORKER_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"FBS/worker.h\"\n#include \"RTC/Router.hpp\"\n#include \"RTC/WebRtcServer.hpp\"\n#include \"handles/SignalHandle.hpp\"\n#include <flatbuffers/flatbuffer_builder.h>\n#include <absl/container/flat_hash_map.h>\n#include <string>\n\nclass Worker : public Channel::ChannelSocket::Listener,\n               public SignalHandle::Listener,\n               public RTC::Router::Listener\n{\npublic:\n\texplicit Worker(Channel::ChannelSocket* channel, SharedInterface* shared);\n\t~Worker() override;\n\nprivate:\n\tvoid Close();\n\tflatbuffers::Offset<FBS::Worker::DumpResponse> FillBuffer(flatbuffers::FlatBufferBuilder& builder) const;\n\tflatbuffers::Offset<FBS::Worker::ResourceUsageResponse> FillBufferResourceUsage(\n\t  flatbuffers::FlatBufferBuilder& builder) const;\n\tvoid SetNewRouterId(std::string& routerId) const;\n\tRTC::WebRtcServer* GetWebRtcServer(const std::string& webRtcServerId) const;\n\tRTC::Router* GetRouter(const std::string& routerId) const;\n\tvoid CheckNoWebRtcServer(const std::string& webRtcServerId) const;\n\tvoid CheckNoRouter(const std::string& routerId) const;\n\n\t/* Methods inherited from Channel::ChannelSocket::RequestHandler. */\npublic:\n\tvoid HandleRequest(Channel::ChannelRequest* request) override;\n\tvoid HandleNotification(Channel::ChannelNotification* notification) override;\n\n\t/* Methods inherited from Channel::ChannelSocket::Listener. */\npublic:\n\tvoid OnChannelClosed(Channel::ChannelSocket* channel) override;\n\n\t/* Methods inherited from SignalHandle::Listener. */\npublic:\n\tvoid OnSignal(SignalHandle* signalsHandler, int signum) override;\n\n\t/* Pure virtual methods inherited from RTC::Router::Listener. */\npublic:\n\tRTC::WebRtcServer* OnRouterNeedWebRtcServer(RTC::Router* router, std::string& webRtcServerId) override;\n\nprivate:\n\t// Passed by argument.\n\tChannel::ChannelSocket* channel{ nullptr };\n\t// Allocated by this.\n\tSignalHandle* signalHandle{ nullptr };\n\tSharedInterface* shared{ nullptr };\n\tabsl::flat_hash_map<std::string, RTC::WebRtcServer*> mapWebRtcServers;\n\tabsl::flat_hash_map<std::string, RTC::Router*> mapRouters;\n\t// Others.\n\tbool closed{ false };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/common.hpp",
    "content": "#ifndef MS_COMMON_HPP\n#define MS_COMMON_HPP\n\n#include <algorithm> // std::transform(), std::find(), std::min(), std::max(), std::copy(), std::clamp(), std::ranges\n#include <cinttypes>  // PRIu64, etc\n#include <cstddef>    // size_t\n#include <cstdint>    // uint8_t, etc\n#include <functional> // std::function\n#include <memory>     // std::addressof(), std::unique_ptr(), etc\n#include <optional>\n#include <utility> // std::pair, std::move(), std::piecewise_construct\n#ifdef _WIN32\n#include <winsock2.h>\n// Avoid uv/win.h: error C2628 'intptr_t' followed by 'int' is illegal.\n#if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED)\n#include <BaseTsd.h>\ntypedef SSIZE_T ssize_t;\n#define SSIZE_MAX INTPTR_MAX\n#define _SSIZE_T_\n#define _SSIZE_T_DEFINED\n#endif\n#else\n#include <arpa/inet.h>  // htonl(), htons(), ntohl(), ntohs()\n#include <netinet/in.h> // sockaddr_in, sockaddr_in6\n#include <sys/socket.h> // struct sockaddr, struct sockaddr_storage, AF_INET, AF_INET6\n#endif\n\n// This is a macro to silence false warnings in GCC in switch() blocks with FBS\n// types.\n#if defined(__GNUC__) && !defined(__clang__)\n#define NO_DEFAULT_GCC()                                                                           \\\n\tdefault:                                                                                         \\\n\t\t__builtin_unreachable()\n#else\n#define NO_DEFAULT_GCC()\n#endif\n\nusing ChannelReadCtx    = void*;\nusing ChannelReadFreeFn = void (*)(uint8_t*, uint32_t, size_t);\n// Returns `ChannelReadFree` on successful read that must be used to free\n// `message`.\nusing ChannelReadFn = ChannelReadFreeFn (*)(\n  uint8_t** /*message*/,\n  uint32_t* /*messageLen*/,\n  size_t* /*messageCtx*/,\n  // This is `uv_async_t` handle that can be called later with `uv_async_send()`\n  // when there is more data to read.\n  const void* /*handle*/,\n  ChannelReadCtx /*ctx*/\n);\n\nusing ChannelWriteCtx = void*;\nusing ChannelWriteFn =\n  void (*)(const uint8_t* /*message*/, uint32_t /*messageLen*/, ChannelWriteCtx /*ctx*/);\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/BackoffTimerHandle.hpp",
    "content": "#ifndef MS_BACKOFF_TIMER_HANDLE_HPP\n#define MS_BACKOFF_TIMER_HANDLE_HPP\n\n#include \"common.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include \"handles/TimerHandle.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n\n// Forward declaration.\nclass Shared;\n\nclass BackoffTimerHandle : public BackoffTimerHandleInterface, public TimerHandleInterface::Listener\n{\n\t// Only Shared class can invoke the constructor.\n\tfriend class Shared;\n\nprivate:\n\texplicit BackoffTimerHandle(BackoffTimerHandleOptions options);\n\npublic:\n\tBackoffTimerHandle& operator=(const BackoffTimerHandle&) = delete;\n\n\tBackoffTimerHandle(const BackoffTimerHandle&) = delete;\n\n\t~BackoffTimerHandle() override;\n\npublic:\n\t/**\n\t * Start the BackoffTimer (if it's stopped) or restart it (if already\n\t * running). It will reset the timeout count.\n\t */\n\tvoid Start() override;\n\n\t/**\n\t * Stop the BackoffTimer. It will reset the timeout count.\n\t */\n\tvoid Stop() override;\n\n\t/**\n\t * Set the base timeout duration. It will be applied after the next timeout\n\t * and effective duration can be larger if backoff algorithm is exponential.\n\t */\n\tvoid SetBaseTimeoutMs(uint64_t baseTimeoutMs) override;\n\n\t/**\n\t * Whether the BackoffTimer is running. Useful to check if this BackoffTimer\n\t * will timeout again within the OnTimer() callback.\n\t */\n\tbool IsRunning() const override\n\t{\n\t\treturn this->running;\n\t}\n\n\tconst std::string GetLabel() const override\n\t{\n\t\treturn this->label;\n\t}\n\n\t/**\n\t * Maximum number of restarts.\n\t *\n\t * @remarks\n\t * - If `maxRestarts` was not given in the constructor, this method returns\n\t *   `std::nullopt`.\n\t */\n\tstd::optional<size_t> GetMaxRestarts() const override\n\t{\n\t\treturn this->maxRestarts;\n\t}\n\n\t/**\n\t * Number of times the timer has expired.\n\t */\n\tsize_t GetExpirationCount() const override\n\t{\n\t\treturn this->expirationCount;\n\t}\n\nprivate:\n\tuint64_t ComputeNextTimeoutMs() const;\n\n\t/* Pure virtual methods inherited from TimerHandleInterface::Listener. */\npublic:\n\tvoid OnTimer(TimerHandleInterface* timer) override;\n\nprivate:\n\t// Passed by argument.\n\tBackoffTimerHandleInterface::Listener* listener{ nullptr };\n\tconst std::string label;\n\tuint64_t baseTimeoutMs;\n\tBackoffAlgorithm backoffAlgorithm;\n\tstd::optional<uint64_t> maxBackoffTimeoutMs;\n\tstd::optional<size_t> maxRestarts;\n\t// Allocated by this.\n\tTimerHandle* timer{ nullptr };\n\t// Others.\n\tbool running{ false };\n\tsize_t expirationCount{ 0 };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/BackoffTimerHandleInterface.hpp",
    "content": "#ifndef MS_BACKOFF_TIMER_HANDLE_INTERFACE_HPP\n#define MS_BACKOFF_TIMER_HANDLE_INTERFACE_HPP\n\n#include \"common.hpp\"\n#include <limits> // std::numeric_limits()\n#include <string>\n\nclass BackoffTimerHandleInterface\n{\npublic:\n\tclass Listener\n\t{\n\tpublic:\n\t\tvirtual ~Listener() = default;\n\n\tpublic:\n\t\t/**\n\t\t * Invoked on timeout expiration. The parent can modify the base\n\t\t * timeout given as reference and affect the next timeout duration.\n\t\t *\n\t\t * @remarks\n\t\t * - If the caller deletes this BackoffTimer instance within the callback\n\t\t *   it must signal it be setting `stop` to true.\n\t\t */\n\t\tvirtual void OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) = 0;\n\t};\n\npublic:\n\tenum class BackoffAlgorithm : uint8_t\n\t{\n\t\t// The base duration will be used for any restart.\n\t\tFIXED,\n\t\t// An exponential backoff is used for restarts, with a 2x multiplier,\n\t\t// meaning that every restart will use a duration that is twice as long as\n\t\t// the previous.\n\t\tEXPONENTIAL,\n\t};\n\npublic:\n\tstruct BackoffTimerHandleOptions\n\t{\n\t\t/**\n\t\t * Listener on which `OnBackoffTimer()` callback will be invoked.\n\t\t */\n\t\tBackoffTimerHandleInterface::Listener* listener{ nullptr };\n\t\t/**\n\t\t * Label.\n\t\t */\n\t\tstd::string label;\n\t\t/**\n\t\t * Base timeout duration (ms).\n\t\t */\n\t\tuint64_t baseTimeoutMs;\n\t\t/**\n\t\t * Backoff algorithm.\n\t\t */\n\t\tBackoffAlgorithm backoffAlgorithm;\n\t\t/**\n\t\t * Maximum duration of the backoff timeout (ms). If no value is given, no\n\t\t * limit is set.\n\t\t */\n\t\tstd::optional<uint64_t> maxBackoffTimeoutMs;\n\t\t/**\n\t\t * Maximum number of restarts. If no value is given, it will restart\n\t\t * forever until stopped.\n\t\t */\n\t\tstd::optional<size_t> maxRestarts;\n\t};\n\npublic:\n\tstatic constexpr uint64_t MaxTimeoutMs{ std::numeric_limits<uint64_t>::max() / 2 };\n\npublic:\n\tBackoffTimerHandleInterface() = default;\n\n\tBackoffTimerHandleInterface& operator=(const BackoffTimerHandleInterface&) = delete;\n\n\tBackoffTimerHandleInterface(const BackoffTimerHandleInterface&) = delete;\n\n\tvirtual ~BackoffTimerHandleInterface() = default;\n\npublic:\n\t/**\n\t * Start the BackoffTimer (if it's stopped) or restart it (if already\n\t * running). It will reset the timeout count.\n\t */\n\tvirtual void Start() = 0;\n\n\t/**\n\t * Stop the BackoffTimer. It will reset the timeout count.\n\t */\n\tvirtual void Stop() = 0;\n\n\t/**\n\t * Set the base timeout duration. It will be applied after the next timeout\n\t * and effective duration can be larger if backoff algorithm is exponential.\n\t */\n\tvirtual void SetBaseTimeoutMs(uint64_t baseTimeoutMs) = 0;\n\n\t/**\n\t * Whether the BackoffTimer is running. Useful to check if this BackoffTimer\n\t * will timeout again within the OnTimer() callback.\n\t */\n\tvirtual bool IsRunning() const = 0;\n\n\tvirtual const std::string GetLabel() const = 0;\n\n\t/**\n\t * Maximum number of restarts.\n\t *\n\t * @remarks\n\t * - If `maxRestarts` was not given in the constructor, this method returns 0.\n\t */\n\tvirtual std::optional<size_t> GetMaxRestarts() const = 0;\n\n\t/**\n\t * Number of times the timer has expired.\n\t */\n\tvirtual size_t GetExpirationCount() const = 0;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/SignalHandle.hpp",
    "content": "#ifndef MS_SIGNAL_HANDLE_HPP\n#define MS_SIGNAL_HANDLE_HPP\n\n#include <uv.h>\n#include <string>\n#include <vector>\n\nclass SignalHandle\n{\npublic:\n\tclass Listener\n\t{\n\tpublic:\n\t\tvirtual ~Listener() = default;\n\n\tpublic:\n\t\tvirtual void OnSignal(SignalHandle* signalsHandler, int signum) = 0;\n\t};\n\npublic:\n\texplicit SignalHandle(Listener* listener);\n\t~SignalHandle();\n\npublic:\n\tvoid AddSignal(int signum, const std::string& name);\n\nprivate:\n\tvoid InternalClose();\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvSignal(int signum);\n\nprivate:\n\t// Passed by argument.\n\tListener* listener{ nullptr };\n\t// Allocated by this.\n\tstd::vector<uv_signal_t*> uvHandles;\n\t// Others.\n\tbool closed{ false };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/TcpConnectionHandle.hpp",
    "content": "#ifndef MS_TCP_CONNECTION_HANDLE_HPP\n#define MS_TCP_CONNECTION_HANDLE_HPP\n\n#include \"common.hpp\"\n#include <uv.h>\n#include <string>\n\nclass TcpConnectionHandle\n{\nprotected:\n\tusing onSendCallback = const std::function<void(bool sent)>;\n\npublic:\n\tclass Listener\n\t{\n\tpublic:\n\t\tvirtual ~Listener() = default;\n\n\tpublic:\n\t\tvirtual void OnTcpConnectionClosed(TcpConnectionHandle* connection) = 0;\n\t};\n\npublic:\n\t/* Struct for the data field of uv_req_t when writing into the connection. */\n\tstruct UvWriteData\n\t{\n\t\texplicit UvWriteData(size_t storeSize) : store(new uint8_t[storeSize])\n\t\t{\n\t\t}\n\n\t\t// Disable copy constructor because of the dynamically allocated data (store).\n\t\tUvWriteData(const UvWriteData&) = delete;\n\n\t\t~UvWriteData()\n\t\t{\n\t\t\tdelete[] this->store;\n\t\t\tdelete this->cb;\n\t\t}\n\n\t\tuv_write_t req{};\n\t\tuint8_t* store{ nullptr };\n\t\tTcpConnectionHandle::onSendCallback* cb{ nullptr };\n\t};\n\npublic:\n\texplicit TcpConnectionHandle(size_t bufferSize);\n\tTcpConnectionHandle& operator=(const TcpConnectionHandle&) = delete;\n\tTcpConnectionHandle(const TcpConnectionHandle&)            = delete;\n\tvirtual ~TcpConnectionHandle();\n\npublic:\n\tvoid TriggerClose();\n\tbool IsClosed() const\n\t{\n\t\treturn this->closed;\n\t}\n\tvoid Dump(int indentation = 0) const;\n\tvoid Setup(\n\t  Listener* listener,\n\t  struct sockaddr_storage* localAddr,\n\t  const std::string& localIp,\n\t  uint16_t localPort);\n\tuv_tcp_t* GetUvHandle() const\n\t{\n\t\treturn this->uvHandle;\n\t}\n\tvoid Start();\n\tvoid Write(\n\t  const uint8_t* data1,\n\t  size_t len1,\n\t  const uint8_t* data2,\n\t  size_t len2,\n\t  TcpConnectionHandle::onSendCallback* cb);\n\tvoid ErrorReceiving();\n\tconst struct sockaddr* GetLocalAddress() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(this->localAddr);\n\t}\n\tint GetLocalFamily() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(this->localAddr)->sa_family;\n\t}\n\tconst std::string& GetLocalIp() const\n\t{\n\t\treturn this->localIp;\n\t}\n\tuint16_t GetLocalPort() const\n\t{\n\t\treturn this->localPort;\n\t}\n\tconst struct sockaddr* GetPeerAddress() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(&this->peerAddr);\n\t}\n\tconst std::string& GetPeerIp() const\n\t{\n\t\treturn this->peerIp;\n\t}\n\tuint16_t GetPeerPort() const\n\t{\n\t\treturn this->peerPort;\n\t}\n\tsize_t GetRecvBytes() const\n\t{\n\t\treturn this->recvBytes;\n\t}\n\tsize_t GetSentBytes() const\n\t{\n\t\treturn this->sentBytes;\n\t}\n\nprivate:\n\tvoid InternalClose();\n\tbool SetPeerAddress();\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvReadAlloc(size_t suggestedSize, uv_buf_t* buf);\n\tvoid OnUvRead(ssize_t nread, const uv_buf_t* buf);\n\tvoid OnUvWrite(int status, onSendCallback* cb);\n\n\t/* Pure virtual methods that must be implemented by the subclass. */\nprotected:\n\tvirtual void UserOnTcpConnectionRead() = 0;\n\nprotected:\n\t// Passed by argument.\n\tsize_t bufferSize{ 0u };\n\t// Allocated by this.\n\tuint8_t* buffer{ nullptr };\n\t// Others.\n\tsize_t bufferDataLen{ 0u };\n\tstd::string localIp;\n\tuint16_t localPort{ 0u };\n\tstruct sockaddr_storage peerAddr{};\n\tstd::string peerIp;\n\tuint16_t peerPort{ 0u };\n\nprivate:\n\t// Passed by argument.\n\tListener* listener{ nullptr };\n\t// Allocated by this.\n\tuv_tcp_t* uvHandle{ nullptr };\n\t// Others.\n\tstruct sockaddr_storage* localAddr{ nullptr };\n#ifdef MS_LIBURING_SUPPORTED\n\t// Local file descriptor for io_uring.\n\tuv_os_fd_t fd{ 0u };\n#endif\n\tbool closed{ false };\n\tsize_t recvBytes{ 0u };\n\tsize_t sentBytes{ 0u };\n\tbool isClosedByPeer{ false };\n\tbool hasError{ false };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/TcpServerHandle.hpp",
    "content": "#ifndef MS_TCP_SERVER_HANDLE_HPP\n#define MS_TCP_SERVER_HANDLE_HPP\n\n#include \"common.hpp\"\n#include \"handles/TcpConnectionHandle.hpp\"\n#include <uv.h>\n#include <absl/container/flat_hash_set.h>\n#include <string>\n\nclass TcpServerHandle : public TcpConnectionHandle::Listener\n{\npublic:\n\t/**\n\t * uvHandle must be an already initialized and binded uv_tcp_t pointer.\n\t */\n\texplicit TcpServerHandle(uv_tcp_t* uvHandle);\n\t~TcpServerHandle() override;\n\npublic:\n\tvoid Dump(int indentation = 0) const;\n\tconst struct sockaddr* GetLocalAddress() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(&this->localAddr);\n\t}\n\tint GetLocalFamily() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(&this->localAddr)->sa_family;\n\t}\n\tconst std::string& GetLocalIp() const\n\t{\n\t\treturn this->localIp;\n\t}\n\tuint16_t GetLocalPort() const\n\t{\n\t\treturn this->localPort;\n\t}\n\tsize_t GetNumConnections() const\n\t{\n\t\treturn this->connections.size();\n\t}\n\tuint32_t GetSendBufferSize() const;\n\tvoid SetSendBufferSize(uint32_t size);\n\tuint32_t GetRecvBufferSize() const;\n\tvoid SetRecvBufferSize(uint32_t size);\n\nprotected:\n\tvoid AcceptTcpConnection(TcpConnectionHandle* connection);\n\nprivate:\n\tvoid InternalClose();\n\tbool SetLocalAddress();\n\n\t/* Pure virtual methods that must be implemented by the subclass. */\nprotected:\n\tvirtual void UserOnTcpConnectionAlloc()                                 = 0;\n\tvirtual void UserOnTcpConnectionClosed(TcpConnectionHandle* connection) = 0;\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvConnection(int status);\n\n\t/* Methods inherited from TcpConnectionHandle::Listener. */\npublic:\n\tvoid OnTcpConnectionClosed(TcpConnectionHandle* connection) override;\n\nprotected:\n\tstruct sockaddr_storage localAddr{};\n\tstd::string localIp;\n\tuint16_t localPort{ 0u };\n\nprivate:\n\t// Allocated by this (may be passed by argument).\n\tuv_tcp_t* uvHandle{ nullptr };\n\t// Others.\n\tabsl::flat_hash_set<TcpConnectionHandle*> connections;\n\tbool closed{ false };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/TimerHandle.hpp",
    "content": "#ifndef MS_TIMER_HANDLE_HPP\n#define MS_TIMER_HANDLE_HPP\n\n#include \"common.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n#include <uv.h>\n\n// Forward declaration.\nclass Shared;\nclass BackoffTimerHandle;\n\nclass TimerHandle : public TimerHandleInterface\n{\n\t// Only Shared and BackoffTimerHandle classes can invoke the constructor.\n\tfriend class Shared;\n\tfriend class BackoffTimerHandle;\n\nprivate:\n\texplicit TimerHandle(TimerHandleInterface::Listener* listener);\n\npublic:\n\tTimerHandle& operator=(const TimerHandle&) = delete;\n\n\tTimerHandle(const TimerHandle&) = delete;\n\n\t~TimerHandle() override;\n\npublic:\n\tvoid Start(uint64_t timeout, uint64_t repeat = 0) override;\n\n\tvoid Stop() override;\n\n\tvoid Restart() override;\n\n\tvoid Restart(uint64_t timeout, uint64_t repeat = 0) override;\n\n\tuint64_t GetTimeout() const override\n\t{\n\t\treturn this->timeout;\n\t}\n\n\tuint64_t GetRepeat() const override\n\t{\n\t\treturn this->repeat;\n\t}\n\n\tbool IsActive() const override\n\t{\n\t\treturn uv_is_active(reinterpret_cast<uv_handle_t*>(this->uvHandle)) != 0;\n\t}\n\nprivate:\n\tvoid InternalClose();\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvTimer();\n\nprivate:\n\t// Passed by argument.\n\tTimerHandleInterface::Listener* listener{ nullptr };\n\t// Allocated by this.\n\tuv_timer_t* uvHandle{ nullptr };\n\t// Others.\n\tbool closed{ false };\n\tuint64_t timeout{ 0u };\n\tuint64_t repeat{ 0u };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/TimerHandleInterface.hpp",
    "content": "#ifndef MS_TIMER_HANDLE_INTERFACE_HPP\n#define MS_TIMER_HANDLE_INTERFACE_HPP\n\n#include \"common.hpp\"\n\nclass TimerHandleInterface\n{\npublic:\n\tclass Listener\n\t{\n\tpublic:\n\t\tvirtual ~Listener() = default;\n\n\tpublic:\n\t\tvirtual void OnTimer(TimerHandleInterface* timer) = 0;\n\t};\n\npublic:\n\tTimerHandleInterface() = default;\n\n\tTimerHandleInterface& operator=(const TimerHandleInterface&) = delete;\n\n\tTimerHandleInterface(const TimerHandleInterface&) = delete;\n\n\tvirtual ~TimerHandleInterface() = default;\n\npublic:\n\tvirtual void Start(uint64_t timeout, uint64_t repeat = 0) = 0;\n\n\tvirtual void Stop() = 0;\n\n\tvirtual void Restart() = 0;\n\n\tvirtual void Restart(uint64_t timeout, uint64_t repeat = 0) = 0;\n\n\tvirtual uint64_t GetTimeout() const = 0;\n\n\tvirtual uint64_t GetRepeat() const = 0;\n\n\tvirtual bool IsActive() const = 0;\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/UdpSocketHandle.hpp",
    "content": "#ifndef MS_UDP_SOCKET_HANDLE_HPP\n#define MS_UDP_SOCKET_HANDLE_HPP\n\n#include \"common.hpp\"\n#include <uv.h>\n#include <string>\n\nclass UdpSocketHandle\n{\nprotected:\n\tusing onSendCallback = const std::function<void(bool sent)>;\n\npublic:\n\t/* Struct for the data field of uv_req_t when sending a datagram. */\n\tstruct UvSendData\n\t{\n\t\texplicit UvSendData(size_t storeSize) : store(new uint8_t[storeSize])\n\t\t{\n\t\t}\n\n\t\t// Disable copy constructor because of the dynamically allocated data (store).\n\t\tUvSendData(const UvSendData&) = delete;\n\n\t\t~UvSendData()\n\t\t{\n\t\t\tdelete[] this->store;\n\t\t\tdelete this->cb;\n\t\t}\n\n\t\tuv_udp_send_t req{};\n\t\tuint8_t* store{ nullptr };\n\t\tUdpSocketHandle::onSendCallback* cb{ nullptr };\n\t};\n\npublic:\n\t/**\n\t * uvHandle must be an already initialized and binded uv_udp_t pointer.\n\t */\n\texplicit UdpSocketHandle(uv_udp_t* uvHandle);\n\tUdpSocketHandle& operator=(const UdpSocketHandle&) = delete;\n\tUdpSocketHandle(const UdpSocketHandle&)            = delete;\n\tvirtual ~UdpSocketHandle();\n\npublic:\n\tbool IsClosed() const\n\t{\n\t\treturn this->closed;\n\t}\n\tvoid Dump(int indentation = 0) const;\n\tvoid Send(\n\t  const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandle::onSendCallback* cb);\n\tconst struct sockaddr* GetLocalAddress() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(&this->localAddr);\n\t}\n\tint GetLocalFamily() const\n\t{\n\t\treturn reinterpret_cast<const struct sockaddr*>(&this->localAddr)->sa_family;\n\t}\n\tconst std::string& GetLocalIp() const\n\t{\n\t\treturn this->localIp;\n\t}\n\tuint16_t GetLocalPort() const\n\t{\n\t\treturn this->localPort;\n\t}\n\tsize_t GetRecvBytes() const\n\t{\n\t\treturn this->recvBytes;\n\t}\n\tsize_t GetSentBytes() const\n\t{\n\t\treturn this->sentBytes;\n\t}\n\tuint32_t GetSendBufferSize() const;\n\tvoid SetSendBufferSize(uint32_t size);\n\tuint32_t GetRecvBufferSize() const;\n\tvoid SetRecvBufferSize(uint32_t size);\n\nprivate:\n\tvoid InternalClose();\n\tbool SetLocalAddress();\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvRecvAlloc(size_t suggestedSize, uv_buf_t* buf);\n\tvoid OnUvRecv(ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags);\n\tvoid OnUvSend(int status, UdpSocketHandle::onSendCallback* cb);\n\n\t/* Pure virtual methods that must be implemented by the subclass. */\nprotected:\n\tvirtual void UserOnUdpDatagramReceived(\n\t  const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr) = 0;\n\nprotected:\n\tstruct sockaddr_storage localAddr{};\n\tstd::string localIp;\n\tuint16_t localPort{ 0u };\n\nprivate:\n\t// Allocated by this (may be passed by argument).\n\tuv_udp_t* uvHandle{ nullptr };\n\t// Others.\n#ifdef MS_LIBURING_SUPPORTED\n\t// Local file descriptor for io_uring.\n\tuv_os_fd_t fd{ 0u };\n#endif\n\tbool closed{ false };\n\tsize_t recvBytes{ 0u };\n\tsize_t sentBytes{ 0u };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/handles/UnixStreamSocketHandle.hpp",
    "content": "#ifndef MS_UNIX_STREAM_SOCKET_HANDLE_HPP\n#define MS_UNIX_STREAM_SOCKET_HANDLE_HPP\n\n#include \"common.hpp\"\n#include <uv.h>\n\nclass UnixStreamSocketHandle\n{\npublic:\n\t/* Struct for the data field of uv_req_t when writing data. */\n\tstruct UvWriteData\n\t{\n\t\texplicit UvWriteData(size_t storeSize) : store(new uint8_t[storeSize])\n\t\t{\n\t\t}\n\n\t\t// Disable copy constructor because of the dynamically allocated data (store).\n\t\tUvWriteData(const UvWriteData&) = delete;\n\n\t\t~UvWriteData()\n\t\t{\n\t\t\tdelete[] this->store;\n\t\t}\n\n\t\tuv_write_t req{};\n\t\tuint8_t* store{ nullptr };\n\t};\n\n\tenum class Role : uint8_t\n\t{\n\t\tPRODUCER = 1,\n\t\tCONSUMER\n\t};\n\npublic:\n\tUnixStreamSocketHandle(int fd, size_t bufferSize, UnixStreamSocketHandle::Role role);\n\tUnixStreamSocketHandle& operator=(const UnixStreamSocketHandle&) = delete;\n\tUnixStreamSocketHandle(const UnixStreamSocketHandle&)            = delete;\n\tvirtual ~UnixStreamSocketHandle();\n\npublic:\n\tvoid Close();\n\tbool IsClosed() const\n\t{\n\t\treturn this->closed;\n\t}\n\tvoid Write(const uint8_t* data, size_t len);\n\tuint32_t GetSendBufferSize() const;\n\tvoid SetSendBufferSize(uint32_t size);\n\tuint32_t GetRecvBufferSize() const;\n\tvoid SetRecvBufferSize(uint32_t size);\n\n\t/* Callbacks fired by UV events. */\npublic:\n\tvoid OnUvReadAlloc(size_t suggestedSize, uv_buf_t* buf);\n\tvoid OnUvRead(ssize_t nread, const uv_buf_t* buf);\n\tvoid OnUvWriteError(int error);\n\n\t/* Pure virtual methods that must be implemented by the subclass. */\nprotected:\n\tvirtual void UserOnUnixStreamRead()         = 0;\n\tvirtual void UserOnUnixStreamSocketClosed() = 0;\n\nprivate:\n\t// Allocated by this.\n\tuv_pipe_t* uvHandle{ nullptr };\n\t// Others.\n\tbool closed{ false };\n\tbool isClosedByPeer{ false };\n\tbool hasError{ false };\n\nprotected:\n\t// Passed by argument.\n\tsize_t bufferSize{ 0u };\n\tUnixStreamSocketHandle::Role role;\n\t// Allocated by this.\n\tuint8_t* buffer{ nullptr };\n\t// Others.\n\tsize_t bufferDataLen{ 0u };\n};\n\n#endif\n"
  },
  {
    "path": "worker/include/lib.hpp",
    "content": "#include \"common.hpp\"\n\n// NOLINTNEXTLINE(readability-identifier-naming)\nextern \"C\" int mediasoup_worker_run(\n  int argc,\n  char* argv[],\n  const char* version,\n  int consumerChannelFd,\n  int producerChannelFd,\n  ChannelReadFn channelReadFn,\n  ChannelReadCtx channelReadCtx,\n  ChannelWriteFn channelWriteFn,\n  ChannelWriteCtx channelWriteCtx);\n"
  },
  {
    "path": "worker/meson.build",
    "content": "project(\n  'mediasoup-worker',\n  ['c', 'cpp'],\n  default_options : [\n    'cpp_std=c++20',\n    'warning_level=1',\n    'default_library=static',\n  ],\n  meson_version: '>= 1.1.0',\n)\n\ncpp_args = [\n  host_machine.endian() == 'little' ? '-DMS_LITTLE_ENDIAN' : '-DMS_BIG_ENDIAN',\n]\n\nif host_machine.system() == 'windows'\n  cpp_args += [\n    '-DNOMINMAX', # Don't define min and max macros (windows.h)\n    # Don't bloat namespace with incompatible winsock versions.\n    '-DWIN32_LEAN_AND_MEAN',\n    # Don't warn about usage of insecure C functions.\n    '-D_CRT_SECURE_NO_WARNINGS',\n    '-D_SCL_SECURE_NO_WARNINGS',\n    # Introduced in VS 2017 15.8, allow overaligned types in aligned_storage.\n    '-D_ENABLE_EXTENDED_ALIGNED_STORAGE',\n  ]\nendif\n\nif get_option('ms_build_tests')\n  cpp_args += [\n    '-DMS_TEST',\n    '-DMS_LOG_STD'\n  ]\nendif\n\nif get_option('ms_build_fuzzer')\n  cpp_args += [\n    '-DMS_FUZZER',\n    '-DMS_LOG_STD'\n  ]\nendif\n\nif get_option('ms_log_trace')\n  cpp_args += [\n    '-DMS_LOG_TRACE',\n  ]\nendif\n\nif get_option('ms_log_file_line')\n  cpp_args += [\n    '-DMS_LOG_FILE_LINE',\n  ]\nendif\n\nif get_option('ms_rtc_logger_rtp')\n  cpp_args += [\n    '-DMS_RTC_LOGGER_RTP',\n  ]\nendif\n\nif get_option('ms_dump_rtp_payload_descriptor')\n  cpp_args += [\n    '-DMS_DUMP_RTP_PAYLOAD_DESCRIPTOR',\n  ]\nendif\n\nif get_option('ms_dump_rtp_shared_packet_memory_usage')\n  cpp_args += [\n    '-DMS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE',\n  ]\nendif\n\ncpp = meson.get_compiler('cpp')\n\n# This is a workaround to define FLATBUFFERS_LOCALE_INDEPENDENT=0 outside\n# flatbuffers project, which is needed in case the current arch doesn't support\n# strtoll_l or strtoull_l (such as musl in Alpine Linux). Problem is that\n# flatbuffers build system not only defines it for its own usage but also relies\n# on it in its include/flatbuffers/util.h file, and if such a file is included\n# by mediasoup source files (and it is included) build fails with \"error:\n# 'strtoull_l' was not declared in this scope\".\n# See issue: https://github.com/versatica/mediasoup/issues/1223\nadd_project_arguments('-DFLATBUFFERS_LOCALE_INDEPENDENT=@0@'.format(cpp.has_function('strtoull_l')), language: 'cpp')\n\ncommon_sources = [\n  'src/lib.cpp',\n  'src/DepLibSRTP.cpp',\n  'src/DepLibUV.cpp',\n  'src/DepLibWebRTC.cpp',\n  'src/DepOpenSSL.cpp',\n  # TODO: Remove once we only use built-in SCTP stack.\n  'src/DepUsrSCTP.cpp',\n  'src/Logger.cpp',\n  'src/MediaSoupErrors.cpp',\n  'src/Settings.cpp',\n  'src/Shared.cpp',\n  'src/Worker.cpp',\n  'src/Utils/BitStream.cpp',\n  'src/Utils/Crypto.cpp',\n  'src/Utils/File.cpp',\n  'src/Utils/IP.cpp',\n  'src/Utils/String.cpp',\n  'src/handles/BackoffTimerHandle.cpp',\n  'src/handles/SignalHandle.cpp',\n  'src/handles/TcpConnectionHandle.cpp',\n  'src/handles/TcpServerHandle.cpp',\n  'src/handles/TimerHandle.cpp',\n  'src/handles/UdpSocketHandle.cpp',\n  'src/handles/UnixStreamSocketHandle.cpp',\n  'src/Channel/ChannelNotifier.cpp',\n  'src/Channel/ChannelRequest.cpp',\n  'src/Channel/ChannelMessageRegistrator.cpp',\n  'src/Channel/ChannelNotification.cpp',\n  'src/Channel/ChannelSocket.cpp',\n  'src/RTC/ActiveSpeakerObserver.cpp',\n  'src/RTC/AudioLevelObserver.cpp',\n  'src/RTC/Consumer.cpp',\n  'src/RTC/DataConsumer.cpp',\n  'src/RTC/DataProducer.cpp',\n  'src/RTC/DirectTransport.cpp',\n  'src/RTC/DtlsTransport.cpp',\n  'src/RTC/KeyFrameRequestManager.cpp',\n  'src/RTC/NackGenerator.cpp',\n  'src/RTC/PipeConsumer.cpp',\n  'src/RTC/PipeTransport.cpp',\n  'src/RTC/PlainTransport.cpp',\n  'src/RTC/PortManager.cpp',\n  'src/RTC/Producer.cpp',\n  'src/RTC/RateCalculator.cpp',\n  'src/RTC/Router.cpp',\n  'src/RTC/RtcLogger.cpp',\n  'src/RTC/RtpListener.cpp',\n  'src/RTC/RtpObserver.cpp',\n  # TODO: Remove once we only use built-in SCTP stack.\n  'src/RTC/SctpAssociation.cpp',\n  'src/RTC/SctpListener.cpp',\n  'src/RTC/SenderBandwidthEstimator.cpp',\n  'src/RTC/SeqManager.cpp',\n  'src/RTC/Serializable.cpp',\n  'src/RTC/SimpleConsumer.cpp',\n  'src/RTC/SimulcastConsumer.cpp',\n  'src/RTC/SrtpSession.cpp',\n  'src/RTC/SvcConsumer.cpp',\n  'src/RTC/TcpConnection.cpp',\n  'src/RTC/TcpServer.cpp',\n  'src/RTC/Transport.cpp',\n  'src/RTC/TransportCongestionControlClient.cpp',\n  'src/RTC/TransportCongestionControlServer.cpp',\n  'src/RTC/TransportTuple.cpp',\n  'src/RTC/TrendCalculator.cpp',\n  'src/RTC/UdpSocket.cpp',\n  'src/RTC/WebRtcServer.cpp',\n  'src/RTC/WebRtcTransport.cpp',\n  'src/RTC/RtpDictionaries/Parameters.cpp',\n  'src/RTC/RtpDictionaries/RtcpFeedback.cpp',\n  'src/RTC/RtpDictionaries/RtcpParameters.cpp',\n  'src/RTC/RtpDictionaries/RtpCodecMimeType.cpp',\n  'src/RTC/RtpDictionaries/RtpCodecParameters.cpp',\n  'src/RTC/RtpDictionaries/RtpEncodingParameters.cpp',\n  'src/RTC/RtpDictionaries/RtpHeaderExtensionParameters.cpp',\n  'src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp',\n  'src/RTC/RtpDictionaries/RtpParameters.cpp',\n  'src/RTC/RtpDictionaries/RtpRtxParameters.cpp',\n  'src/RTC/SctpDictionaries/SctpStreamParameters.cpp',\n  'src/RTC/ICE/IceCandidate.cpp',\n  'src/RTC/ICE/IceServer.cpp',\n  'src/RTC/ICE/StunPacket.cpp',\n  'src/RTC/RTP/Packet.cpp',\n  'src/RTC/RTP/ProbationGenerator.cpp',\n  'src/RTC/RTP/RetransmissionBuffer.cpp',\n  'src/RTC/RTP/RtpStream.cpp',\n  'src/RTC/RTP/RtpStreamRecv.cpp',\n  'src/RTC/RTP/RtpStreamSend.cpp',\n  'src/RTC/RTP/RtxStream.cpp',\n  'src/RTC/RTP/SharedPacket.cpp',\n  'src/RTC/RTP/Codecs/AV1.cpp',\n  'src/RTC/RTP/Codecs/H264.cpp',\n  'src/RTC/RTP/Codecs/Opus.cpp',\n  'src/RTC/RTP/Codecs/VP8.cpp',\n  'src/RTC/RTP/Codecs/VP9.cpp',\n  'src/RTC/RTP/Codecs/DependencyDescriptor.cpp',\n  'src/RTC/RTCP/Packet.cpp',\n  'src/RTC/RTCP/CompoundPacket.cpp',\n  'src/RTC/RTCP/SenderReport.cpp',\n  'src/RTC/RTCP/ReceiverReport.cpp',\n  'src/RTC/RTCP/Sdes.cpp',\n  'src/RTC/RTCP/Bye.cpp',\n  'src/RTC/RTCP/Feedback.cpp',\n  'src/RTC/RTCP/FeedbackPs.cpp',\n  'src/RTC/RTCP/FeedbackRtp.cpp',\n  'src/RTC/RTCP/FeedbackRtpNack.cpp',\n  'src/RTC/RTCP/FeedbackRtpTmmb.cpp',\n  'src/RTC/RTCP/FeedbackRtpSrReq.cpp',\n  'src/RTC/RTCP/FeedbackRtpTllei.cpp',\n  'src/RTC/RTCP/FeedbackRtpEcn.cpp',\n  'src/RTC/RTCP/FeedbackRtpTransport.cpp',\n  'src/RTC/RTCP/FeedbackPsPli.cpp',\n  'src/RTC/RTCP/FeedbackPsSli.cpp',\n  'src/RTC/RTCP/FeedbackPsRpsi.cpp',\n  'src/RTC/RTCP/FeedbackPsFir.cpp',\n  'src/RTC/RTCP/FeedbackPsTst.cpp',\n  'src/RTC/RTCP/FeedbackPsVbcm.cpp',\n  'src/RTC/RTCP/FeedbackPsLei.cpp',\n  'src/RTC/RTCP/FeedbackPsAfb.cpp',\n  'src/RTC/RTCP/FeedbackPsRemb.cpp',\n  'src/RTC/RTCP/XR.cpp',\n  'src/RTC/RTCP/XrDelaySinceLastRr.cpp',\n  'src/RTC/RTCP/XrReceiverReferenceTime.cpp',\n  'src/RTC/SCTP/association/Association.cpp',\n  'src/RTC/SCTP/association/AssociationListenerDeferrer.cpp',\n  'src/RTC/SCTP/association/HeartbeatHandler.cpp',\n  'src/RTC/SCTP/association/NegotiatedCapabilities.cpp',\n  'src/RTC/SCTP/association/PacketSender.cpp',\n  'src/RTC/SCTP/association/StateCookie.cpp',\n  'src/RTC/SCTP/association/StreamResetHandler.cpp',\n  'src/RTC/SCTP/association/TransmissionControlBlock.cpp',\n  'src/RTC/SCTP/rx/DataTracker.cpp',\n  'src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp',\n  'src/RTC/SCTP/rx/ReassemblyQueue.cpp',\n  'src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp',\n  'src/RTC/SCTP/tx/OutstandingData.cpp',\n  'src/RTC/SCTP/tx/RetransmissionErrorCounter.cpp',\n  'src/RTC/SCTP/tx/RetransmissionQueue.cpp',\n  'src/RTC/SCTP/tx/RetransmissionTimeout.cpp',\n  'src/RTC/SCTP/tx/RoundRobinSendQueue.cpp',\n  'src/RTC/SCTP/tx/StreamScheduler.cpp',\n  'src/RTC/SCTP/packet/Packet.cpp',\n  'src/RTC/SCTP/packet/TLV.cpp',\n  'src/RTC/SCTP/packet/UserData.cpp',\n  'src/RTC/SCTP/packet/Chunk.cpp',\n  'src/RTC/SCTP/packet/chunks/DataChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/InitChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/InitAckChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/SackChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/HeartbeatAckChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/AbortAssociationChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/ShutdownChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/ShutdownAckChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/OperationErrorChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/CookieEchoChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/CookieAckChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/ForwardTsnChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/ReConfigChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/IDataChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/IForwardTsnChunk.cpp',\n  'src/RTC/SCTP/packet/chunks/UnknownChunk.cpp',\n  'src/RTC/SCTP/packet/Parameter.cpp',\n  'src/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/IPv4AddressParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/IPv6AddressParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/StateCookieParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/CookiePreservativeParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.cpp',\n  'src/RTC/SCTP/packet/parameters/UnknownParameter.cpp',\n  'src/RTC/SCTP/packet/ErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.cpp',\n  'src/RTC/SCTP/packet/errorCauses/UnknownErrorCause.cpp',\n  'src/RTC/SCTP/public/AssociationMetrics.cpp',\n  'src/RTC/SCTP/public/Message.cpp',\n]\n\nlibuv_proj = subproject(\n  'libuv',\n  default_options: [\n    'warning_level=0',\n    'build_tests=false',\n    'build_benchmarks=false',\n  ],\n)\n\nopenssl_proj = subproject(\n  'openssl',\n  default_options: [\n    'warning_level=0',\n  ],\n)\n\nlibsrtp3_proj = subproject(\n  'libsrtp3',\n  default_options: [\n    'warning_level=0',\n    'crypto-library=openssl',\n    'crypto-library-kdf=disabled',\n    'tests=disabled',\n  ],\n)\n\nusrsctp_proj = subproject(\n  'usrsctp',\n  default_options: [\n    'warning_level=0',\n    'sctp_build_programs=false',\n  ],\n)\n\nabseil_cpp_proj = subproject(\n  'abseil-cpp',\n  default_options: [\n    'warning_level=0',\n    'cpp_std=c++17',\n  ],\n)\n\nflatbuffers_proj = subproject(\n  'flatbuffers',\n  default_options: [\n    'warning_level=0',\n  ],\n)\n\n# flatbuffers schemas subdirectory.\nsubdir('fbs')\n\n# Add current build directory so libwebrtc has access to FBS folder.\nlibwebrtc_include_directories = include_directories('include', 'fbs')\nsubdir('deps/libwebrtc')\n\ndependencies = [\n  abseil_cpp_proj.get_variable('absl_container_dep'),\n  openssl_proj.get_variable('openssl_dep'),\n  libuv_proj.get_variable('libuv_dep'),\n  libsrtp3_proj.get_variable('libsrtp3_dep'),\n  usrsctp_proj.get_variable('usrsctp_dep'),\n  flatbuffers_proj.get_variable('flatbuffers_dep'),\n  flatbuffers_generator_dep,\n  libwebrtc_dep,\n]\n\nlink_whole = [\n  abseil_cpp_proj.get_variable('absl_container_lib'),\n  openssl_proj.get_variable('libcrypto_lib'),\n  openssl_proj.get_variable('libssl_lib'),\n  libuv_proj.get_variable('libuv'),\n  libsrtp3_proj.get_variable('libsrtp3'),\n  usrsctp_proj.get_variable('usrsctp'),\n  flatbuffers_proj.get_variable('flatbuffers_lib'),\n  libwebrtc,\n]\n\nif host_machine.system() == 'windows'\n  wingetopt_proj = subproject(\n    'wingetopt',\n    default_options: [\n      'warning_level=0',\n    ],\n  )\n\n  dependencies += [\n    wingetopt_proj.get_variable('wingetopt_dep'),\n  ]\n  link_whole += [\n    wingetopt_proj.get_variable('wingetopt_lib'),\n  ]\nendif\n\nif host_machine.system() == 'linux' and not get_option('ms_disable_liburing')\n  kernel_version = run_command('uname', '-r', check: true).stdout().strip()\n\n  # Enable liburing for kernel versions greather than or equal to 6.\n  if kernel_version[0].to_int() >= 6\n    liburing_proj = subproject(\n      'liburing',\n      default_options: [\n        'default_library=static',\n        # NOTE: We need to add this to disable ASAN in liburing, otherwise when\n        # building `mediasoup-test-asan-undefined` binary, liburing receives\n        # `use_ubsan: true` and tries to compile its `sanitize.c` file\n        # that contains references to `__asan_*` functions, but we don't enable\n        # ASAN (`-fsanitize=address) in that binaries so liburing fails to\n        # compile.\n        'b_sanitize=none',\n      ],\n    )\n\n    dependencies += [\n      liburing_proj.get_variable('uring'),\n    ]\n    link_whole += [\n      liburing_proj.get_variable('liburing')\n    ]\n    common_sources += [\n      'src/DepLibUring.cpp',\n    ]\n    cpp_args += [\n      '-DMS_LIBURING_SUPPORTED',\n    ]\n  endif\nendif\n\nif get_option('ms_build_tests')\n  catch2_proj = subproject(\n    'catch2',\n    default_options: [\n      'warning_level=0',\n    ],\n  )\n\n  dependencies += [\n    catch2_proj.get_variable('catch2_dep'),\n  ]\nendif\n\nlibmediasoup_worker = library(\n  'libmediasoup-worker',\n  name_prefix: '',\n  build_by_default: false,\n  install: true,\n  install_tag: 'libmediasoup-worker',\n  dependencies: dependencies,\n  sources: common_sources,\n  include_directories: include_directories('include'),\n  cpp_args: cpp_args,\n  link_whole: link_whole,\n)\n\nexecutable(\n  'mediasoup-worker',\n  build_by_default: true,\n  install: true,\n  install_tag: 'mediasoup-worker',\n  dependencies: dependencies,\n  sources: common_sources + ['src/main.cpp'],\n  include_directories: include_directories('include'),\n  cpp_args: cpp_args + ['-DMS_EXECUTABLE'],\n)\n\nmock_sources = [\n  'mocks/src/MockShared.cpp',\n  'mocks/src/handles/MockBackoffTimerHandle.cpp',\n  'mocks/src/Channel/MockChannelMessageRegistrator.cpp',\n  'mocks/src/RTC/SCTP/association/MockTransmissionControlBlockContext.cpp',\n]\n\ntest_sources = [\n  'test/src/tests.cpp',\n  'test/src/testHelpers.cpp',\n  'test/src/RTC/TestKeyFrameRequestManager.cpp',\n  'test/src/RTC/TestNackGenerator.cpp',\n  'test/src/RTC/TestRateCalculator.cpp',\n  'test/src/RTC/TestRtpEncodingParameters.cpp',\n  'test/src/RTC/TestSeqManager.cpp',\n  'test/src/RTC/TestSimpleConsumer.cpp',\n  'test/src/RTC/TestTransportCongestionControlServer.cpp',\n  'test/src/RTC/TestTransportTuple.cpp',\n  'test/src/RTC/TestTrendCalculator.cpp',\n  'test/src/RTC/ICE/iceCommon.cpp',\n  'test/src/RTC/ICE/TestStunPacket.cpp',\n  'test/src/RTC/RTP/rtpCommon.cpp',\n  'test/src/RTC/RTP/TestPacket.cpp',\n  'test/src/RTC/RTP/TestProbationGenerator.cpp',\n  'test/src/RTC/RTP/TestRetransmissionBuffer.cpp',\n  'test/src/RTC/RTP/TestRtpStreamSend.cpp',\n  'test/src/RTC/RTP/TestRtpStreamRecv.cpp',\n  'test/src/RTC/RTP/TestSharedPacket.cpp',\n  'test/src/RTC/RTP/Codecs/TestH264.cpp',\n  'test/src/RTC/RTP/Codecs/TestVP8.cpp',\n  'test/src/RTC/RTP/Codecs/TestVP9.cpp',\n  'test/src/RTC/RTP/Codecs/TestDependencyDescriptor.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsAfb.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsFir.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsLei.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsPli.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsRemb.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsRpsi.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsSli.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsTst.cpp',\n  'test/src/RTC/RTCP/TestFeedbackPsVbcm.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpEcn.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpNack.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpSrReq.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpTllei.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpTmmb.cpp',\n  'test/src/RTC/RTCP/TestFeedbackRtpTransport.cpp',\n  'test/src/RTC/RTCP/TestBye.cpp',\n  'test/src/RTC/RTCP/TestReceiverReport.cpp',\n  'test/src/RTC/RTCP/TestSdes.cpp',\n  'test/src/RTC/RTCP/TestSenderReport.cpp',\n  'test/src/RTC/RTCP/TestPacket.cpp',\n  'test/src/RTC/RTCP/TestXr.cpp',\n  'test/src/RTC/SCTP/sctpCommon.cpp',\n  'test/src/RTC/SCTP/association/TestHeartbeatHandler.cpp',\n  'test/src/RTC/SCTP/association/TestNegotiatedCapabilities.cpp',\n  'test/src/RTC/SCTP/association/TestStateCookie.cpp',\n  'test/src/RTC/SCTP/rx/TestDataTracker.cpp',\n  'test/src/RTC/SCTP/rx/TestInterleavedReassemblyStreams.cpp',\n  'test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp',\n  'test/src/RTC/SCTP/rx/TestTraditionalReassemblyStreams.cpp',\n  'test/src/RTC/SCTP/tx/TestOutstandingData.cpp',\n  'test/src/RTC/SCTP/tx/TestRetransmissionErrorCounter.cpp',\n  'test/src/RTC/SCTP/tx/TestRetransmissionQueue.cpp',\n  'test/src/RTC/SCTP/tx/TestRetransmissionTimeout.cpp',\n  'test/src/RTC/SCTP/tx/TestRoundRobinSendQueue.cpp',\n  'test/src/RTC/SCTP/tx/TestOutstandingData.cpp',\n  'test/src/RTC/SCTP/tx/TestStreamScheduler.cpp',\n  'test/src/RTC/SCTP/packet/TestPacket.cpp',\n  'test/src/RTC/SCTP/packet/TestChunk.cpp',\n  'test/src/RTC/SCTP/packet/TestParameter.cpp',\n  'test/src/RTC/SCTP/packet/TestErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestDataChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestInitChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestInitAckChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestSackChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestHeartbeatRequestChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestHeartbeatAckChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestAbortAssociationChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestShutdownChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestShutdownAckChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestOperationErrorChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestCookieEchoChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestCookieAckChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestShutdownCompleteChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestForwardTsnChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestReConfigChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestIDataChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestIForwardTsnChunk.cpp',\n  'test/src/RTC/SCTP/packet/chunks/TestUnknownChunk.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestHeartbeatInfoParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestIPv4AddressParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestIPv6AddressParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestStateCookieParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestUnrecognizedParameterParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestCookiePreservativeParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestSupportedAddressTypesParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestForwardTsnSupportedParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestSupportedExtensionsParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestOutgoingSsnResetRequestParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestIncomingSsnResetRequestParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestSsnTsnResetRequestParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestReconfigurationResponseParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestAddOutgoingStreamsRequestParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestAddIncomingStreamsRequestParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestZeroChecksumAcceptableParameter.cpp',\n  'test/src/RTC/SCTP/packet/parameters/TestUnknownParameter.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestInvalidStreamIdentifierErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestMissingMandatoryParameterErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestStaleCookieErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestOutOfResourceErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestUnresolvableAddressErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedChunkTypeErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestInvalidMandatoryParameterErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedParametersErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestNoUserDataErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestCookieReceivedWhileShuttingDownErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestRestartOfAnAssociationWithNewAddressesErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestUserInitiatedAbortErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestProtocolViolationErrorCause.cpp',\n  'test/src/RTC/SCTP/packet/errorCauses/TestUnknownErrorCause.cpp',\n  'test/src/Utils/TestBits.cpp',\n  'test/src/Utils/TestByte.cpp',\n  'test/src/Utils/TestCrypto.cpp',\n  'test/src/Utils/TestIP.cpp',\n  'test/src/Utils/TestNumber.cpp',\n  'test/src/Utils/TestString.cpp',\n  'test/src/Utils/TestTime.cpp',\n  'test/src/Utils/TestUnwrappedSequenceNumber.cpp',\n]\n\nmediasoup_worker_test = executable(\n  'mediasoup-worker-test',\n  build_by_default: false,\n  install: true,\n  install_tag: 'mediasoup-worker-test',\n  dependencies: dependencies,\n  sources: common_sources + test_sources + mock_sources,\n  include_directories: include_directories(\n    'include',\n    'test/include',\n    'mocks/include',\n  ),\n  cpp_args: cpp_args,\n)\n\ntest(\n  'mediasoup-worker-test',\n  mediasoup_worker_test,\n  workdir: meson.project_source_root(),\n)\n\nmediasoup_worker_test_asan_address = executable(\n  'mediasoup-worker-test-asan-address',\n  build_by_default: false,\n  install: true,\n  install_tag: 'mediasoup-worker-test-asan-address',\n  dependencies: dependencies,\n  sources: common_sources + test_sources + mock_sources,\n  include_directories: include_directories(\n    'include',\n    'test/include',\n    'mocks/include',\n  ),\n  cpp_args: cpp_args + [\n    '-g',\n    '-O3',\n    '-fno-omit-frame-pointer',\n    '-fsanitize=address',\n  ],\n  link_args: [\n    '-fsanitize=address',\n  ],\n)\n\ntest(\n  'mediasoup-worker-test-asan-address',\n  mediasoup_worker_test_asan_address,\n  workdir: meson.project_source_root(),\n)\n\nmediasoup_worker_test_asan_undefined = executable(\n  'mediasoup-worker-test-asan-undefined',\n  build_by_default: false,\n  install: true,\n  install_tag: 'mediasoup-worker-test-asan-undefined',\n  dependencies: dependencies,\n  sources: common_sources + test_sources + mock_sources,\n  include_directories: include_directories(\n    'include',\n    'test/include',\n    'mocks/include',\n  ),\n  cpp_args: cpp_args + [\n    '-g',\n    '-O3',\n    '-fno-omit-frame-pointer',\n    '-fsanitize=undefined',\n  ],\n  link_args: [\n    '-fsanitize=undefined',\n  ],\n)\n\ntest(\n  'mediasoup-worker-test-asan-undefined',\n  mediasoup_worker_test_asan_undefined,\n  workdir: meson.project_source_root(),\n)\n\nfuzzer_sources = [\n  'fuzzer/src/fuzzer.cpp',\n  'fuzzer/src/FuzzerUtils.cpp',\n  'fuzzer/src/RTC/FuzzerDtlsTransport.cpp',\n  'fuzzer/src/RTC/FuzzerRateCalculator.cpp',\n  'fuzzer/src/RTC/FuzzerSeqManager.cpp',\n  'fuzzer/src/RTC/FuzzerTrendCalculator.cpp',\n  'fuzzer/src/RTC/ICE/FuzzerStunPacket.cpp',\n  'fuzzer/src/RTC/RTP/FuzzerPacket.cpp',\n  'fuzzer/src/RTC/RTP/FuzzerProbationGenerator.cpp',\n  'fuzzer/src/RTC/RTP/FuzzerRetransmissionBuffer.cpp',\n  'fuzzer/src/RTC/RTP/FuzzerRtpStreamSend.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerOpus.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerAV1.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerH264.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerVP8.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerVP9.cpp',\n  'fuzzer/src/RTC/RTP/Codecs/FuzzerDependencyDescriptor.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerBye.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPs.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsAfb.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsFir.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsLei.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsPli.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRemb.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRpsi.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsSli.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsTst.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsVbcm.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtp.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpEcn.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpNack.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpSrReq.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTllei.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTmmb.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTransport.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerPacket.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerReceiverReport.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerSdes.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerSenderReport.cpp',\n  'fuzzer/src/RTC/RTCP/FuzzerXr.cpp',\n  'fuzzer/src/RTC/SCTP/association/FuzzerStateCookie.cpp',\n  'fuzzer/src/RTC/SCTP/packet/FuzzerPacket.cpp',\n]\n\nexecutable(\n  'mediasoup-worker-fuzzer',\n  build_by_default: false,\n  install: true,\n  install_tag: 'mediasoup-worker-fuzzer',\n  dependencies: dependencies,\n  sources: common_sources + fuzzer_sources + mock_sources,\n  include_directories: include_directories(\n    'include',\n    'fuzzer/include',\n    'mocks/include',\n  ),\n  cpp_args: cpp_args + [\n    '-g',\n    '-O3',\n    '-fno-omit-frame-pointer',\n    '-fsanitize=address,undefined,fuzzer',\n  ],\n  link_args: [\n    '-fsanitize=address,undefined,fuzzer',\n  ],\n)\n"
  },
  {
    "path": "worker/meson_options.txt",
    "content": "option('ms_log_trace', type: 'boolean', value: false, description: 'Logs the current method/function if current log level is \"debug\"')\noption('ms_log_file_line', type: 'boolean', value: false, description: 'Logging macros print more verbose information, including current file and line')\noption('ms_rtc_logger_rtp', type: 'boolean', value: false, description: 'Prints a line with information for each processed RTP packet')\noption('ms_dump_rtp_payload_descriptor', type: 'boolean', value: false, description: 'RTC::RTP::Packet::Dump() method also dumps payload descriptor details')\noption('ms_dump_rtp_shared_packet_memory_usage', type: 'boolean', value: false, description: 'prints information about total memory allocated by RTC::RTP::SharedPacket instances')\noption('ms_disable_liburing', type: 'boolean', value: false, description: 'When set to true, disables liburing integration despite current host supports it')\noption('ms_build_tests', type: 'boolean', value: false, description: 'Must be enabled when building mediasoup worker tests')\noption('ms_build_fuzzer', type: 'boolean', value: false, description: 'Must be enabled when building mediasoup worker fuzzer')\n"
  },
  {
    "path": "worker/mocks/include/Channel/MockChannelMessageRegistrator.hpp",
    "content": "#ifndef MS_MOCKS_MOCK_CHANNEL_MESSAGE_REGISTRATOR_HPP\n#define MS_MOCKS_MOCK_CHANNEL_MESSAGE_REGISTRATOR_HPP\n\n#include \"Channel/ChannelMessageRegistratorInterface.hpp\"\n// TODO: We should have a ChannelSocketInterface class instead.\n#include \"Channel/ChannelSocket.hpp\"\n#include <string>\n#include <unordered_map>\n\nnamespace mocks\n{\n\tnamespace Channel\n\t{\n\t\tclass MockChannelMessageRegistrator : public ::Channel::ChannelMessageRegistratorInterface\n\t\t{\n\t\tpublic:\n\t\t\texplicit MockChannelMessageRegistrator();\n\n\t\t\t~MockChannelMessageRegistrator() override;\n\n\t\tpublic:\n\t\t\tflatbuffers::Offset<FBS::Worker::ChannelMessageHandlers> FillBuffer(\n\t\t\t  flatbuffers::FlatBufferBuilder& builder) override;\n\n\t\t\tvoid RegisterHandler(\n\t\t\t  const std::string& id,\n\t\t\t  ::Channel::ChannelSocket::RequestHandler* channelRequestHandler,\n\t\t\t  ::Channel::ChannelSocket::NotificationHandler* channelNotificationHandler) override;\n\n\t\t\tvoid UnregisterHandler(const std::string& id) override;\n\n\t\t\t::Channel::ChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) override;\n\n\t\t\t::Channel::ChannelSocket::NotificationHandler* GetChannelNotificationHandler(\n\t\t\t  const std::string& id) override;\n\n\t\tprivate:\n\t\t\tstd::unordered_map<std::string, ::Channel::ChannelSocket::RequestHandler*> mapChannelRequestHandlers;\n\t\t\tstd::unordered_map<std::string, ::Channel::ChannelSocket::NotificationHandler*>\n\t\t\t  mapChannelNotificationHandlers;\n\t\t};\n\t} // namespace Channel\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/MockShared.hpp",
    "content": "#ifndef MS_MOCKS_MOCK_SHARED_HPP\n#define MS_MOCKS_MOCK_SHARED_HPP\n\n#include \"SharedInterface.hpp\"\n#include \"mocks/include/Channel/MockChannelMessageRegistrator.hpp\"\n#include \"mocks/include/handles/MockBackoffTimerHandle.hpp\"\n#include \"Channel/ChannelNotifier.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include <map>\n#include <string>\n#include <string_view>\n\nnamespace mocks\n{\n\tclass MockShared : public SharedInterface\n\t{\n\tpublic:\n\t\texplicit MockShared(std::function<uint64_t()> getTimeMs);\n\n\t\t~MockShared() override = default;\n\n\tpublic:\n\t\t::Channel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() override\n\t\t{\n\t\t\treturn this->channelMessageRegistrator.get();\n\t\t}\n\n\t\t::Channel::ChannelNotifier* GetChannelNotifier() override\n\t\t{\n\t\t\treturn this->channelNotifier.get();\n\t\t}\n\n\t\tTimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) override;\n\n\t\tBackoffTimerHandleInterface* CreateBackoffTimer(\n\t\t  const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) override;\n\n\t\tuint64_t GetTimeMs() override\n\t\t{\n\t\t\treturn this->getTimeMs();\n\t\t}\n\n\t\tuint64_t GetTimeUs() override\n\t\t{\n\t\t\treturn GetTimeMs() * 1000;\n\t\t}\n\n\t\tuint64_t GetTimeNs() override\n\t\t{\n\t\t\treturn GetTimeMs() * 1000 * 1000;\n\t\t}\n\n\t\tint64_t GetTimeMsInt64() override\n\t\t{\n\t\t\treturn static_cast<int64_t>(GetTimeMs());\n\t\t}\n\n\t\tint64_t GetTimeUsInt64() override\n\t\t{\n\t\t\treturn static_cast<int64_t>(GetTimeUs());\n\t\t}\n\n\t\t// Methods for testing.\n\tpublic:\n\t\tMockBackoffTimerHandle* GetBackoffTimer(const std::string_view label) const\n\t\t{\n\t\t\tconst auto it = this->backoffTimers.find(std::string(label));\n\n\t\t\tif (it != this->backoffTimers.end())\n\t\t\t{\n\t\t\t\treturn it->second;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\t\t}\n\n\tprivate:\n\t\t// Given by argument.\n\t\tconst std::function<uint64_t()> getTimeMs;\n\t\t// Others.\n\t\tstd::unique_ptr<::Channel::ChannelSocket> channelSocket;\n\t\tstd::unique_ptr<mocks::Channel::MockChannelMessageRegistrator> channelMessageRegistrator;\n\t\tstd::unique_ptr<::Channel::ChannelNotifier> channelNotifier;\n\t\tstd::map<std::string /*label*/, MockBackoffTimerHandle* /*backoffTimer*/> backoffTimers;\n\t};\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/RTC/SCTP/association/MockAssociationListener.hpp",
    "content": "#ifndef MS_MOCKS_RTC_SCTP_MOCK_ASSOCIATION_LISTENER_HPP\n#define MS_MOCKS_RTC_SCTP_MOCK_ASSOCIATION_LISTENER_HPP\n\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include <deque>\n#include <map>\n#include <set>\n#include <string>\n#include <vector>\n\nnamespace mocks\n{\n\tnamespace RTC\n\t{\n\t\tnamespace SCTP\n\t\t{\n\t\t\tclass MockAssociationListener : public ::RTC::SCTP::AssociationListenerInterface\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tbool OnAssociationSendData(const uint8_t* data, size_t len) override\n\t\t\t\t{\n\t\t\t\t\tthis->sentPackets.emplace_back(data, data + len);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationConnecting() override\n\t\t\t\t{\n\t\t\t\t\tthis->connecting = true;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationConnected() override\n\t\t\t\t{\n\t\t\t\t\tthis->connecting = false;\n\t\t\t\t\tthis->connected  = true;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationFailed(\n\t\t\t\t  ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override\n\t\t\t\t{\n\t\t\t\t\tthis->connecting         = false;\n\t\t\t\t\tthis->connected          = false;\n\t\t\t\t\tthis->failed             = true;\n\t\t\t\t\tthis->failedErrorKind    = errorKind;\n\t\t\t\t\tthis->failedErrorMessage = errorMessage;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationClosed(\n\t\t\t\t  ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override\n\t\t\t\t{\n\t\t\t\t\tthis->connecting         = false;\n\t\t\t\t\tthis->connected          = false;\n\t\t\t\t\tthis->closed             = true;\n\t\t\t\t\tthis->closedErrorKind    = errorKind;\n\t\t\t\t\tthis->closedErrorMessage = errorMessage;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationRestarted() override\n\t\t\t\t{\n\t\t\t\t\tthis->restarted = true;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationError(\n\t\t\t\t  ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override\n\t\t\t\t{\n\t\t\t\t\tthis->connecting          = false;\n\t\t\t\t\tthis->connected           = false;\n\t\t\t\t\tthis->errored             = true;\n\t\t\t\t\tthis->erroredErrorKind    = errorKind;\n\t\t\t\t\tthis->erroredErrorMessage = errorMessage;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationMessageReceived(::RTC::SCTP::Message message) override\n\t\t\t\t{\n\t\t\t\t\tthis->receivedMessages.emplace_back(std::move(message));\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationStreamsResetPerformed(std::span<const uint16_t> /*outboundStreamIds*/) override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Do something here for tests.\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationStreamsResetFailed(\n\t\t\t\t  std::span<const uint16_t> /*outboundStreamIds*/, std::string_view /*errorMessage*/) override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Do something here for tests.\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationInboundStreamsReset(std::span<const uint16_t> /*inboundStreamIds*/) override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Do something here for tests.\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationStreamBufferedAmountLow(uint16_t streamId) override\n\t\t\t\t{\n\t\t\t\t\t++this->onStreamBufferedAmountLowCalls[streamId];\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationTotalBufferedAmountLow() override\n\t\t\t\t{\n\t\t\t\t\t++this->onTotalBufferedAmountLowCalls;\n\t\t\t\t}\n\n\t\t\t\tbool OnAssociationIsTransportReadyForSctp() override\n\t\t\t\t{\n\t\t\t\t\treturn this->transportReady;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) override\n\t\t\t\t{\n\t\t\t\t\tthis->onAssociationLifecycleMessageFullySentCalls.insert(lifecycleId);\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered) override\n\t\t\t\t{\n\t\t\t\t\tthis->onAssociationLifecycleMessageExpiredCalls[lifecycleId] = maybeDelivered;\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) override\n\t\t\t\t{\n\t\t\t\t\tthis->onAssociationLifecycleMessageDeliveredCalls.insert(lifecycleId);\n\t\t\t\t}\n\n\t\t\t\tvoid OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) override\n\t\t\t\t{\n\t\t\t\t\tthis->onAssociationLifecycleMessageEndCalls.insert(lifecycleId);\n\t\t\t\t}\n\n\t\t\t\t// Methods for testing.\n\t\t\tpublic:\n\t\t\t\tbool IsConnecting() const\n\t\t\t\t{\n\t\t\t\t\treturn this->connecting;\n\t\t\t\t}\n\n\t\t\t\tbool IsConnected() const\n\t\t\t\t{\n\t\t\t\t\treturn this->connected;\n\t\t\t\t}\n\n\t\t\t\tbool HasRestarted() const\n\t\t\t\t{\n\t\t\t\t\treturn this->restarted;\n\t\t\t\t}\n\n\t\t\t\tbool HasFailed() const\n\t\t\t\t{\n\t\t\t\t\treturn this->failed;\n\t\t\t\t}\n\n\t\t\t\t::RTC::SCTP::Types::ErrorKind GetFailedErrorKind() const\n\t\t\t\t{\n\t\t\t\t\treturn this->failedErrorKind;\n\t\t\t\t}\n\n\t\t\t\tconst std::string& GetFailedErrorMessage() const\n\t\t\t\t{\n\t\t\t\t\treturn this->failedErrorMessage;\n\t\t\t\t}\n\n\t\t\t\tbool IsClosed() const\n\t\t\t\t{\n\t\t\t\t\treturn this->closed;\n\t\t\t\t}\n\n\t\t\t\t::RTC::SCTP::Types::ErrorKind GetClosedErrorKind() const\n\t\t\t\t{\n\t\t\t\t\treturn this->closedErrorKind;\n\t\t\t\t}\n\n\t\t\t\tconst std::string& GetClosedErrorMessage() const\n\t\t\t\t{\n\t\t\t\t\treturn this->closedErrorMessage;\n\t\t\t\t}\n\n\t\t\t\tbool HasErrored() const\n\t\t\t\t{\n\t\t\t\t\treturn this->errored;\n\t\t\t\t}\n\n\t\t\t\t::RTC::SCTP::Types::ErrorKind GetErroredErrorKind() const\n\t\t\t\t{\n\t\t\t\t\treturn this->erroredErrorKind;\n\t\t\t\t}\n\n\t\t\t\tconst std::string& GetErroredErrorMessage() const\n\t\t\t\t{\n\t\t\t\t\treturn this->erroredErrorMessage;\n\t\t\t\t}\n\n\t\t\t\tbool HasOnStreamBufferedAmountLowBeenCalledWithStreamId(uint64_t streamId) const\n\t\t\t\t{\n\t\t\t\t\treturn this->onStreamBufferedAmountLowCalls.contains(streamId);\n\t\t\t\t}\n\n\t\t\t\tsize_t CountOnStreamBufferedAmountLowCallsWithStreamId(uint64_t streamId) const\n\t\t\t\t{\n\t\t\t\t\tif (!this->onStreamBufferedAmountLowCalls.contains(streamId))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this->onStreamBufferedAmountLowCalls.at(streamId);\n\t\t\t\t}\n\n\t\t\t\tsize_t CountOnTotalBufferedAmountLowCalls() const\n\t\t\t\t{\n\t\t\t\t\treturn this->onTotalBufferedAmountLowCalls;\n\t\t\t\t}\n\n\t\t\t\tbool HasSentPackets() const\n\t\t\t\t{\n\t\t\t\t\treturn !this->sentPackets.empty();\n\t\t\t\t}\n\n\t\t\t\tbool HasReceivedMessages() const\n\t\t\t\t{\n\t\t\t\t\treturn !this->receivedMessages.empty();\n\t\t\t\t}\n\n\t\t\t\tstd::vector<uint8_t> ConsumeFirstSentPacket()\n\t\t\t\t{\n\t\t\t\t\tif (this->sentPackets.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn {};\n\t\t\t\t\t}\n\n\t\t\t\t\tconst auto packet = std::move(this->sentPackets.front());\n\n\t\t\t\t\tthis->sentPackets.pop_front();\n\n\t\t\t\t\treturn packet;\n\t\t\t\t}\n\n\t\t\t\tstd::optional<::RTC::SCTP::Message> ConsumeFirstReceivedMessage()\n\t\t\t\t{\n\t\t\t\t\tif (this->receivedMessages.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn std::nullopt;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto message = std::move(this->receivedMessages.front());\n\n\t\t\t\t\tthis->receivedMessages.pop_front();\n\n\t\t\t\t\treturn message;\n\t\t\t\t}\n\n\t\t\t\tbool IsTransportReady() const\n\t\t\t\t{\n\t\t\t\t\treturn this->transportReady;\n\t\t\t\t}\n\n\t\t\t\tbool HasOnAssociationLifecycleMessageFullySentBeenCalledWithLifecycleId(uint64_t lifecycleId) const\n\t\t\t\t{\n\t\t\t\t\treturn this->onAssociationLifecycleMessageFullySentCalls.contains(lifecycleId);\n\t\t\t\t}\n\n\t\t\t\tbool HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(\n\t\t\t\t  uint64_t lifecycleId, bool maybeDelivered) const\n\t\t\t\t{\n\t\t\t\t\tif (!this->onAssociationLifecycleMessageExpiredCalls.contains(lifecycleId))\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this->onAssociationLifecycleMessageExpiredCalls.at(lifecycleId) == maybeDelivered;\n\t\t\t\t}\n\n\t\t\t\tbool HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(uint64_t lifecycleId) const\n\t\t\t\t{\n\t\t\t\t\treturn this->onAssociationLifecycleMessageDeliveredCalls.contains(lifecycleId);\n\t\t\t\t}\n\n\t\t\t\tbool HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(uint64_t lifecycleId) const\n\t\t\t\t{\n\t\t\t\t\treturn this->onAssociationLifecycleMessageEndCalls.contains(lifecycleId);\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// Observable state for tests.\n\t\t\t\tbool connecting{ false };\n\t\t\t\tbool connected{ false };\n\t\t\t\tbool restarted{ false };\n\t\t\t\tbool failed{ false };\n\t\t\t\t::RTC::SCTP::Types::ErrorKind failedErrorKind;\n\t\t\t\tstd::string failedErrorMessage;\n\t\t\t\tbool closed{ false };\n\t\t\t\t::RTC::SCTP::Types::ErrorKind closedErrorKind;\n\t\t\t\tstd::string closedErrorMessage;\n\t\t\t\tbool errored{ false };\n\t\t\t\t::RTC::SCTP::Types::ErrorKind erroredErrorKind;\n\t\t\t\tstd::string erroredErrorMessage;\n\t\t\t\tstd::map<uint16_t /*streamId*/, size_t /*count*/> onStreamBufferedAmountLowCalls;\n\t\t\t\tsize_t onTotalBufferedAmountLowCalls{ 0 };\n\t\t\t\tstd::deque<std::vector<uint8_t>> sentPackets;\n\t\t\t\tstd::deque<::RTC::SCTP::Message> receivedMessages;\n\t\t\t\tbool transportReady{ true };\n\t\t\t\tstd::set<uint64_t /*lifecycleId*/> onAssociationLifecycleMessageFullySentCalls;\n\t\t\t\tstd::map<uint64_t /*lifecycleId*/, bool /*maybeDelivered*/> onAssociationLifecycleMessageExpiredCalls;\n\t\t\t\tstd::set<uint64_t /*lifecycleId*/> onAssociationLifecycleMessageDeliveredCalls;\n\t\t\t\tstd::set<uint64_t /*lifecycleId*/> onAssociationLifecycleMessageEndCalls;\n\t\t\t};\n\t\t} // namespace SCTP\n\t} // namespace RTC\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp",
    "content": "#ifndef MS_MOCKS_RTC_SCTP_MOCK_TRANSMISSION_CONTROL_BLOCK_HPP\n#define MS_MOCKS_RTC_SCTP_MOCK_TRANSMISSION_CONTROL_BLOCK_HPP\n\n#include \"common.hpp\"\n#include \"mocks/include/mockTypes.hpp\"\n#include \"RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/public/AssociationListenerInterface.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include <queue>\n#include <stdexcept>\n#include <string>\n\nnamespace mocks\n{\n\tnamespace RTC\n\t{\n\t\tnamespace SCTP\n\t\t{\n\t\t\tclass MockTransmissionControlBlockContext\n\t\t\t  : public ::RTC::SCTP::TransmissionControlBlockContextInterface\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tusing GetCurrentRtoMsAction = std::function<uint64_t()>;\n\n\t\t\tpublic:\n\t\t\t\texplicit MockTransmissionControlBlockContext(\n\t\t\t\t  ::RTC::SCTP::AssociationListenerInterface& associationListener,\n\t\t\t\t  const ::RTC::SCTP::SctpOptions& sctpOptions)\n\t\t\t\t  : associationListener(associationListener), sctpOptions(sctpOptions)\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\tpublic:\n\t\t\t\tbool IsAssociationEstablished() const override\n\t\t\t\t{\n\t\t\t\t\treturn this->associationEstablished;\n\t\t\t\t}\n\n\t\t\t\tuint32_t GetLocalInitialTsn() const override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Implement this.\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tuint32_t GetRemoteInitialTsn() const override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Implement this.\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tvoid ObserveRttMs(uint64_t rttMs) override\n\t\t\t\t{\n\t\t\t\t\tthis->observeRttMsCallCount++;\n\t\t\t\t}\n\n\t\t\t\tuint64_t GetCurrentRtoMs() const override\n\t\t\t\t{\n\t\t\t\t\tif (!this->getCurrentRtoMsOnceActions.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tauto action = std::move(this->getCurrentRtoMsOnceActions.front());\n\n\t\t\t\t\t\tthis->getCurrentRtoMsOnceActions.pop();\n\n\t\t\t\t\t\treturn action();\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbool IncrementTxErrorCounter(std::string_view /*reason*/) override\n\t\t\t\t{\n\t\t\t\t\tthis->incrementTxErrorCounterCallCount++;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tvoid ClearTxErrorCounter() override\n\t\t\t\t{\n\t\t\t\t\tthis->incrementTxErrorCounterCallCount = 0;\n\t\t\t\t}\n\n\t\t\t\tbool HasTooManyTxErrors() const override\n\t\t\t\t{\n\t\t\t\t\t// TODO: Implement this.\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tstd::unique_ptr<::RTC::SCTP::Packet> CreatePacket() const override;\n\n\t\t\t\tbool SendPacket(::RTC::SCTP::Packet* packet) override;\n\n\t\t\t\t// Methods for testing.\n\t\t\tpublic:\n\t\t\t\tvoid SetAssociationEstablished(bool associationEstablished)\n\t\t\t\t{\n\t\t\t\t\tthis->associationEstablished = associationEstablished;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * @remarks\n\t\t\t\t * - Must be called before expecting calls to `ObserveRttMs()`.\n\t\t\t\t */\n\t\t\t\tMockTransmissionControlBlockContext& ExpectObserveRttMsCalledTimes(size_t times)\n\t\t\t\t{\n\t\t\t\t\tthis->observeRttMsCallCount         = 0;\n\t\t\t\t\tthis->expectedObserveRttMsCallCount = times;\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * @remarks\n\t\t\t\t * - Must be called before expecting calls to `IncrementTxErrorCounter()`.\n\t\t\t\t */\n\t\t\t\tMockTransmissionControlBlockContext& ExpectIncrementTxErrorCounterCalledTimes(size_t times)\n\t\t\t\t{\n\t\t\t\t\tthis->incrementTxErrorCounterCallCount         = 0;\n\t\t\t\t\tthis->expectedIncrementTxErrorCounterCallCount = times;\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\tMockTransmissionControlBlockContext& WillGetCurrentRtoMsOnce(GetCurrentRtoMsAction action)\n\t\t\t\t{\n\t\t\t\t\tthis->getCurrentRtoMsOnceActions.push(std::move(action));\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\tmocks::VerificationResult VerifyExpectations() const\n\t\t\t\t{\n\t\t\t\t\tif (\n\t\t\t\t\t  this->expectedObserveRttMsCallCount.has_value() &&\n\t\t\t\t\t  this->observeRttMsCallCount != this->expectedObserveRttMsCallCount.value())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn { .ok           = false,\n\t\t\t\t\t\t\t       .errorMessage = \"ObserveRttMs() call count mismatch [expected:\" +\n\t\t\t\t\t\t\t                       std::to_string(this->expectedObserveRttMsCallCount.value()) +\n\t\t\t\t\t\t\t                       \", got:\" + std::to_string(this->observeRttMsCallCount) + \"]\" };\n\t\t\t\t\t}\n\n\t\t\t\t\tif (\n\t\t\t\t\t  this->expectedIncrementTxErrorCounterCallCount.has_value() &&\n\t\t\t\t\t  this->incrementTxErrorCounterCallCount !=\n\t\t\t\t\t    this->expectedIncrementTxErrorCounterCallCount.value())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn { .ok = false,\n\t\t\t\t\t\t\t       .errorMessage =\n\t\t\t\t\t\t\t         \"IncrementTxErrorCounter() call count mismatch [expected:\" +\n\t\t\t\t\t\t\t         std::to_string(this->expectedIncrementTxErrorCounterCallCount.value()) +\n\t\t\t\t\t\t\t         \", got:\" + std::to_string(this->incrementTxErrorCounterCallCount) + \"]\" };\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { .ok = true, .errorMessage = \"\" };\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// Passed by argument.\n\t\t\t\t::RTC::SCTP::AssociationListenerInterface& associationListener;\n\t\t\t\tconst ::RTC::SCTP::SctpOptions sctpOptions;\n\n\t\t\t\t// ObserveRttMs().\n\t\t\t\tsize_t observeRttMsCallCount{ 0 };\n\t\t\t\tstd::optional<size_t> expectedObserveRttMsCallCount;\n\n\t\t\t\t// GetCurrentRtoMs().\n\t\t\t\tmutable std::queue<GetCurrentRtoMsAction> getCurrentRtoMsOnceActions;\n\n\t\t\t\t// IncrementTxErrorCounter().\n\t\t\t\tsize_t incrementTxErrorCounterCallCount{ 0 };\n\t\t\t\tstd::optional<size_t> expectedIncrementTxErrorCounterCallCount;\n\n\t\t\t\t// Others.\n\t\t\t\tbool associationEstablished{ false };\n\t\t\t};\n\t\t} // namespace SCTP\n\t} // namespace RTC\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/RTC/SCTP/tx/MockSendQueue.hpp",
    "content": "#ifndef MS_MOCKS_RTC_SCTP_MOCK_SEND_QUEUE_HPP\n#define MS_MOCKS_RTC_SCTP_MOCK_SEND_QUEUE_HPP\n\n#include \"common.hpp\"\n#include \"mocks/include/mockTypes.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include <queue>\n#include <stdexcept>\n#include <string>\n\nnamespace mocks\n{\n\tnamespace RTC\n\t{\n\t\tnamespace SCTP\n\t\t{\n\t\t\tclass MockSendQueue : public ::RTC::SCTP::SendQueueInterface\n\t\t\t{\n\t\t\tpublic:\n\t\t\t\tusing ProduceAction =\n\t\t\t\t  std::function<std::optional<DataToSend>(uint64_t /*nowMs*/, size_t /*maxLength*/)>;\n\n\t\t\t\tstruct DiscardExpectation\n\t\t\t\t{\n\t\t\t\t\t// If std::nullopt, don't check it.\n\t\t\t\t\tstd::optional<uint16_t> streamId;\n\t\t\t\t\t// If std::nullopt, don't check it.\n\t\t\t\t\tstd::optional<uint32_t> outgoingMessageId;\n\t\t\t\t\tbool returnValue{ false };\n\t\t\t\t};\n\n\t\t\tpublic:\n\t\t\t\tMockSendQueue() = default;\n\n\t\t\t\t~MockSendQueue() override = default;\n\n\t\t\tpublic:\n\t\t\t\tvoid EnableMessageInterleaving(bool /*enabled*/) override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tstd::optional<DataToSend> Produce(uint64_t nowMs, size_t maxLength) override\n\t\t\t\t{\n\t\t\t\t\tthis->produceCallCount++;\n\n\t\t\t\t\tif (!this->produceOnceActions.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tauto action = std::move(this->produceOnceActions.front());\n\n\t\t\t\t\t\tthis->produceOnceActions.pop();\n\n\t\t\t\t\t\treturn action(nowMs, maxLength);\n\t\t\t\t\t}\n\t\t\t\t\telse if (this->produceRepeatedlyAction)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn this->produceRepeatedlyAction(nowMs, maxLength);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\treturn std::nullopt;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbool Discard(uint16_t streamId, uint32_t outgoingMessageId) override\n\t\t\t\t{\n\t\t\t\t\tthis->discardCallCount++;\n\n\t\t\t\t\tif (this->discardExpectations.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow std::logic_error(\"Discard() called but no expectation was set\");\n\t\t\t\t\t}\n\n\t\t\t\t\tauto discardExpectation = this->discardExpectations.front();\n\n\t\t\t\t\tthis->discardExpectations.pop();\n\n\t\t\t\t\tif (discardExpectation.streamId.has_value() && discardExpectation.streamId.value() != streamId)\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow std::logic_error(\n\t\t\t\t\t\t  \"Discard() called with unexpected streamId [expected:\" +\n\t\t\t\t\t\t  std::to_string(discardExpectation.streamId.value()) +\n\t\t\t\t\t\t  \", got:\" + std::to_string(streamId) + \"]\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (\n\t\t\t\t\t  discardExpectation.outgoingMessageId.has_value() &&\n\t\t\t\t\t  discardExpectation.outgoingMessageId.value() != outgoingMessageId)\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow std::logic_error(\n\t\t\t\t\t\t  \"Discard() called with unexpected outgoingMessageId [expected:\" +\n\t\t\t\t\t\t  std::to_string(discardExpectation.outgoingMessageId.value()) +\n\t\t\t\t\t\t  \", got:\" + std::to_string(outgoingMessageId) + \"]\");\n\t\t\t\t\t}\n\n\t\t\t\t\treturn discardExpectation.returnValue;\n\t\t\t\t}\n\n\t\t\t\tvoid PrepareResetStream(uint16_t /*streamId*/) override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tbool HasStreamsReadyToBeReset() const override\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tstd::vector<uint16_t> GetStreamsReadyToBeReset() override\n\t\t\t\t{\n\t\t\t\t\treturn {};\n\t\t\t\t}\n\n\t\t\t\tvoid CommitResetStreams() override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tvoid RollbackResetStreams() override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tvoid Reset() override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\tsize_t GetStreamBufferedAmount(uint16_t /*streamId*/) const override\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tsize_t GetTotalBufferedAmount() const override\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tsize_t GetStreamBufferedAmountLowThreshold(uint16_t /*streamId*/) const override\n\t\t\t\t{\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tvoid SetStreamBufferedAmountLowThreshold(uint16_t /*streamId*/, size_t /*bytes*/) override\n\t\t\t\t{\n\t\t\t\t}\n\n\t\t\t\t// Methods for testing.\n\t\t\tpublic:\n\t\t\t\tMockSendQueue& WillProduceOnce(ProduceAction action)\n\t\t\t\t{\n\t\t\t\t\tthis->produceOnceActions.push(std::move(action));\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\tMockSendQueue& WillProduceRepeatedly(ProduceAction action)\n\t\t\t\t{\n\t\t\t\t\tthis->produceRepeatedlyAction = std::move(action);\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * @remarks\n\t\t\t\t * - Must be called before expecting calls to `Produce()`.\n\t\t\t\t */\n\t\t\t\tMockSendQueue& ExpectProduceCalledTimes(size_t times)\n\t\t\t\t{\n\t\t\t\t\tthis->produceCallCount         = 0;\n\t\t\t\t\tthis->expectedProduceCallCount = times;\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\tMockSendQueue& WillDiscardOnce(uint16_t streamId, uint32_t outgoingMessageId, bool returnValue)\n\t\t\t\t{\n\t\t\t\t\tthis->discardExpectations.push({ streamId, outgoingMessageId, returnValue });\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\t/**\n\t\t\t\t * @remarks\n\t\t\t\t * - Must be called before expecting calls to `Discard()`.\n\t\t\t\t */\n\t\t\t\tMockSendQueue& ExpectDiscardCalledTimes(size_t times)\n\t\t\t\t{\n\t\t\t\t\tthis->discardCallCount         = 0;\n\t\t\t\t\tthis->expectedDiscardCallCount = times;\n\n\t\t\t\t\treturn *this;\n\t\t\t\t}\n\n\t\t\t\tmocks::VerificationResult VerifyExpectations() const\n\t\t\t\t{\n\t\t\t\t\tif (\n\t\t\t\t\t  this->expectedProduceCallCount.has_value() &&\n\t\t\t\t\t  this->produceCallCount != this->expectedProduceCallCount.value())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn { .ok           = false,\n\t\t\t\t\t\t\t       .errorMessage = \"Produce() call count mismatch [expected:\" +\n\t\t\t\t\t\t\t                       std::to_string(this->expectedProduceCallCount.value()) +\n\t\t\t\t\t\t\t                       \", got:\" + std::to_string(this->produceCallCount) + \"]\" };\n\t\t\t\t\t}\n\n\t\t\t\t\tif (\n\t\t\t\t\t  this->expectedDiscardCallCount.has_value() &&\n\t\t\t\t\t  this->discardCallCount != this->expectedDiscardCallCount.value())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn { .ok           = false,\n\t\t\t\t\t\t\t       .errorMessage = \"Discard() call count mismatch [expected:\" +\n\t\t\t\t\t\t\t                       std::to_string(this->expectedDiscardCallCount.value()) +\n\t\t\t\t\t\t\t                       \", got:\" + std::to_string(this->discardCallCount) + \"]\" };\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!this->discardExpectations.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn { .ok           = false,\n\t\t\t\t\t\t\t       .errorMessage = \"Discard() has \" +\n\t\t\t\t\t\t\t                       std::to_string(this->discardExpectations.size()) +\n\t\t\t\t\t\t\t                       \" unconsumed expectation(s)\" };\n\t\t\t\t\t}\n\n\t\t\t\t\treturn { .ok = true, .errorMessage = \"\" };\n\t\t\t\t}\n\n\t\t\tprivate:\n\t\t\t\t// Produce().\n\t\t\t\tstd::queue<ProduceAction> produceOnceActions;\n\t\t\t\tProduceAction produceRepeatedlyAction;\n\t\t\t\tsize_t produceCallCount{ 0 };\n\t\t\t\tstd::optional<size_t> expectedProduceCallCount;\n\n\t\t\t\t// Discard().\n\t\t\t\tstd::queue<DiscardExpectation> discardExpectations;\n\t\t\t\tsize_t discardCallCount{ 0 };\n\t\t\t\tstd::optional<size_t> expectedDiscardCallCount;\n\t\t\t};\n\t\t} // namespace SCTP\n\t} // namespace RTC\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/handles/MockBackoffTimerHandle.hpp",
    "content": "#ifndef MS_MOCKS_MOCK_BACKOFF_TIMER_HANDLE_HPP\n#define MS_MOCKS_MOCK_BACKOFF_TIMER_HANDLE_HPP\n\n#include \"common.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <limits>\n\nnamespace mocks\n{\n\t// Forward declaration.\n\tclass MockShared;\n\n\tclass MockBackoffTimerHandle : public BackoffTimerHandleInterface\n\t{\n\t\t// Only MockShared class can invoke the constructor.\n\t\tfriend class mocks::MockShared;\n\n\tprivate:\n\t\texplicit MockBackoffTimerHandle(\n\t\t  BackoffTimerHandleOptions options,\n\t\t  std::function<uint64_t()> getTimeMs,\n\t\t  std::function<void()> onDelete);\n\n\tpublic:\n\t\tMockBackoffTimerHandle& operator=(const MockBackoffTimerHandle&) = delete;\n\n\t\tMockBackoffTimerHandle(const MockBackoffTimerHandle&) = delete;\n\n\t\t~MockBackoffTimerHandle() override\n\t\t{\n\t\t\tthis->onDelete();\n\t\t}\n\n\tpublic:\n\t\tvoid Dump(int indentation = 0) const;\n\n\t\tvoid Start() override\n\t\t{\n\t\t\tthis->running     = true;\n\t\t\tthis->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs();\n\t\t}\n\n\t\tvoid Stop() override\n\t\t{\n\t\t\tthis->running     = false;\n\t\t\tthis->expiresAtMs = std::numeric_limits<uint64_t>::max();\n\t\t}\n\n\t\t/**\n\t\t * @remarks\n\t\t * - This method must be defined in the .cpp since it's called by the\n\t\t *   constructor. Otherwise clang-tidy complains:\n\t\t *   \"warning: Call to virtual method 'BackoffTimerHandle::SetBaseTimeoutMs'\n\t\t *   during construction bypasses virtual dispatch\n\t\t *   [clang-analyzer-optin.cplusplus.VirtualCall]\"\n\t\t */\n\t\tvoid SetBaseTimeoutMs(uint64_t baseTimeoutMs) override;\n\n\t\tbool IsRunning() const override\n\t\t{\n\t\t\treturn this->running;\n\t\t}\n\n\t\tconst std::string GetLabel() const override\n\t\t{\n\t\t\treturn this->label;\n\t\t}\n\n\t\tstd::optional<size_t> GetMaxRestarts() const override\n\t\t{\n\t\t\treturn this->maxRestarts;\n\t\t}\n\n\t\tsize_t GetExpirationCount() const override\n\t\t{\n\t\t\treturn this->expirationCount;\n\t\t}\n\n\t\t// Methods for testing.\n\tpublic:\n\t\tuint64_t GetExpiresAtMs() const\n\t\t{\n\t\t\treturn this->expiresAtMs;\n\t\t}\n\n\t\tbool EvaluateHasExpired()\n\t\t{\n\t\t\tif (this->getTimeMs() >= this->expiresAtMs)\n\t\t\t{\n\t\t\t\tTriggerExpire();\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\tprivate:\n\t\tuint64_t ComputeNextTimeoutMs() const\n\t\t{\n\t\t\tauto expirationCount = this->expirationCount;\n\n\t\t\tswitch (this->backoffAlgorithm)\n\t\t\t{\n\t\t\t\tcase BackoffAlgorithm::FIXED:\n\t\t\t\t{\n\t\t\t\t\treturn this->baseTimeoutMs;\n\t\t\t\t}\n\n\t\t\t\tcase BackoffAlgorithm::EXPONENTIAL:\n\t\t\t\t{\n\t\t\t\t\tauto timeoutMs = this->baseTimeoutMs;\n\n\t\t\t\t\twhile (expirationCount > 0 && timeoutMs < BackoffTimerHandleInterface::MaxTimeoutMs)\n\t\t\t\t\t{\n\t\t\t\t\t\ttimeoutMs *= 2;\n\t\t\t\t\t\t--expirationCount;\n\n\t\t\t\t\t\tif (this->maxBackoffTimeoutMs.has_value() && timeoutMs > this->maxBackoffTimeoutMs.value())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn this->maxBackoffTimeoutMs.value();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn std::min<uint64_t>(timeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs);\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\tvoid TriggerExpire();\n\n\tprivate:\n\t\t// Passed by argument.\n\t\tBackoffTimerHandleInterface::Listener* listener{ nullptr };\n\t\tconst std::string label;\n\t\tuint64_t baseTimeoutMs;\n\t\tBackoffAlgorithm backoffAlgorithm;\n\t\tstd::optional<uint64_t> maxBackoffTimeoutMs;\n\t\tstd::optional<size_t> maxRestarts;\n\t\tstd::function<uint64_t()> getTimeMs;\n\t\tconst std::function<void()> onDelete;\n\t\t// Others.\n\t\tbool running{ false };\n\t\tsize_t expirationCount{ 0 };\n\t\tuint64_t expiresAtMs{ std::numeric_limits<uint64_t>::max() };\n\t};\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/handles/MockTimerHandle.hpp",
    "content": "#ifndef MS_MOCKS_MOCK_TIMER_HANDLE_HPP\n#define MS_MOCKS_MOCK_TIMER_HANDLE_HPP\n\n#include \"common.hpp\"\n#include \"handles/TimerHandleInterface.hpp\"\n\nnamespace mocks\n{\n\t// Forward declaration.\n\tclass MockShared;\n\n\tclass MockTimerHandle : public TimerHandleInterface\n\t{\n\t\t// Only MockShared class can invoke the constructor.\n\t\tfriend class mocks::MockShared;\n\n\tprivate:\n\t\texplicit MockTimerHandle() = default;\n\n\tpublic:\n\t\tMockTimerHandle& operator=(const MockTimerHandle&) = delete;\n\n\t\tMockTimerHandle(const MockTimerHandle&) = delete;\n\n\t\t~MockTimerHandle() override = default;\n\n\tpublic:\n\t\tvoid Start(uint64_t timeout, uint64_t repeat = 0) override\n\t\t{\n\t\t\tthis->running = true;\n\t\t\tthis->timeout = timeout;\n\t\t\tthis->repeat  = repeat;\n\t\t}\n\n\t\tvoid Stop() override\n\t\t{\n\t\t\tthis->running = false;\n\t\t}\n\n\t\tvoid Restart() override\n\t\t{\n\t\t\tthis->running = true;\n\t\t}\n\n\t\tvoid Restart(uint64_t timeout, uint64_t repeat = 0) override\n\t\t{\n\t\t\tthis->running = true;\n\t\t\tthis->timeout = timeout;\n\t\t\tthis->repeat  = repeat;\n\t\t}\n\n\t\tuint64_t GetTimeout() const override\n\t\t{\n\t\t\treturn this->timeout;\n\t\t}\n\n\t\tuint64_t GetRepeat() const override\n\t\t{\n\t\t\treturn this->repeat;\n\t\t}\n\n\t\tbool IsActive() const override\n\t\t{\n\t\t\treturn this->running;\n\t\t}\n\n\tprivate:\n\t\tbool running{ false };\n\t\tuint64_t timeout{ 0u };\n\t\tuint64_t repeat{ 0u };\n\t};\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/include/mockTypes.hpp",
    "content": "#ifndef MS_MOCKS_TYPES_HPP\n#define MS_MOCKS_TYPES_HPP\n\n#include <string>\n\nnamespace mocks\n{\n\tstruct VerificationResult\n\t{\n\t\tbool ok;\n\t\tstd::string errorMessage;\n\t};\n} // namespace mocks\n\n#endif\n"
  },
  {
    "path": "worker/mocks/src/Channel/MockChannelMessageRegistrator.cpp",
    "content": "#define MS_CLASS \"ChannelMessageRegistrator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"mocks/include/Channel/MockChannelMessageRegistrator.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <vector>\n\nnamespace mocks\n{\n\tnamespace Channel\n\t{\n\t\tMockChannelMessageRegistrator::MockChannelMessageRegistrator()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tMockChannelMessageRegistrator::~MockChannelMessageRegistrator()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->mapChannelRequestHandlers.clear();\n\t\t\tthis->mapChannelNotificationHandlers.clear();\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::Worker::ChannelMessageHandlers> MockChannelMessageRegistrator::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder)\n\t\t{\n\t\t\t// Add channelRequestHandlerIds.\n\t\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> channelRequestHandlerIds;\n\n\t\t\tfor (const auto& kv : this->mapChannelRequestHandlers)\n\t\t\t{\n\t\t\t\tconst auto& handlerId = kv.first;\n\n\t\t\t\tchannelRequestHandlerIds.push_back(builder.CreateString(handlerId));\n\t\t\t}\n\n\t\t\t// Add channelNotificationHandlerIds.\n\t\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> channelNotificationHandlerIds;\n\n\t\t\tfor (const auto& kv : this->mapChannelNotificationHandlers)\n\t\t\t{\n\t\t\t\tconst auto& handlerId = kv.first;\n\n\t\t\t\tchannelNotificationHandlerIds.push_back(builder.CreateString(handlerId));\n\t\t\t}\n\n\t\t\treturn FBS::Worker::CreateChannelMessageHandlersDirect(\n\t\t\t  builder, &channelRequestHandlerIds, &channelNotificationHandlerIds);\n\t\t}\n\n\t\tvoid MockChannelMessageRegistrator::RegisterHandler(\n\t\t  const std::string& id,\n\t\t  ::Channel::ChannelSocket::RequestHandler* channelRequestHandler,\n\t\t  ::Channel::ChannelSocket::NotificationHandler* channelNotificationHandler)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (channelRequestHandler)\n\t\t\t{\n\t\t\t\tif (this->mapChannelRequestHandlers.contains(id))\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"Channel request handler with ID %s already exists\", id.c_str());\n\t\t\t\t}\n\n\t\t\t\tthis->mapChannelRequestHandlers[id] = channelRequestHandler;\n\t\t\t}\n\n\t\t\tif (channelNotificationHandler)\n\t\t\t{\n\t\t\t\tif (this->mapChannelNotificationHandlers.contains(id))\n\t\t\t\t{\n\t\t\t\t\tif (channelRequestHandler)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->mapChannelRequestHandlers.erase(id);\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_THROW_ERROR(\"Channel notification handler with ID %s already exists\", id.c_str());\n\t\t\t\t}\n\n\t\t\t\tthis->mapChannelNotificationHandlers[id] = channelNotificationHandler;\n\t\t\t}\n\t\t}\n\n\t\tvoid MockChannelMessageRegistrator::UnregisterHandler(const std::string& id)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->mapChannelRequestHandlers.erase(id);\n\t\t\tthis->mapChannelNotificationHandlers.erase(id);\n\t\t}\n\n\t\t::Channel::ChannelSocket::RequestHandler* MockChannelMessageRegistrator::GetChannelRequestHandler(\n\t\t  const std::string& id)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto it = this->mapChannelRequestHandlers.find(id);\n\n\t\t\tif (it != this->mapChannelRequestHandlers.end())\n\t\t\t{\n\t\t\t\treturn it->second;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\t\t}\n\n\t\t::Channel::ChannelSocket::NotificationHandler* MockChannelMessageRegistrator::GetChannelNotificationHandler(\n\t\t  const std::string& id)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto it = this->mapChannelNotificationHandlers.find(id);\n\n\t\t\tif (it != this->mapChannelNotificationHandlers.end())\n\t\t\t{\n\t\t\t\treturn it->second;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\t\t}\n\t} // namespace Channel\n} // namespace mocks\n"
  },
  {
    "path": "worker/mocks/src/MockShared.cpp",
    "content": "#define MS_CLASS \"mocks::MockShared\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"mocks/include/MockShared.hpp\"\n#include \"Logger.hpp\"\n#include \"mocks/include/handles/MockTimerHandle.hpp\"\n\nnamespace mocks\n{\n\tMockShared::MockShared(std::function<uint64_t()> getTimeMs)\n\t  : getTimeMs(std::move(getTimeMs)),\n\t    channelSocket(new ::Channel::ChannelSocket()),\n\t    channelMessageRegistrator(new mocks::Channel::MockChannelMessageRegistrator()),\n\t    channelNotifier(new ::Channel::ChannelNotifier(this->channelSocket.get()))\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tTimerHandleInterface* MockShared::CreateTimer(TimerHandleInterface::Listener* /*listener*/)\n\t{\n\t\tMS_TRACE();\n\n\t\treturn new MockTimerHandle();\n\t}\n\n\tBackoffTimerHandleInterface* MockShared::CreateBackoffTimer(\n\t  const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto& label = options.label;\n\n\t\tauto* backoffTimer = new MockBackoffTimerHandle(\n\t\t  options,\n\t\t  /*getTimeMs*/ this->getTimeMs,\n\t\t  /*onDelete*/\n\t\t  [this, label]()\n\t\t  {\n\t\t\t  this->backoffTimers.erase(label);\n\t\t  });\n\n\t\tthis->backoffTimers[options.label] = backoffTimer;\n\n\t\treturn backoffTimer;\n\t}\n} // namespace mocks\n"
  },
  {
    "path": "worker/mocks/src/RTC/SCTP/association/MockTransmissionControlBlockContext.cpp",
    "content": "#define MS_CLASS \"mocks::RTC::SCTP::MockTransmissionControlBlockContext\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp\"\n#include \"Logger.hpp\"\n\nalignas(4) static thread_local uint8_t FactoryBuffer[65536];\n\nnamespace mocks\n{\n\tnamespace RTC\n\t{\n\t\tnamespace SCTP\n\t\t{\n\t\t\tstd::unique_ptr<::RTC::SCTP::Packet> MockTransmissionControlBlockContext::CreatePacket() const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto packet = std::unique_ptr<::RTC::SCTP::Packet>{ ::RTC::SCTP::Packet::Factory(\n\t\t\t\t\tFactoryBuffer, this->sctpOptions.mtu) };\n\n\t\t\t\tpacket->SetSourcePort(this->sctpOptions.sourcePort);\n\t\t\t\tpacket->SetDestinationPort(this->sctpOptions.destinationPort);\n\n\t\t\t\treturn packet;\n\t\t\t}\n\n\t\t\tbool MockTransmissionControlBlockContext::SendPacket(::RTC::SCTP::Packet* packet)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tconst bool sent =\n\t\t\t\t  this->associationListener.OnAssociationSendData(packet->GetBuffer(), packet->GetLength());\n\n\t\t\t\treturn sent;\n\t\t\t}\n\t\t} // namespace SCTP\n\t} // namespace RTC\n} // namespace mocks\n"
  },
  {
    "path": "worker/mocks/src/handles/MockBackoffTimerHandle.cpp",
    "content": "#define MS_CLASS \"mocks::MockBackoffTimerHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"mocks/include/handles/MockBackoffTimerHandle.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <string>\n\nnamespace mocks\n{\n\tMockBackoffTimerHandle::MockBackoffTimerHandle(\n\t  BackoffTimerHandleOptions options,\n\t  std::function<uint64_t()> getTimeMs,\n\t  std::function<void()> onDelete)\n\t  : listener(options.listener),\n\t    label(std::move(options.label)),\n\t    baseTimeoutMs(options.baseTimeoutMs),\n\t    backoffAlgorithm(options.backoffAlgorithm),\n\t    maxBackoffTimeoutMs(options.maxBackoffTimeoutMs),\n\t    maxRestarts(options.maxRestarts),\n\t    getTimeMs(std::move(getTimeMs)),\n\t    onDelete(std::move(onDelete))\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->listener)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"options.listener must be given\");\n\t\t}\n\n\t\tif (this->label.empty())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"options.label must be given\");\n\t\t}\n\n\t\tif (this->baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs)\n\t\t{\n\t\t\tMS_THROW_ERROR(\n\t\t\t  \"[%s] base timeout (%\" PRIu64 \" ms) cannot be greater than %\" PRIu64 \" ms\",\n\t\t\t  this->label.c_str(),\n\t\t\t  this->baseTimeoutMs,\n\t\t\t  BackoffTimerHandleInterface::MaxTimeoutMs);\n\t\t}\n\t}\n\n\tvoid MockBackoffTimerHandle::Dump(int indentation) const\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint64_t nowMs = this->getTimeMs();\n\n\t\tMS_DUMP_CLEAN(indentation, \"<mocks::MockBackoffTimerHandle>\");\n\n\t\tMS_DUMP_CLEAN(indentation, \"  label: %s\", this->label.c_str());\n\t\tMS_DUMP_CLEAN(indentation, \"  base timeout (ms): %\" PRIu64, this->baseTimeoutMs);\n\t\tMS_DUMP_CLEAN(\n\t\t  indentation,\n\t\t  \"  max backoff timeout (ms): %s\",\n\t\t  this->maxBackoffTimeoutMs.has_value()\n\t\t    ? std::to_string(this->maxBackoffTimeoutMs.value()).c_str()\n\t\t    : \"(unset)\");\n\t\tMS_DUMP_CLEAN(\n\t\t  indentation,\n\t\t  \"  max restarts (ms): %s\",\n\t\t  this->maxRestarts.has_value() ? std::to_string(this->maxRestarts.value()).c_str() : \"(unset)\");\n\t\tMS_DUMP_CLEAN(indentation, \"  running: %s\", this->running ? \"yes\" : \"no\");\n\t\tMS_DUMP_CLEAN(indentation, \"  expiration count: %zu\", this->expirationCount);\n\t\tMS_DUMP_CLEAN(indentation, \"  now (ms): %\" PRIu64, nowMs);\n\t\tMS_DUMP_CLEAN(indentation, \"  expires at (ms): %\" PRIu64, this->expiresAtMs);\n\t\tMS_DUMP_CLEAN(indentation, \"  expires in (ms): %\" PRIu64, this->expiresAtMs - nowMs);\n\n\t\tMS_DUMP_CLEAN(indentation, \"</mocks::MockBackoffTimerHandle>\");\n\t}\n\n\tvoid MockBackoffTimerHandle::SetBaseTimeoutMs(uint64_t baseTimeoutMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs)\n\t\t{\n\t\t\tMS_THROW_ERROR(\n\t\t\t  \"[%s] base timeout (%\" PRIu64 \" ms) cannot be greater than %\" PRIu64 \" ms\",\n\t\t\t  this->label.c_str(),\n\t\t\t  baseTimeoutMs,\n\t\t\t  BackoffTimerHandleInterface::MaxTimeoutMs);\n\t\t}\n\n\t\tthis->baseTimeoutMs = baseTimeoutMs;\n\t}\n\n\tvoid MockBackoffTimerHandle::TriggerExpire()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->expirationCount++;\n\n\t\t// Compute whether the BackoffTimer should still be running after this timeout\n\t\t// expiration so the parent can check IsRunning() within the `OnBackoffTimer()`\n\t\t// callback.\n\t\tthis->running =\n\t\t  !this->maxRestarts.has_value() || this->expirationCount <= this->maxRestarts.value();\n\n\t\tuint64_t baseTimeoutMs{ this->baseTimeoutMs };\n\t\tbool stop{ false };\n\n\t\t// Call the listener by passing base timeout as reference so the parent has\n\t\t// a chance to change it and affect the next timeout.\n\t\tthis->listener->OnBackoffTimer(this, baseTimeoutMs, stop);\n\n\t\t// If the parent has set `stop` to true it means that it has deleted the\n\t\t// instance, so stop here.\n\t\tif (stop)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// NOTE: This may throw.\n\t\tSetBaseTimeoutMs(baseTimeoutMs);\n\n\t\t// The caller may have called Stop() within the callback so we must check\n\t\t// the `running` flag.\n\t\tif (this->running)\n\t\t{\n\t\t\tthis->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs();\n\t\t}\n\t}\n} // namespace mocks\n"
  },
  {
    "path": "worker/scripts/.npmrc",
    "content": "package-lock=true\n"
  },
  {
    "path": "worker/scripts/clang-scripts.mjs",
    "content": "import * as process from 'node:process';\nimport * as os from 'node:os';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { globSync } from 'glob';\n\nconst PYTHON = getPython();\nconst ROOT_DIR = getRootDir();\nconst BUILD_DIR = getBuildDir();\nconst NUM_CORES = getNumCores();\nconst CLANG_FORMAT_VERSION = 22;\nconst CLANG_TIDY_VERSION = 21;\n\nconst CLANG_FORMAT_PATHS = [\n\t'../src/**/*.cpp',\n\t'../include/**/*.hpp',\n\t'../test/src/**/*.cpp',\n\t'../test/include/**/**.hpp',\n\t'../fuzzer/src/**/*.cpp',\n\t'../fuzzer/include/**/*.hpp',\n\t'../mocks/src/**/*.cpp',\n\t'../mocks/include/**/*.hpp',\n];\n\nconst CLANG_TIDY_PATHS = [\n\t'../src/**/*.cpp',\n\t'../test/src/**/*.cpp',\n\t'../fuzzer/src/**/*.cpp',\n\t'../mocks/src/**/*.cpp',\n];\n\nconst task = process.argv.slice(2).join(' ');\n\nvoid run();\n\nasync function run() {\n\tswitch (task) {\n\t\tcase 'lint': {\n\t\t\tlint();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'format': {\n\t\t\tformat();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'tidy': {\n\t\t\ttidy({ fix: false });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'tidy:fix': {\n\t\t\ttidy({ fix: true });\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'normalize-compile-commands': {\n\t\t\tnormalizeCompileCommands();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault: {\n\t\t\tlogError('unknown task');\n\n\t\t\texitWithError();\n\t\t}\n\t}\n}\n\nfunction lint() {\n\tlogInfo('lint()');\n\n\tconst clangFormat = getClangToolBinary({\n\t\tclangToolName: 'clang-format',\n\t\tversion: CLANG_FORMAT_VERSION,\n\t\tcheckRequireVersion: true,\n\t});\n\n\tconst clangFormatFiles = globSync(CLANG_FORMAT_PATHS).join(' ');\n\n\texecuteCmd(`\"${clangFormat}\" --Werror --dry-run ${clangFormatFiles}`);\n}\n\nfunction format() {\n\tlogInfo('format()');\n\n\tconst clangFormat = getClangToolBinary({\n\t\tclangToolName: 'clang-format',\n\t\tversion: CLANG_FORMAT_VERSION,\n\t\tcheckRequireVersion: true,\n\t});\n\n\tconst clangFormatFiles = globSync(CLANG_FORMAT_PATHS).join(' ');\n\n\texecuteCmd(`\"${clangFormat}\" --Werror -i ${clangFormatFiles}`);\n}\n\nfunction tidy({ fix }) {\n\tlogInfo(`tidy() [fix:${fix}]`);\n\n\tconst clangTidy = getClangToolBinary({\n\t\tclangToolName: 'clang-tidy',\n\t\tversion: CLANG_TIDY_VERSION,\n\t\tcheckRequireVersion: true,\n\t});\n\n\tconst runClangTidy = getClangToolBinary({\n\t\tclangToolName: 'run-clang-tidy',\n\t\tversion: CLANG_TIDY_VERSION,\n\t\t// Don't check version because this command does not provide --version\n\t\t// option.\n\t\tcheckRequireVersion: false,\n\t});\n\n\tconst clangApplyReplacements = getClangToolBinary({\n\t\tclangToolName: 'clang-apply-replacements',\n\t\tversion: CLANG_TIDY_VERSION,\n\t\tcheckRequireVersion: true,\n\t});\n\n\tconst tidyChecksArg = process.env.MEDIASOUP_TIDY_CHECKS\n\t\t? `-checks=-*,${process.env.MEDIASOUP_TIDY_CHECKS}`\n\t\t: '';\n\n\tconst runClangTidyFilesArgs = process.env.MEDIASOUP_TIDY_FILES\n\t\t? globSync(\n\t\t\t\tprocess.env.MEDIASOUP_TIDY_FILES.split(/\\s/)\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.map(filePath => `../${filePath}`)\n\t\t\t).join(' ')\n\t\t: globSync(CLANG_TIDY_PATHS).join(' ');\n\n\tif (!runClangTidyFilesArgs) {\n\t\tlogError('tidy() | no files found');\n\n\t\texitWithError();\n\t}\n\n\tconst fixArg = fix ? '-fix' : '';\n\n\t// Check .clang-tidy config file and fail if some option is invalid/unknown.\n\texecuteCmd(`\"${clangTidy}\" --verify-config`);\n\n\texecuteCmd(\n\t\t`\"${PYTHON}\" \"${runClangTidy}\" -clang-tidy-binary=\"${clangTidy}\" -clang-apply-replacements-binary=\"${clangApplyReplacements}\" -p=\"${BUILD_DIR}\" -j=${NUM_CORES} -quiet ${fixArg} -format ${tidyChecksArg} ${runClangTidyFilesArgs}`\n\t);\n}\n\nfunction normalizeCompileCommands() {\n\tlogInfo('normalizeCompileCommands()');\n\n\tconst compileCommandsFile = `${BUILD_DIR}/compile_commands.json`;\n\n\ttry {\n\t\tconst commands = JSON.parse(fs.readFileSync(compileCommandsFile, 'utf8'));\n\n\t\tfor (const entry of commands) {\n\t\t\tif (entry.file && entry.directory) {\n\t\t\t\t// Resolve to absolute path first.\n\t\t\t\tconst absolutePath = path.resolve(entry.directory, entry.file);\n\n\t\t\t\t// Convert to relative path from repo root.\n\t\t\t\tentry.file = path.relative(ROOT_DIR, absolutePath);\n\t\t\t}\n\t\t}\n\n\t\tfs.writeFileSync(compileCommandsFile, JSON.stringify(commands, null, 2));\n\t} catch (error) {\n\t\tlogError(\n\t\t\t`normalizeCompileCommands() | failed to clean up compile_commands.json: ${error}`\n\t\t);\n\n\t\texitWithError();\n\t}\n}\n\nfunction getClangToolBinary({ clangToolName, version, checkRequireVersion }) {\n\tlogInfo(\n\t\t`getClangToolBinary() [clangToolName:${clangToolName}, version:${version}]`\n\t);\n\n\tlet clangToolBinary;\n\n\t// Try `clangTool-version` first, otherwise try `clangTool`.\n\ttry {\n\t\tclangToolBinary = `${clangToolName}-${version}`;\n\n\t\tlogInfo(`getClangToolBinary() | trying ${clangToolBinary}...`);\n\n\t\texecSync(`${clangToolBinary} --help`, {\n\t\t\tstdio: ['ignore', 'ignore', 'ignore'],\n\t\t});\n\t} catch (error) {\n\t\tclangToolBinary = clangToolName;\n\n\t\tlogInfo(`getClangToolBinary() | trying ${clangToolBinary}...`);\n\n\t\ttry {\n\t\t\texecSync(`${clangToolBinary} --help`, {\n\t\t\t\tstdio: ['ignore', 'ignore', 'ignore'],\n\t\t\t});\n\t\t} catch {\n\t\t\tlogError(`getClangToolBinary() | ${clangToolName} binary not found`);\n\n\t\t\texitWithError();\n\t\t}\n\t}\n\n\tlogInfo(`getClangToolBinary() | using ${clangToolBinary}`);\n\n\tif (checkRequireVersion) {\n\t\ttry {\n\t\t\tcheckClangToolVersion(clangToolBinary, version);\n\t\t} catch (error) {\n\t\t\tlogError(`getClangToolBinary() | failed: ${error.message}`);\n\n\t\t\texitWithError();\n\t\t}\n\t}\n\n\tconst clangToolBinaryAbsolutePath = execSync(`which ${clangToolBinary}`, {\n\t\tencoding: 'utf8',\n\t}).trim();\n\n\treturn clangToolBinaryAbsolutePath;\n}\n\nfunction checkClangToolVersion(clangToolBinary, requiredVersion) {\n\ttry {\n\t\tlet version;\n\n\t\t// Run the command and capture the output.\n\t\tconst output = execSync(`${clangToolBinary} --version`, {\n\t\t\tencoding: 'utf-8',\n\t\t});\n\n\t\t// Extract the mayor version number from the output.\n\t\tconst match = output.match(/version (\\d+)/);\n\n\t\tif (match && match[1]) {\n\t\t\tversion = parseInt(match[1], 10);\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`checkClangToolVersion() | unable to parse output of '${clangToolBinary} --version': ${output}`\n\t\t\t);\n\t\t}\n\n\t\tif (version === requiredVersion) {\n\t\t\tlogInfo(\n\t\t\t\t`checkClangToolVersion() | ${clangToolBinary} version is the required one (${requiredVersion})`\n\t\t\t);\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`checkClangToolVersion() | ${clangToolBinary} version (${version}) is not the required one (${requiredVersion})`\n\t\t\t);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`checkClangToolVersion() | failed to check ${clangToolBinary} version: ${error.message}`,\n\t\t\t{ cause: error }\n\t\t);\n\t}\n}\n\nfunction getPython() {\n\tlet python = process.env.PYTHON;\n\n\tif (!python) {\n\t\ttry {\n\t\t\texecSync('python3 --version', { stdio: ['ignore', 'ignore', 'ignore'] });\n\n\t\t\tpython = 'python3';\n\t\t} catch (error) {\n\t\t\tpython = 'python';\n\t\t}\n\t}\n\n\treturn python;\n}\n\nfunction getRootDir() {\n\treturn path.resolve(path.join('../../'));\n}\n\nfunction getBuildDir() {\n\tconst workerDir = path.join(ROOT_DIR, 'worker');\n\tconst workerOutDir = process.env.MEDIASOUP_OUT_DIR ?? `${workerDir}/out`;\n\tconst mediasoupBuildtype = process.env.MEDIASOUP_BUILDTYPE ?? 'Release';\n\tconst workerInstallDir =\n\t\tprocess.env.MEDIASOUP_INSTALL_DIR ??\n\t\t`${workerOutDir}/${mediasoupBuildtype}`;\n\tconst buildDir = process.envBUILD_DIR ?? `${workerInstallDir}/build`;\n\n\treturn buildDir;\n}\n\nfunction getNumCores() {\n\treturn Object.keys(os.cpus()).length;\n}\n\nfunction executeCmd(command) {\n\tlogInfo(`executeCmd(): ${command}`);\n\n\ttry {\n\t\texecSync(command, { stdio: ['ignore', process.stdout, process.stderr] });\n\t} catch (error) {\n\t\tlogError('executeCmd() failed');\n\n\t\texitWithError();\n\t}\n}\n\nfunction logInfo(message) {\n\t// eslint-disable-next-line no-console\n\tconsole.log(`clang-scripts.mjs \\x1b[36m[INFO] [${task}]\\x1b[0m`, message);\n}\n\n// eslint-disable-next-line no-unused-vars\nfunction logWarn(message) {\n\t// eslint-disable-next-line no-console\n\tconsole.warn(`clang-scripts.mjs \\x1b[33m[WARN] [${task}]\\x1b[0m`, message);\n}\n\nfunction logError(message) {\n\t// eslint-disable-next-line no-console\n\tconsole.error(`clang-scripts.mjs \\x1b[31m[ERROR] [${task}]\\x1b[0m`, message);\n}\n\nfunction exitWithError() {\n\tprocess.exit(1);\n}\n"
  },
  {
    "path": "worker/scripts/get-dep.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\n\nWORKER_PWD=${PWD}\nDEP=$1\n\ncurrent_dir_name=${WORKER_PWD##*/}\nif [ \"${current_dir_name}\" != \"worker\" ] ; then\n\techo \">>> [ERROR] $(basename $0) must be called from mediasoup/worker directory\" >&2\n\texit 1\nfi\n\nfunction get_dep()\n{\n\tGIT_REPO=\"$1\"\n\tGIT_TAG=\"$2\"\n\tDEST=\"$3\"\n\n\techo \">>> [INFO] getting dep '${DEP}'...\"\n\n\tif [ -d \"${DEST}\" ] ; then\n\t\techo \">>> [INFO] deleting ${DEST}...\"\n\t\tgit rm -rf --ignore-unmatch ${DEST} > /dev/null\n\t\trm -rf ${DEST}\n\tfi\n\n\techo \">>> [INFO] cloning ${GIT_REPO}...\"\n\tgit clone ${GIT_REPO} ${DEST}\n\n\tcd ${DEST}\n\n\techo \">>> [INFO] setting '${GIT_TAG}' git tag...\"\n\tgit checkout --quiet ${GIT_TAG}\n\tset -e\n\n\techo \">>> [INFO] adding dep source code to the repository...\"\n\trm -rf .git\n\tgit add .\n\n\techo \">>> [INFO] got dep '${DEP}'\"\n\n\tcd ${WORKER_PWD}\n}\n\nfunction get_fuzzer_corpora()\n{\n\tGIT_REPO=\"https://github.com/RTC-Cartel/webrtc-fuzzer-corpora.git\"\n\tGIT_TAG=\"master\"\n\tDEST=\"deps/webrtc-fuzzer-corpora\"\n\n\tget_dep \"${GIT_REPO}\" \"${GIT_TAG}\" \"${DEST}\"\n}\n\ncase \"${DEP}\" in\n\t'-h')\n\t\techo \"Usage:\"\n\t\techo \"  ./scripts/$(basename $0) [fuzzer-corpora]\"\n\t\techo\n\t\t;;\n\tfuzzer-corpora)\n\t\tget_fuzzer_corpora\n\t\t;;\n\t*)\n\t\techo \">>> [ERROR] unknown dep '${DEP}'\" >&2\n\t\texit 1\nesac\n\nif [ $? -eq 0 ] ; then\n\techo \">>> [INFO] done\"\nelse\n\techo \">>> [ERROR] failed\" >&2\n\texit 1\nfi\n"
  },
  {
    "path": "worker/scripts/package.json",
    "content": "{\n\t\"name\": \"mediasoup-worker-dev-tools\",\n\t\"version\": \"0.0.1\",\n\t\"description\": \"mediasoup worker dev tools\",\n\t\"scripts\": {\n\t\t\"lint\": \"node clang-scripts.mjs lint\",\n\t\t\"format\": \"node clang-scripts.mjs format\",\n\t\t\"tidy\": \"node clang-scripts.mjs tidy\",\n\t\t\"tidy:fix\": \"node clang-scripts.mjs tidy:fix\",\n\t\t\"normalize-compile-commands\": \"node clang-scripts.mjs normalize-compile-commands\"\n\t},\n\t\"dependencies\": {\n\t\t\"glob\": \"^13.0.6\"\n\t}\n}\n"
  },
  {
    "path": "worker/scripts/run-fuzzer.sh",
    "content": "#!/usr/bin/env bash\n\nWORKER_PWD=${PWD}\nDURATION_SEC=$1\n\ncurrent_dir_name=${WORKER_PWD##*/}\nif [ \"${current_dir_name}\" != \"worker\" ] ; then\n\techo \"run-fuzzer.sh [ERROR] $(basename $0) must be called from mediasoup/worker directory\" >&2\n\texit 1\nfi\n\nif [ \"$#\" -eq 0 ] ; then\n\techo \"run-fuzzer.sh [ERROR] duration (in seconds) must be given as argument\" >&2\n\texit 1\nfi\n\ninvoke fuzzer-run-all &\n\nMEDIASOUP_WORKER_FUZZER_PID=$!\n\ni=${DURATION_SEC}\n\nuntil [ ${i} -eq 0 ]\ndo\n\techo \"run-fuzzer.sh [INFO] ${i} seconds left\"\n\tif ! kill -0 ${MEDIASOUP_WORKER_FUZZER_PID} &> /dev/null ; then\n\t\techo \"run-fuzzer.sh [ERROR] mediasoup-worker-fuzzer died\" >&2\n\t\texit 1\n\telse\n\t\t((i=i-1))\n\t\tsleep 1\n\tfi\ndone\n\necho \"run-fuzzer.sh [INFO] mediasoup-worker-fuzzer is still running after given ${DURATION_SEC} seconds so no fuzzer issues so far\"\n\nkill -SIGTERM ${MEDIASOUP_WORKER_FUZZER_PID} &> /dev/null\nexit 0\n"
  },
  {
    "path": "worker/src/Channel/ChannelMessageRegistrator.cpp",
    "content": "#define MS_CLASS \"ChannelMessageRegistrator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Channel/ChannelMessageRegistrator.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <vector>\n\nnamespace Channel\n{\n\tChannelMessageRegistrator::ChannelMessageRegistrator()\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tChannelMessageRegistrator::~ChannelMessageRegistrator()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->mapChannelRequestHandlers.clear();\n\t\tthis->mapChannelNotificationHandlers.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Worker::ChannelMessageHandlers> ChannelMessageRegistrator::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\t// Add channelRequestHandlerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> channelRequestHandlerIds;\n\n\t\tfor (const auto& kv : this->mapChannelRequestHandlers)\n\t\t{\n\t\t\tconst auto& handlerId = kv.first;\n\n\t\t\tchannelRequestHandlerIds.push_back(builder.CreateString(handlerId));\n\t\t}\n\n\t\t// Add channelNotificationHandlerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> channelNotificationHandlerIds;\n\n\t\tfor (const auto& kv : this->mapChannelNotificationHandlers)\n\t\t{\n\t\t\tconst auto& handlerId = kv.first;\n\n\t\t\tchannelNotificationHandlerIds.push_back(builder.CreateString(handlerId));\n\t\t}\n\n\t\treturn FBS::Worker::CreateChannelMessageHandlersDirect(\n\t\t  builder, &channelRequestHandlerIds, &channelNotificationHandlerIds);\n\t}\n\n\tvoid ChannelMessageRegistrator::RegisterHandler(\n\t  const std::string& id,\n\t  ChannelSocket::RequestHandler* channelRequestHandler,\n\t  ChannelSocket::NotificationHandler* channelNotificationHandler)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (channelRequestHandler)\n\t\t{\n\t\t\tif (this->mapChannelRequestHandlers.contains(id))\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"Channel request handler with ID %s already exists\", id.c_str());\n\t\t\t}\n\n\t\t\tthis->mapChannelRequestHandlers[id] = channelRequestHandler;\n\t\t}\n\n\t\tif (channelNotificationHandler)\n\t\t{\n\t\t\tif (this->mapChannelNotificationHandlers.contains(id))\n\t\t\t{\n\t\t\t\tif (channelRequestHandler)\n\t\t\t\t{\n\t\t\t\t\tthis->mapChannelRequestHandlers.erase(id);\n\t\t\t\t}\n\n\t\t\t\tMS_THROW_ERROR(\"Channel notification handler with ID %s already exists\", id.c_str());\n\t\t\t}\n\n\t\t\tthis->mapChannelNotificationHandlers[id] = channelNotificationHandler;\n\t\t}\n\t}\n\n\tvoid ChannelMessageRegistrator::UnregisterHandler(const std::string& id)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->mapChannelRequestHandlers.erase(id);\n\t\tthis->mapChannelNotificationHandlers.erase(id);\n\t}\n\n\tChannelSocket::RequestHandler* ChannelMessageRegistrator::GetChannelRequestHandler(\n\t  const std::string& id)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapChannelRequestHandlers.find(id);\n\n\t\tif (it != this->mapChannelRequestHandlers.end())\n\t\t{\n\t\t\treturn it->second;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\t}\n\n\tChannelSocket::NotificationHandler* ChannelMessageRegistrator::GetChannelNotificationHandler(\n\t  const std::string& id)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapChannelNotificationHandlers.find(id);\n\n\t\tif (it != this->mapChannelNotificationHandlers.end())\n\t\t{\n\t\t\treturn it->second;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\t}\n} // namespace Channel\n"
  },
  {
    "path": "worker/src/Channel/ChannelNotification.cpp",
    "content": "#define MS_CLASS \"Channel::ChannelNotification\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Channel/ChannelNotification.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace Channel\n{\n\t/* Class variables. */\n\n\t// clang-format off\n\tconst absl::flat_hash_map<FBS::Notification::Event, const char*> ChannelNotification::Event2String =\n\t{\n\t\t{ FBS::Notification::Event::WORKER_CLOSE,        \"worker.close\"       },\n\t\t{ FBS::Notification::Event::TRANSPORT_SEND_RTCP, \"transport.sendRtcp\" },\n\t\t{ FBS::Notification::Event::PRODUCER_SEND,       \"producer.send\"      },\n\t\t{ FBS::Notification::Event::DATAPRODUCER_SEND,   \"dataProducer.send\"  },\n\t};\n\t// clang-format on\n\n\t/* Instance methods. */\n\n\tChannelNotification::ChannelNotification(const FBS::Notification::Notification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->data  = notification;\n\t\tthis->event = notification->event();\n\n\t\tauto eventCStrIt = ChannelNotification::Event2String.find(this->event);\n\n\t\tif (eventCStrIt == ChannelNotification::Event2String.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"unknown event '%\" PRIu8 \"'\", static_cast<uint8_t>(this->event));\n\t\t}\n\n\t\tthis->eventCStr = eventCStrIt->second;\n\t\tthis->handlerId = this->data->handlerId()->str();\n\t}\n} // namespace Channel\n"
  },
  {
    "path": "worker/src/Channel/ChannelNotifier.cpp",
    "content": "#define MS_CLASS \"Channel::Notifier\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Channel/ChannelNotifier.hpp\"\n#include \"Logger.hpp\"\n\nnamespace Channel\n{\n\t/* Instance methods. */\n\n\tChannelNotifier::ChannelNotifier(Channel::ChannelSocket* channel) : channel(channel)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid ChannelNotifier::Emit(const std::string& targetId, FBS::Notification::Event event)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& builder = this->bufferBuilder;\n\t\tauto notification = FBS::Notification::CreateNotificationDirect(builder, targetId.c_str(), event);\n\t\tauto message =\n\t\t  FBS::Message::CreateMessage(builder, FBS::Message::Body::Notification, notification.Union());\n\n\t\tbuilder.FinishSizePrefixed(message);\n\t\tthis->channel->Send(builder.GetBufferPointer(), builder.GetSize());\n\t\tbuilder.Clear();\n\t}\n} // namespace Channel\n"
  },
  {
    "path": "worker/src/Channel/ChannelRequest.cpp",
    "content": "#define MS_CLASS \"Channel::ChannelRequest\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Channel/ChannelRequest.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace Channel\n{\n\t/* Class variables. */\n\n\tthread_local flatbuffers::FlatBufferBuilder ChannelRequest::bufferBuilder{};\n\n\t// clang-format off\n\tconst absl::flat_hash_map<FBS::Request::Method, const char*> ChannelRequest::Method2String =\n\t{\n\t\t{ FBS::Request::Method::WORKER_DUMP,                                    \"worker.dump\"                                },\n\t\t{ FBS::Request::Method::WORKER_GET_RESOURCE_USAGE,                      \"worker.getResourceUsage\"                    },\n\t\t{ FBS::Request::Method::WORKER_UPDATE_SETTINGS,                         \"worker.updateSettings\"                      },\n\t\t{ FBS::Request::Method::WORKER_CREATE_WEBRTCSERVER,                     \"worker.createWebRtcServer\"                  },\n\t\t{ FBS::Request::Method::WORKER_CREATE_ROUTER,                           \"worker.createRouter\"                        },\n\t\t{ FBS::Request::Method::WORKER_WEBRTCSERVER_CLOSE,                      \"worker.closeWebRtcServer\"                   },\n\t\t{ FBS::Request::Method::WORKER_CLOSE_ROUTER,                            \"worker.closeRouter\"                         },\n\t\t{ FBS::Request::Method::WEBRTCSERVER_DUMP,                              \"webRtcServer.dump\"                          },\n\t\t{ FBS::Request::Method::ROUTER_DUMP,                                    \"router.dump\"                                },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_WEBRTCTRANSPORT,                  \"router.createWebRtcTransport\"               },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER,      \"router.createWebRtcTransportWithServer\"     },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_PLAINTRANSPORT,                   \"router.createPlainTransport\"                },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_PIPETRANSPORT,                    \"router.createPipeTransport\"                 },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_DIRECTTRANSPORT,                  \"router.createDirectTransport\"               },\n\t\t{ FBS::Request::Method::ROUTER_CLOSE_TRANSPORT,                         \"router.closeTransport\"                      },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_ACTIVESPEAKEROBSERVER,            \"router.createActiveSpeakerObserver\"         },\n\t\t{ FBS::Request::Method::ROUTER_CREATE_AUDIOLEVELOBSERVER,               \"router.createAudioLevelObserver\"            },\n\t\t{ FBS::Request::Method::ROUTER_CLOSE_RTPOBSERVER,                       \"router.closeRtpObserver\"                    },\n\t\t{ FBS::Request::Method::TRANSPORT_DUMP,                                 \"transport.dump\"                             },\n\t\t{ FBS::Request::Method::TRANSPORT_GET_STATS,                            \"transport.getStats\"                         },\n\t\t{ FBS::Request::Method::TRANSPORT_CONNECT,                              \"transport.connect\"                          },\n\t\t{ FBS::Request::Method::TRANSPORT_SET_MAX_INCOMING_BITRATE,             \"transport.setMaxIncomingBitrate\"            },\n\t\t{ FBS::Request::Method::TRANSPORT_SET_MAX_OUTGOING_BITRATE,             \"transport.setMaxOutgoingBitrate\"            },\n\t\t{ FBS::Request::Method::TRANSPORT_SET_MIN_OUTGOING_BITRATE,             \"transport.setMinOutgoingBitrate\"            },\n\t\t{ FBS::Request::Method::TRANSPORT_RESTART_ICE,                          \"transport.restartIce\"                       },\n\t\t{ FBS::Request::Method::TRANSPORT_PRODUCE,                              \"transport.produce\"                          },\n\t\t{ FBS::Request::Method::TRANSPORT_PRODUCE_DATA,                         \"transport.produceData\"                      },\n\t\t{ FBS::Request::Method::TRANSPORT_CONSUME,                              \"transport.consume\"                          },\n\t\t{ FBS::Request::Method::TRANSPORT_CONSUME_DATA,                         \"transport.consumeData\"                      },\n\t\t{ FBS::Request::Method::TRANSPORT_ENABLE_TRACE_EVENT,                   \"transport.enableTraceEvent\"                 },\n\t\t{ FBS::Request::Method::TRANSPORT_CLOSE_PRODUCER,                       \"transport.closeProducer\"                    },\n\t\t{ FBS::Request::Method::TRANSPORT_CLOSE_CONSUMER,                       \"transport.closeConsumer\"                    },\n\t\t{ FBS::Request::Method::TRANSPORT_CLOSE_DATAPRODUCER,                   \"transport.closeDataProducer\"                },\n\t\t{ FBS::Request::Method::TRANSPORT_CLOSE_DATACONSUMER,                   \"transport.closeDataConsumer\"                },\n\t\t{ FBS::Request::Method::PLAINTRANSPORT_CONNECT,                         \"plainTransport.connect\"                     },\n\t\t{ FBS::Request::Method::PIPETRANSPORT_CONNECT,                          \"pipeTransport.connect\"                      },\n\t\t{ FBS::Request::Method::WEBRTCTRANSPORT_CONNECT,                        \"webRtcTransport.connect\"                    },\n\t\t{ FBS::Request::Method::PRODUCER_DUMP,                                  \"producer.dump\"                              },\n\t\t{ FBS::Request::Method::PRODUCER_GET_STATS,                             \"producer.getStats\"                          },\n\t\t{ FBS::Request::Method::PRODUCER_PAUSE,                                 \"producer.pause\"                             },\n\t\t{ FBS::Request::Method::PRODUCER_RESUME,                                \"producer.resume\"                            },\n\t\t{ FBS::Request::Method::PRODUCER_ENABLE_TRACE_EVENT,                    \"producer.enableTraceEvent\"                  },\n\t\t{ FBS::Request::Method::CONSUMER_DUMP,                                  \"consumer.dump\"                              },\n\t\t{ FBS::Request::Method::CONSUMER_GET_STATS,                             \"consumer.getStats\"                          },\n\t\t{ FBS::Request::Method::CONSUMER_PAUSE,                                 \"consumer.pause\"                             },\n\t\t{ FBS::Request::Method::CONSUMER_RESUME,                                \"consumer.resume\"                            },\n\t\t{ FBS::Request::Method::CONSUMER_SET_PREFERRED_LAYERS,                  \"consumer.setPreferredLayers\"                },\n\t\t{ FBS::Request::Method::CONSUMER_SET_PRIORITY,                          \"consumer.setPriority\"                       },\n\t\t{ FBS::Request::Method::CONSUMER_REQUEST_KEY_FRAME,                     \"consumer.requestKeyFrame\"                   },\n\t\t{ FBS::Request::Method::CONSUMER_ENABLE_TRACE_EVENT,                    \"consumer.enableTraceEvent\"                  },\n\t\t{ FBS::Request::Method::DATAPRODUCER_DUMP,                              \"dataProducer.dump\"                          },\n\t\t{ FBS::Request::Method::DATAPRODUCER_GET_STATS,                         \"dataProducer.getStats\"                      },\n\t\t{ FBS::Request::Method::DATAPRODUCER_PAUSE,                             \"dataProducer.pause\"                         },\n\t\t{ FBS::Request::Method::DATAPRODUCER_RESUME,                            \"dataProducer.resume\"                        },\n\t\t{ FBS::Request::Method::DATACONSUMER_DUMP,                              \"dataConsumer.dump\"                          },\n\t\t{ FBS::Request::Method::DATACONSUMER_GET_STATS,                         \"dataConsumer.getStats\"                      },\n\t\t{ FBS::Request::Method::DATACONSUMER_PAUSE,                             \"dataConsumer.pause\"                         },\n\t\t{ FBS::Request::Method::DATACONSUMER_RESUME,                            \"dataConsumer.resume\"                        },\n\t\t{ FBS::Request::Method::DATACONSUMER_GET_BUFFERED_AMOUNT,               \"dataConsumer.getBufferedAmount\"             },\n\t\t{ FBS::Request::Method::DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, \"dataConsumer.setBufferedAmountLowThreshold\" },\n\t\t{ FBS::Request::Method::DATACONSUMER_SEND,                              \"dataConsumer.send\"                          },\n\t\t{ FBS::Request::Method::DATACONSUMER_SET_SUBCHANNELS,                   \"dataConsumer.setSubchannels\"                },\n\t\t{ FBS::Request::Method::DATACONSUMER_ADD_SUBCHANNEL,                    \"dataConsumer.addSubchannel\"                 },\n\t\t{ FBS::Request::Method::DATACONSUMER_REMOVE_SUBCHANNEL,                 \"dataConsumer.removeSubchannel\"              },\n\t\t{ FBS::Request::Method::RTPOBSERVER_PAUSE,                              \"rtpObserver.pause\"                          },\n\t\t{ FBS::Request::Method::RTPOBSERVER_RESUME,                             \"rtpObserver.resume\"                         },\n\t\t{ FBS::Request::Method::RTPOBSERVER_ADD_PRODUCER,                       \"rtpObserver.addProducer\"                    },\n\t\t{ FBS::Request::Method::RTPOBSERVER_REMOVE_PRODUCER,                    \"rtpObserver.removeProducer\"                 },\n\t};\n\t// clang-format on\n\n\t/* Instance methods. */\n\n\t/**\n\t * msg contains the request flatbuffer.\n\t */\n\tChannelRequest::ChannelRequest(Channel::ChannelSocket* channel, const FBS::Request::Request* request)\n\t  : channel(channel)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->data   = request;\n\t\tthis->id     = request->id();\n\t\tthis->method = request->method();\n\n\t\tconst auto methodCStrIt = ChannelRequest::Method2String.find(this->method);\n\n\t\tif (methodCStrIt == ChannelRequest::Method2String.end())\n\t\t{\n\t\t\tError(\"unknown method\");\n\n\t\t\tMS_THROW_ERROR(\"unknown method '%\" PRIu8 \"'\", static_cast<uint8_t>(this->method));\n\t\t}\n\n\t\tthis->methodCStr = methodCStrIt->second;\n\t\tthis->handlerId  = this->data->handlerId()->str();\n\t}\n\n\tvoid ChannelRequest::Accept()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(!this->replied, \"request already replied\");\n\n\t\tthis->replied = true;\n\n\t\tauto& builder = ChannelRequest::bufferBuilder;\n\t\tauto response =\n\t\t  FBS::Response::CreateResponse(builder, this->id, true, FBS::Response::Body::NONE, 0);\n\n\t\tSendResponse(response);\n\t}\n\n\tvoid ChannelRequest::Error(const char* reason)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(!this->replied, \"request already replied\");\n\n\t\tthis->replied = true;\n\n\t\tauto& builder = ChannelRequest::bufferBuilder;\n\t\tauto response = FBS::Response::CreateResponseDirect(\n\t\t  builder, this->id, /*accepted*/ false, FBS::Response::Body::NONE, 0, /*error*/ \"Error\", reason);\n\n\t\tSendResponse(response);\n\t}\n\n\tvoid ChannelRequest::TypeError(const char* reason)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(!this->replied, \"request already replied\");\n\n\t\tthis->replied = true;\n\n\t\tauto& builder = ChannelRequest::bufferBuilder;\n\t\tauto response = FBS::Response::CreateResponseDirect(\n\t\t  builder, this->id, /*accepted*/ false, FBS::Response::Body::NONE, 0, /*error*/ \"TypeError\", reason);\n\n\t\tSendResponse(response);\n\t}\n\n\tvoid ChannelRequest::Send(const uint8_t* buffer, size_t size) const\n\t{\n\t\tthis->channel->Send(buffer, size);\n\t}\n\n\tvoid ChannelRequest::SendResponse(const flatbuffers::Offset<FBS::Response::Response>& response) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& builder = ChannelRequest::bufferBuilder;\n\t\tauto message =\n\t\t  FBS::Message::CreateMessage(builder, FBS::Message::Body::Response, response.Union());\n\n\t\tbuilder.FinishSizePrefixed(message);\n\t\tSend(builder.GetBufferPointer(), builder.GetSize());\n\t\tbuilder.Clear();\n\t}\n} // namespace Channel\n"
  },
  {
    "path": "worker/src/Channel/ChannelSocket.cpp",
    "content": "#define MS_CLASS \"Channel::ChannelSocket\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Channel/ChannelSocket.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memcpy(), std::memmove()\n\nnamespace Channel\n{\n\t// Binary length for a 4194304 bytes payload.\n\tstatic constexpr size_t MessageMaxLen{ 4194308 };\n\tstatic constexpr size_t PayloadMaxLen{ 4194304 };\n\n\t/* Static methods for UV callbacks. */\n\n\tinline static void onAsync(uv_handle_t* handle)\n\t{\n\t\twhile (static_cast<ChannelSocket*>(handle->data)->CallbackRead())\n\t\t{\n\t\t\t// Read while there are new messages.\n\t\t}\n\t}\n\n\tinline static void onCloseAsync(uv_handle_t* handle)\n\t{\n\t\tdelete reinterpret_cast<uv_async_t*>(handle);\n\t}\n\n\t/* Instance methods. */\n\n#if defined(MS_TEST) || defined(MS_FUZZER)\n\tChannelSocket::ChannelSocket()\n\t{\n\t\tMS_TRACE_STD();\n\t}\n#endif\n\n\tChannelSocket::ChannelSocket(int consumerFd, int producerFd)\n\t  : consumerSocket(new ConsumerSocket(consumerFd, MessageMaxLen, this)),\n\t    producerSocket(new ProducerSocket(producerFd, MessageMaxLen))\n\t{\n\t\tMS_TRACE_STD();\n\t}\n\n\tChannelSocket::ChannelSocket(\n\t  ChannelReadFn channelReadFn,\n\t  ChannelReadCtx channelReadCtx,\n\t  ChannelWriteFn channelWriteFn,\n\t  ChannelWriteCtx channelWriteCtx)\n\t  : channelReadFn(channelReadFn),\n\t    channelReadCtx(channelReadCtx),\n\t    channelWriteFn(channelWriteFn),\n\t    channelWriteCtx(channelWriteCtx),\n\t    uvReadHandle(new uv_async_t)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tint err;\n\n\t\tthis->uvReadHandle->data = static_cast<void*>(this);\n\n\t\terr =\n\t\t  uv_async_init(DepLibUV::GetLoop(), this->uvReadHandle, reinterpret_cast<uv_async_cb>(onAsync));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tdelete this->uvReadHandle;\n\t\t\tthis->uvReadHandle = nullptr;\n\n\t\t\tMS_THROW_ERROR_STD(\"uv_async_init() failed: %s\", uv_strerror(err));\n\t\t}\n\n\t\terr = uv_async_send(this->uvReadHandle);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tdelete this->uvReadHandle;\n\t\t\tthis->uvReadHandle = nullptr;\n\n\t\t\tMS_THROW_ERROR_STD(\"uv_async_send() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n\n\tChannelSocket::~ChannelSocket()\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tif (!this->closed)\n\t\t{\n\t\t\tClose();\n\t\t}\n\n\t\tdelete this->consumerSocket;\n\t\tdelete this->producerSocket;\n\t}\n\n\tvoid ChannelSocket::Close()\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tif (this->closed)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->closed = true;\n\n\t\tif (this->uvReadHandle)\n\t\t{\n\t\t\tuv_close(\n\t\t\t  reinterpret_cast<uv_handle_t*>(this->uvReadHandle), static_cast<uv_close_cb>(onCloseAsync));\n\t\t}\n\n\t\tif (this->consumerSocket)\n\t\t{\n\t\t\tthis->consumerSocket->Close();\n\t\t}\n\n\t\tif (this->producerSocket)\n\t\t{\n\t\t\tthis->producerSocket->Close();\n\t\t}\n\t}\n\n\tvoid ChannelSocket::SetListener(Listener* listener)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tthis->listener = listener;\n\t}\n\n\tvoid ChannelSocket::Send(const uint8_t* data, uint32_t dataLen)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tif (this->closed)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (dataLen > PayloadMaxLen)\n\t\t{\n\t\t\tMS_ERROR_STD(\"message too big\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tSendImpl(data, dataLen);\n\t}\n\n\tvoid ChannelSocket::SendLog(const char* data, uint32_t dataLen)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tif (this->closed)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (dataLen > PayloadMaxLen)\n\t\t{\n\t\t\tMS_ERROR_STD(\"message too big\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto& builder = this->bufferBuilder;\n\t\tauto log      = FBS::Log::CreateLogDirect(builder, data);\n\t\tauto message  = FBS::Message::CreateMessage(builder, FBS::Message::Body::Log, log.Union());\n\n\t\tbuilder.FinishSizePrefixed(message);\n\t\tthis->Send(builder.GetBufferPointer(), builder.GetSize());\n\t\tbuilder.Clear();\n\t}\n\n\tbool ChannelSocket::CallbackRead()\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tif (this->closed)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tuint8_t* msg{ nullptr };\n\t\tuint32_t msgLen;\n\t\tsize_t msgCtx;\n\n\t\t// Try to read next message using `channelReadFn`, message, its length and\n\t\t// context will be stored in provided arguments.\n\t\tauto free = this->channelReadFn(\n\t\t  std::addressof(msg),\n\t\t  std::addressof(msgLen),\n\t\t  std::addressof(msgCtx),\n\t\t  this->uvReadHandle,\n\t\t  this->channelReadCtx);\n\n\t\t// Non-null free function pointer means message was successfully read above\n\t\t// and will need to be freed later.\n\t\tif (free)\n\t\t{\n\t\t\tconst auto* message = FBS::Message::GetMessage(msg);\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tauto s = flatbuffers::FlatBufferToString(\n\t\t\t  reinterpret_cast<uint8_t*>(msg), FBS::Message::MessageTypeTable());\n\t\t\tMS_DUMP(\"%s\", s.c_str());\n#endif\n\n\t\t\tif (message->data_type() == FBS::Message::Body::Request)\n\t\t\t{\n\t\t\t\tChannelRequest* request{ nullptr };\n\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\trequest = new ChannelRequest(this, message->data_as<FBS::Request::Request>());\n\n\t\t\t\t\t// Notify the listener.\n\t\t\t\t\tthis->listener->HandleRequest(request);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupTypeError& error)\n\t\t\t\t{\n\t\t\t\t\trequest->TypeError(error.what());\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\trequest->Error(error.what());\n\t\t\t\t}\n\n\t\t\t\tdelete request;\n\t\t\t}\n\t\t\telse if (message->data_type() == FBS::Message::Body::Notification)\n\t\t\t{\n\t\t\t\tChannelNotification* notification{ nullptr };\n\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tnotification = new ChannelNotification(message->data_as<FBS::Notification::Notification>());\n\n\t\t\t\t\t// Notify the listener.\n\t\t\t\t\tthis->listener->HandleNotification(notification);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tMS_ERROR(\"notification failed: %s\", error.what());\n\t\t\t\t}\n\n\t\t\t\tdelete notification;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_ERROR(\"discarding wrong Channel data\");\n\t\t\t}\n\n\t\t\t// Message needs to be freed using stored function pointer.\n\t\t\tfree(msg, msgLen, msgCtx);\n\t\t}\n\n\t\t// Return `true` if something was processed.\n\t\treturn free != nullptr;\n\t}\n\n\tvoid ChannelSocket::SendImpl(const uint8_t* payload, uint32_t payloadLen)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\t// Write using function call if provided.\n\t\tif (this->channelWriteFn)\n\t\t{\n\t\t\tthis->channelWriteFn(payload, payloadLen, this->channelWriteCtx);\n\t\t}\n\t\telse if (this->producerSocket)\n\t\t{\n\t\t\tthis->producerSocket->Write(payload, payloadLen);\n\t\t}\n\t}\n\n\tvoid ChannelSocket::OnConsumerSocketMessage(\n\t  const ConsumerSocket* /*consumerSocket*/, char* msg, size_t /*msgLen*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* message = FBS::Message::GetMessage(msg);\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\tauto s = flatbuffers::FlatBufferToString(\n\t\t  reinterpret_cast<uint8_t*>(msg), FBS::Message::MessageTypeTable());\n\t\tMS_DUMP(\"%s\", s.c_str());\n#endif\n\n\t\tif (message->data_type() == FBS::Message::Body::Request)\n\t\t{\n\t\t\tChannelRequest* request{ nullptr };\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\trequest = new ChannelRequest(this, message->data_as<FBS::Request::Request>());\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->HandleRequest(request);\n\t\t\t}\n\t\t\tcatch (const MediaSoupTypeError& error)\n\t\t\t{\n\t\t\t\trequest->TypeError(error.what());\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\trequest->Error(error.what());\n\t\t\t}\n\n\t\t\tdelete request;\n\t\t}\n\t\telse if (message->data_type() == FBS::Message::Body::Notification)\n\t\t{\n\t\t\tChannelNotification* notification{ nullptr };\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tnotification = new ChannelNotification(message->data_as<FBS::Notification::Notification>());\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->HandleNotification(notification);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_ERROR(\"notification failed: %s\", error.what());\n\t\t\t}\n\n\t\t\tdelete notification;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_ERROR(\"discarding wrong Channel data\");\n\t\t}\n\t}\n\n\tvoid ChannelSocket::OnConsumerSocketClosed(const ConsumerSocket* /*consumerSocket*/)\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tthis->listener->OnChannelClosed(this);\n\t}\n\n\t/* Instance methods. */\n\n\tConsumerSocket::ConsumerSocket(int fd, size_t bufferSize, Listener* listener)\n\t  : ::UnixStreamSocketHandle(fd, bufferSize, ::UnixStreamSocketHandle::Role::CONSUMER),\n\t    listener(listener)\n\t{\n\t\tMS_TRACE_STD();\n\t}\n\n\tConsumerSocket::~ConsumerSocket()\n\t{\n\t\tMS_TRACE_STD();\n\t}\n\n\tvoid ConsumerSocket::UserOnUnixStreamRead()\n\t{\n\t\tMS_TRACE_STD();\n\n\t\tsize_t msgStart{ 0 };\n\n\t\t// Be ready to parse more than a single message in a single chunk.\n\t\twhile (true)\n\t\t{\n\t\t\tif (IsClosed())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst size_t readLen = this->bufferDataLen - msgStart;\n\n\t\t\tif (readLen < sizeof(uint32_t))\n\t\t\t{\n\t\t\t\t// Incomplete data.\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tuint32_t msgLen;\n\n\t\t\t// Read message length.\n\t\t\tstd::memcpy(std::addressof(msgLen), this->buffer + msgStart, sizeof(uint32_t));\n\n\t\t\tif (readLen < sizeof(uint32_t) + static_cast<size_t>(msgLen))\n\t\t\t{\n\t\t\t\t// Incomplete data.\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tthis->listener->OnConsumerSocketMessage(\n\t\t\t  this,\n\t\t\t  reinterpret_cast<char*>(this->buffer + msgStart + sizeof(uint32_t)),\n\t\t\t  static_cast<size_t>(msgLen));\n\n\t\t\tmsgStart += sizeof(uint32_t) + static_cast<size_t>(msgLen);\n\t\t}\n\n\t\tif (msgStart != 0)\n\t\t{\n\t\t\tthis->bufferDataLen = this->bufferDataLen - msgStart;\n\n\t\t\tif (this->bufferDataLen != 0)\n\t\t\t{\n\t\t\t\tstd::memmove(this->buffer, this->buffer + msgStart, this->bufferDataLen);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid ConsumerSocket::UserOnUnixStreamSocketClosed()\n\t{\n\t\tMS_TRACE_STD();\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnConsumerSocketClosed(this);\n\t}\n\n\t/* Instance methods. */\n\n\tProducerSocket::ProducerSocket(int fd, size_t bufferSize)\n\t  : ::UnixStreamSocketHandle(fd, bufferSize, ::UnixStreamSocketHandle::Role::PRODUCER)\n\t{\n\t\tMS_TRACE_STD();\n\t}\n} // namespace Channel\n"
  },
  {
    "path": "worker/src/DepLibSRTP.cpp",
    "content": "#define MS_CLASS \"DepLibSRTP\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepLibSRTP.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <mutex>\n\n/* Static variables. */\n\nstatic std::mutex GlobalSyncMutex;\nstatic size_t GlobalInstances = 0;\n\n// NOTE: This map must always be in sync with the srtp_err_status_t in srtp.h\n// in libsrtp library:\n//   https://github.com/cisco/libsrtp/blob/main/include/srtp.h\n//\n// clang-format off\nconst std::unordered_map<srtp_err_status_t, std::string> DepLibSRTP::ErrorCode2String =\n{\n\t{ srtp_err_status_ok,            \"nothing to report\" },\n\t{ srtp_err_status_fail,          \"unspecified failure\" },\n\t{ srtp_err_status_bad_param,     \"unsupported parameter\" },\n\t{ srtp_err_status_alloc_fail,    \"couldn't allocate memory\" },\n\t{ srtp_err_status_dealloc_fail,  \"couldn't deallocate properly\" },\n\t{ srtp_err_status_init_fail,     \"couldn't initialize\" },\n\t{ srtp_err_status_terminus,      \"can't process as much data as requested\" },\n\t{ srtp_err_status_auth_fail,     \"authentication failure\" },\n\t{ srtp_err_status_cipher_fail,   \"cipher failure\" },\n\t{ srtp_err_status_replay_fail,   \"replay check failed (bad index)\" },\n\t{ srtp_err_status_replay_old,    \"replay check failed (index too old)\" },\n\t{ srtp_err_status_algo_fail,     \"algorithm failed test routine\" },\n\t{ srtp_err_status_no_such_op,    \"unsupported operation\" },\n\t{ srtp_err_status_no_ctx,        \"no appropriate context found\" },\n\t{ srtp_err_status_cant_check,    \"unable to perform desired validation\" },\n\t{ srtp_err_status_key_expired,   \"can't use key any more\" },\n\t{ srtp_err_status_socket_err,    \"error in use of socket\" },\n\t{ srtp_err_status_signal_err,    \"error in use POSIX signals\" },\n\t{ srtp_err_status_nonce_bad,     \"nonce check failed\" },\n\t{ srtp_err_status_read_fail,     \"couldn't read data\" },\n\t{ srtp_err_status_write_fail,    \"couldn't write data\" },\n\t{ srtp_err_status_parse_err,     \"error parsing data\" },\n\t{ srtp_err_status_encode_err,    \"error encoding data\" },\n\t{ srtp_err_status_semaphore_err, \"error while using semaphores\" },\n\t{ srtp_err_status_pfkey_err,     \"error while using pfkey\" },\n\t{ srtp_err_status_bad_mki,       \"error MKI present in packet is invalid\" },\n\t{ srtp_err_status_pkt_idx_old,   \"packet index is too old to consider\" },\n\t{ srtp_err_status_pkt_idx_adv,   \"packet index advanced, reset needed\" },\n\t{ srtp_err_status_buffer_small,  \"out buffer is too small\" },\n\t{ srtp_err_status_cryptex_err,   \"unsupported cryptex operation\" }\n};\n// clang-format on\n\n/* Static methods. */\n\nvoid DepLibSRTP::ClassInit()\n{\n\tMS_TRACE();\n\n\t{\n\t\tconst std::lock_guard<std::mutex> lock(GlobalSyncMutex);\n\n\t\tif (GlobalInstances == 0)\n\t\t{\n\t\t\tMS_DEBUG_TAG(info, \"libsrtp version: \\\"%s\\\"\", srtp_get_version_string());\n\n\t\t\tconst srtp_err_status_t err = srtp_init();\n\n\t\t\tif (DepLibSRTP::IsError(err))\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"srtp_init() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\t\t\t}\n\t\t}\n\n\t\t++GlobalInstances;\n\t}\n}\n\nvoid DepLibSRTP::ClassDestroy()\n{\n\tMS_TRACE();\n\n\t{\n\t\tconst std::lock_guard<std::mutex> lock(GlobalSyncMutex);\n\t\t--GlobalInstances;\n\n\t\tif (GlobalInstances == 0)\n\t\t{\n\t\t\tsrtp_shutdown();\n\t\t}\n\t}\n}\n\nconst std::string& DepLibSRTP::GetErrorString(srtp_err_status_t code)\n{\n\tMS_TRACE();\n\n\tstatic const std::string UnknownError(\"unknown libsrtp error\");\n\n\tauto it = DepLibSRTP::ErrorCode2String.find(code);\n\n\tif (it == DepLibSRTP::ErrorCode2String.end())\n\t{\n\t\treturn UnknownError;\n\t}\n\n\treturn it->second;\n}\n"
  },
  {
    "path": "worker/src/DepLibUV.cpp",
    "content": "#define MS_CLASS \"DepLibUV\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n\n/* Class variables. */\n\nthread_local uv_loop_t* DepLibUV::loop{ nullptr };\n\n/* Static methods for UV callbacks. */\n\ninline static void onCloseLoop(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_loop_t*>(handle);\n}\n\ninline static void onWalk(uv_handle_t* handle, void* /*arg*/)\n{\n\t// Must use MS_ERROR_STD since at this point the Channel is already closed.\n\tMS_ERROR_STD(\n\t  \"alive UV handle found (this shouldn't happen) [type:%s, active:%d, closing:%d, has_ref:%d]\",\n\t  uv_handle_type_name(handle->type),\n\t  uv_is_active(handle),\n\t  uv_is_closing(handle),\n\t  uv_has_ref(handle));\n\n\tif (!uv_is_closing(handle))\n\t{\n\t\tuv_close(handle, onCloseLoop);\n\t}\n}\n\n/* Static methods. */\n\nvoid DepLibUV::ClassInit()\n{\n\t// NOTE: Logger depends on this so we cannot log anything here.\n\n\tDepLibUV::loop = new uv_loop_t;\n\n\tconst int err = uv_loop_init(DepLibUV::loop);\n\n\tif (err != 0)\n\t{\n\t\tMS_ABORT(\"libuv loop initialization failed\");\n\t}\n}\n\nvoid DepLibUV::ClassDestroy()\n{\n\tMS_TRACE();\n\n\t// Here we should not have any UV handle left. All them should have been\n\t// already closed+freed. However, in order to not introduce regressions\n\t// in the future, we check this anyway.\n\t// More context: https://github.com/versatica/mediasoup/pull/576\n\n\tint err;\n\n\tuv_stop(DepLibUV::loop);\n\tuv_walk(DepLibUV::loop, onWalk, nullptr);\n\n\twhile (true)\n\t{\n\t\terr = uv_loop_close(DepLibUV::loop);\n\n\t\tif (err != UV_EBUSY)\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\tuv_run(DepLibUV::loop, UV_RUN_NOWAIT);\n\t}\n\n\tif (err != 0)\n\t{\n\t\tMS_ERROR_STD(\"failed to close libuv loop: %s\", uv_err_name(err));\n\t}\n\n\tdelete DepLibUV::loop;\n}\n\nvoid DepLibUV::PrintVersion()\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_TAG(info, \"libuv version: \\\"%s\\\"\", uv_version_string());\n}\n\nvoid DepLibUV::RunLoop()\n{\n\tMS_TRACE();\n\n\t// This should never happen.\n\tMS_ASSERT(DepLibUV::loop != nullptr, \"loop unset\");\n\n\tconst int ret = uv_run(DepLibUV::loop, UV_RUN_DEFAULT);\n\n\tMS_ASSERT(ret == 0, \"uv_run() returned %s\", uv_err_name(ret));\n}\n"
  },
  {
    "path": "worker/src/DepLibUring.cpp",
    "content": "#define MS_CLASS \"DepLibUring\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepLibUring.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include <stdexcept>\n#include <sys/eventfd.h>\n#include <sys/resource.h>\n#include <sys/utsname.h>\n\n/* Class variables. */\n\nthread_local bool DepLibUring::enabled{ false };\n// liburing instance per thread.\nthread_local DepLibUring::LibUring* DepLibUring::liburing{ nullptr };\n// Completion queue entry array used to retrieve processes tasks.\nthread_local struct io_uring_cqe* Cqes[DepLibUring::QueueDepth];\n\n/* Static methods for UV callbacks. */\n\ninline static void onCloseFd(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_poll_t*>(handle);\n}\n\ninline static void onFdEvent(uv_poll_t* handle, int /*status*/, int /*events*/)\n{\n\tauto* liburing = static_cast<DepLibUring::LibUring*>(handle->data);\n\tauto count     = io_uring_peek_batch_cqe(liburing->GetRing(), Cqes, DepLibUring::QueueDepth);\n\n\t// libuv uses level triggering, so we need to read from the socket to reset\n\t// the counter in order to avoid libuv calling this callback indefinitely.\n\teventfd_t v;\n\tconst int err = eventfd_read(liburing->GetEventFd(), std::addressof(v));\n\n\tif (err < 0)\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\tMS_ABORT(\"eventfd_read() failed: %s\", std::strerror(error));\n\t};\n\n\tfor (unsigned int i{ 0 }; i < count; ++i)\n\t{\n\t\tstruct io_uring_cqe* cqe = Cqes[i];\n\t\tauto* userData           = static_cast<DepLibUring::UserData*>(io_uring_cqe_get_data(cqe));\n\n\t\tif (liburing->IsZeroCopyEnabled())\n\t\t{\n\t\t\t// CQE notification for a zero-copy submission.\n\t\t\tif (cqe->flags & IORING_CQE_F_NOTIF)\n\t\t\t{\n\t\t\t\t// The send buffer is now in the network card, run the send callback.\n\t\t\t\tif (userData->cb)\n\t\t\t\t{\n\t\t\t\t\t(*userData->cb)(true);\n\t\t\t\t\tdelete userData->cb;\n\t\t\t\t\tuserData->cb = nullptr;\n\t\t\t\t}\n\n\t\t\t\tliburing->ReleaseUserDataEntry(userData->idx);\n\t\t\t\tio_uring_cqe_seen(liburing->GetRing(), cqe);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// CQE for a zero-copy submission, a CQE notification will follow.\n\t\t\tif (cqe->flags & IORING_CQE_F_MORE)\n\t\t\t{\n\t\t\t\tif (cqe->res < 0)\n\t\t\t\t{\n\t\t\t\t\tif (userData->cb)\n\t\t\t\t\t{\n\t\t\t\t\t\t(*userData->cb)(false);\n\t\t\t\t\t\tdelete userData->cb;\n\t\t\t\t\t\tuserData->cb = nullptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// NOTE: Do not release the user data as it will be done upon reception\n\t\t\t\t// of CQE notification.\n\t\t\t\tio_uring_cqe_seen(liburing->GetRing(), cqe);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// Successfull SQE.\n\t\tif (cqe->res >= 0)\n\t\t{\n\t\t\tif (userData->cb)\n\t\t\t{\n\t\t\t\t(*userData->cb)(true);\n\t\t\t\tdelete userData->cb;\n\t\t\t\tuserData->cb = nullptr;\n\t\t\t}\n\t\t}\n\t\t// Failed SQE.\n\t\telse\n\t\t{\n\t\t\tif (userData->cb)\n\t\t\t{\n\t\t\t\t(*userData->cb)(false);\n\t\t\t\tdelete userData->cb;\n\t\t\t\tuserData->cb = nullptr;\n\t\t\t}\n\t\t}\n\n\t\tliburing->ReleaseUserDataEntry(userData->idx);\n\t\tio_uring_cqe_seen(liburing->GetRing(), cqe);\n\t}\n}\n\n/* Static class methods */\n\nvoid DepLibUring::ClassInit()\n{\n\tconst auto mayor = io_uring_major_version();\n\tconst auto minor = io_uring_minor_version();\n\n\tMS_DEBUG_TAG(info, \"io_uring version: \\\"%i.%i\\\"\", mayor, minor);\n\n\tif (Settings::configuration.disableLiburing)\n\t{\n\t\tMS_DEBUG_TAG(info, \"io_uring disabled by user settings\");\n\n\t\treturn;\n\t}\n\n\t// This must be called first.\n\tif (DepLibUring::CheckRuntimeSupport())\n\t{\n\t\ttry\n\t\t{\n\t\t\tDepLibUring::liburing = new LibUring();\n\n\t\t\tMS_DEBUG_TAG(info, \"io_uring enabled\");\n\n\t\t\tDepLibUring::enabled = true;\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\tMS_DEBUG_TAG(info, \"io_uring initialization failed, io_uring not enabled\");\n\t\t}\n\t}\n\telse\n\t{\n\t\tMS_DEBUG_TAG(info, \"io_uring not enabled\");\n\t}\n}\n\nvoid DepLibUring::ClassDestroy()\n{\n\tMS_TRACE();\n\n\tdelete DepLibUring::liburing;\n}\n\nbool DepLibUring::CheckRuntimeSupport()\n{\n\tstruct utsname buffer{};\n\n\tconst auto err = uname(std::addressof(buffer));\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uname() failed: %s\", std::strerror(err));\n\t}\n\n\tMS_DEBUG_TAG(info, \"kernel version: %s\", buffer.version);\n\n\tauto* kernelMayorCstr = buffer.release;\n\tauto kernelMayorLong  = strtol(kernelMayorCstr, &kernelMayorCstr, 10);\n\n\t// liburing `sento` capabilities are supported for kernel versions greather\n\t// than or equal to 6.\n\tif (kernelMayorLong < 6)\n\t{\n\t\tMS_DEBUG_TAG(info, \"kernel doesn't support io_uring\");\n\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nbool DepLibUring::IsEnabled()\n{\n\treturn DepLibUring::enabled;\n}\n\nflatbuffers::Offset<FBS::LibUring::Dump> DepLibUring::FillBuffer(flatbuffers::FlatBufferBuilder& builder)\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\treturn DepLibUring::liburing->FillBuffer(builder);\n}\n\nvoid DepLibUring::StartPollingCQEs()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\tDepLibUring::liburing->StartPollingCQEs();\n}\n\nvoid DepLibUring::StopPollingCQEs()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\tDepLibUring::liburing->StopPollingCQEs();\n}\n\nuint8_t* DepLibUring::GetSendBuffer()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\treturn DepLibUring::liburing->GetSendBuffer();\n}\n\nbool DepLibUring::PrepareSend(\n  int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\treturn DepLibUring::liburing->PrepareSend(sockfd, data, len, addr, cb);\n}\n\nbool DepLibUring::PrepareWrite(\n  int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\treturn DepLibUring::liburing->PrepareWrite(sockfd, data1, len1, data2, len2, cb);\n}\n\nvoid DepLibUring::Submit()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\tDepLibUring::liburing->Submit();\n}\n\nvoid DepLibUring::SetActive()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\tDepLibUring::liburing->SetActive();\n}\n\nbool DepLibUring::IsActive()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepLibUring::enabled, \"io_uring not enabled\");\n\n\treturn DepLibUring::liburing->IsActive();\n}\n\n/* Instance methods. */\n\nDepLibUring::LibUring::LibUring()\n{\n\tMS_TRACE();\n\n\t/**\n\t * IORING_SETUP_SINGLE_ISSUER: A hint to the kernel that only a single task\n\t * (or thread) will submit requests, which is used for internal optimisations.\n\t */\n\n\tconst unsigned int flags = IORING_SETUP_SINGLE_ISSUER;\n\n\t// Initialize io_uring.\n\tauto err = io_uring_queue_init(DepLibUring::QueueDepth, std::addressof(this->ring), flags);\n\n\tif (err < 0)\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\tMS_THROW_ERROR(\"io_uring_queue_init() failed: %s\", std::strerror(error));\n\t}\n\n\t// Create an eventfd instance.\n\tthis->efd = eventfd(0, 0);\n\n\tif (this->efd < 0)\n\t{\n\t\tMS_THROW_ERROR(\"eventfd() failed: %s\", std::strerror(-this->efd));\n\t}\n\n\terr = io_uring_register_eventfd(std::addressof(this->ring), this->efd);\n\n\tif (err < 0)\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\tMS_THROW_ERROR(\"io_uring_register_eventfd() failed: %s\", std::strerror(error));\n\t}\n\n\t// Initialize available UserData entries.\n\tfor (size_t i{ 0 }; i < DepLibUring::QueueDepth; ++i)\n\t{\n\t\tthis->userDatas[i].store = this->sendBuffers[i];\n\t\tthis->availableUserDataEntries.push(i);\n\t}\n\n\t// Initialize iovecs.\n\tfor (size_t i{ 0 }; i < DepLibUring::QueueDepth; ++i)\n\t{\n\t\tthis->iovecs[i].iov_base = this->sendBuffers[i];\n\t\tthis->iovecs[i].iov_len  = DepLibUring::SendBufferSize;\n\t}\n\n\terr = io_uring_register_buffers(std::addressof(this->ring), this->iovecs, DepLibUring::QueueDepth);\n\n\tif (err < 0)\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\tif (error == ENOMEM)\n\t\t{\n\t\t\tthis->zeroCopyEnabled = false;\n\n\t\t\tstruct rlimit l = {};\n\n\t\t\tif (getrlimit(RLIMIT_MEMLOCK, std::addressof(l)) == -1)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(info, \"getrlimit() failed: %s\", std::strerror(errno));\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  info,\n\t\t\t\t  \"io_uring_register_buffers() failed due to low RLIMIT_MEMLOCK, disabling zero copy: %s\",\n\t\t\t\t  std::strerror(error));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  info,\n\t\t\t\t  \"io_uring_register_buffers() failed due to low RLIMIT_MEMLOCK (soft:%llu, hard:%llu), disabling zero copy: %s\",\n\t\t\t\t  static_cast<unsigned long long>(l.rlim_cur),\n\t\t\t\t  static_cast<unsigned long long>(l.rlim_max),\n\t\t\t\t  std::strerror(error));\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_THROW_ERROR(\"io_uring_register_buffers() failed: %s\", std::strerror(error));\n\t\t}\n\t}\n}\n\nDepLibUring::LibUring::~LibUring()\n{\n\tMS_TRACE();\n\n\t// Close the event file descriptor.\n\tconst auto err = close(this->efd);\n\n\tif (err != 0)\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\ttry\n\t\t{\n\t\t\tMS_ABORT(\"close() failed: %s\", std::strerror(error));\n\t\t}\n\t\tcatch (const std::exception& error) // NOLINT(bugprone-empty-catch)\n\t\t{\n\t\t\t// NOTE: This is to avoid a warning:\n\t\t\t// warning: ‘throw’ will always call ‘terminate’ [-Wterminate]\n\t\t}\n\t}\n\n\t// Close the ring.\n\tio_uring_queue_exit(std::addressof(this->ring));\n}\n\nflatbuffers::Offset<FBS::LibUring::Dump> DepLibUring::LibUring::FillBuffer(\n  flatbuffers::FlatBufferBuilder& builder) const\n{\n\tMS_TRACE();\n\n\treturn FBS::LibUring::CreateDump(\n\t  builder, this->sqeProcessCount, this->sqeMissCount, this->userDataMissCount);\n}\n\nvoid DepLibUring::LibUring::StartPollingCQEs()\n{\n\tMS_TRACE();\n\n\t// Watch the event file descriptor for completions.\n\tthis->uvHandle = new uv_poll_t;\n\n\tauto err = uv_poll_init(DepLibUV::GetLoop(), this->uvHandle, this->efd);\n\n\tif (err != 0)\n\t{\n\t\tdelete this->uvHandle;\n\n\t\tMS_THROW_ERROR(\"uv_poll_init() failed: %s\", uv_strerror(err));\n\t}\n\n\tthis->uvHandle->data = this;\n\n\terr = uv_poll_start(this->uvHandle, UV_READABLE, static_cast<uv_poll_cb>(onFdEvent));\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_poll_start() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid DepLibUring::LibUring::StopPollingCQEs()\n{\n\tMS_TRACE();\n\n\tthis->uvHandle->data = nullptr;\n\n\t// Stop polling the event file descriptor.\n\tconst auto err = uv_poll_stop(this->uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tMS_ABORT(\"uv_poll_stop() failed: %s\", uv_strerror(err));\n\t}\n\n\t// NOTE: Handles that wrap file descriptors are clossed immediately.\n\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseFd));\n}\n\nuint8_t* DepLibUring::LibUring::GetSendBuffer()\n{\n\tMS_TRACE();\n\n\tif (this->availableUserDataEntries.empty())\n\t{\n\t\tMS_DEBUG_DEV(\"no user data entry available\");\n\n\t\treturn nullptr;\n\t}\n\n\tauto idx = this->availableUserDataEntries.front();\n\n\treturn this->userDatas[idx].store;\n}\n\nbool DepLibUring::LibUring::PrepareSend(\n  int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tauto* userData = this->GetUserData();\n\n\tif (!userData)\n\t{\n\t\tMS_DEBUG_DEV(\"no user data entry available\");\n\n\t\tthis->userDataMissCount++;\n\n\t\treturn false;\n\t}\n\n\tauto* sqe = io_uring_get_sqe(std::addressof(this->ring));\n\n\tif (!sqe)\n\t{\n\t\tMS_DEBUG_DEV(\"no sqe available\");\n\n\t\tthis->sqeMissCount++;\n\n\t\treturn false;\n\t}\n\n\t// The send data buffer belongs to us, no need to memcpy.\n\tif (this->IsDataInSendBuffers(data))\n\t{\n\t\tMS_ASSERT(data == userData->store, \"send buffer does not match userData store\");\n\t}\n\telse\n\t{\n\t\tstd::memcpy(userData->store, data, len);\n\t}\n\n\tuserData->cb = cb;\n\n\tio_uring_sqe_set_data(sqe, userData);\n\n\tconst socklen_t addrlen = Utils::IP::GetAddressLen(addr);\n\n\tif (this->zeroCopyEnabled)\n\t{\n\t\tauto iovec    = this->iovecs[userData->idx];\n\t\tiovec.iov_len = len;\n\n\t\tio_uring_prep_send_zc(sqe, sockfd, iovec.iov_base, iovec.iov_len, 0, 0);\n\t\tio_uring_prep_send_set_addr(sqe, addr, addrlen);\n\n\t\t// Tell io_uring that we are providing the already registered send buffer\n\t\t// for zero copy.\n\t\tsqe->ioprio |= IORING_RECVSEND_FIXED_BUF;\n\t\tsqe->buf_index = userData->idx;\n\t}\n\telse\n\t{\n\t\tio_uring_prep_sendto(sqe, sockfd, userData->store, len, 0, addr, addrlen);\n\t}\n\n\tthis->sqeProcessCount++;\n\n\treturn true;\n}\n\nbool DepLibUring::LibUring::PrepareWrite(\n  int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tauto* userData = this->GetUserData();\n\n\tif (!userData)\n\t{\n\t\tMS_DEBUG_DEV(\"no user data entry available\");\n\n\t\tthis->userDataMissCount++;\n\n\t\treturn false;\n\t}\n\n\tauto* sqe = io_uring_get_sqe(std::addressof(this->ring));\n\n\tif (!sqe)\n\t{\n\t\tMS_DEBUG_DEV(\"no sqe available\");\n\n\t\tthis->sqeMissCount++;\n\n\t\treturn false;\n\t}\n\n\t// The send data buffer belongs to us, no need to memcpy.\n\t// NOTE: data1 contains the TCP framing buffer and data2 the actual payload.\n\tif (this->IsDataInSendBuffers(data2))\n\t{\n\t\tMS_ASSERT(data2 == userData->store, \"send buffer does not match userData store\");\n\n\t\t// Always memcpy the frame len as it resides in the stack memory.\n\t\tstd::memcpy(userData->frameLen, data1, len1);\n\n\t\tuserData->iov[0].iov_base = userData->frameLen;\n\t\tuserData->iov[0].iov_len  = len1;\n\t\tuserData->iov[1].iov_base = userData->store;\n\t\tuserData->iov[1].iov_len  = len2;\n\t}\n\telse\n\t{\n\t\tstd::memcpy(userData->store, data1, len1);\n\t\tstd::memcpy(userData->store + len1, data2, len2);\n\n\t\tuserData->iov[0].iov_base = userData->store;\n\t\tuserData->iov[0].iov_len  = len1;\n\t\tuserData->iov[1].iov_base = userData->store + len1;\n\t\tuserData->iov[1].iov_len  = len2;\n\t}\n\n\tuserData->cb = cb;\n\n\tio_uring_sqe_set_data(sqe, userData);\n\n\tio_uring_prep_writev(sqe, sockfd, userData->iov, 2, 0);\n\n\tthis->sqeProcessCount++;\n\n\treturn true;\n}\n\nvoid DepLibUring::LibUring::Submit()\n{\n\tMS_TRACE();\n\n\t// Unset active flag.\n\tSetInactive();\n\n\tconst auto err = io_uring_submit(std::addressof(this->ring));\n\n\tif (err >= 0)\n\t{\n\t\tMS_DEBUG_DEV(\"%i submission queue entries submitted\", err);\n\t}\n\telse\n\t{\n\t\t// Get positive errno.\n\t\tconst int error = -err;\n\n\t\tMS_ERROR(\"io_uring_submit() failed: %s\", std::strerror(error));\n\t}\n}\n\nDepLibUring::UserData* DepLibUring::LibUring::GetUserData()\n{\n\tMS_TRACE();\n\n\tif (this->availableUserDataEntries.empty())\n\t{\n\t\treturn nullptr;\n\t}\n\n\tauto idx = this->availableUserDataEntries.front();\n\n\tthis->availableUserDataEntries.pop();\n\n\tauto* userData = std::addressof(this->userDatas[idx]);\n\tuserData->idx  = idx;\n\n\treturn userData;\n}\n"
  },
  {
    "path": "worker/src/DepLibWebRTC.cpp",
    "content": "#define MS_CLASS \"DepLibWebRTC\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepLibWebRTC.hpp\"\n#include \"Logger.hpp\"\n#include \"Settings.hpp\"\n#include \"system_wrappers/source/field_trial.h\" // webrtc::field_trial\n#include <mutex>\n\n/* Static. */\n\nstatic std::once_flag GlobalInitOnce;\n\n/* Static methods. */\n\nvoid DepLibWebRTC::ClassInit()\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_TAG(\n\t  info, \"libwebrtc field trials: \\\"%s\\\"\", Settings::configuration.libwebrtcFieldTrials.c_str());\n\n\tstd::call_once(\n\t  GlobalInitOnce,\n\t  []\n\t  {\n\t\t  webrtc::field_trial::InitFieldTrialsFromString(\n\t\t    Settings::configuration.libwebrtcFieldTrials.c_str());\n\t  });\n}\n\nvoid DepLibWebRTC::ClassDestroy()\n{\n\tMS_TRACE();\n}\n"
  },
  {
    "path": "worker/src/DepOpenSSL.cpp",
    "content": "#define MS_CLASS \"DepOpenSSL\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepOpenSSL.hpp\"\n#include \"Logger.hpp\"\n#include <openssl/crypto.h>\n#include <openssl/rand.h>\n#include <mutex>\n\n/* Static. */\n\nstatic std::once_flag GlobalInitOnce;\n\n/* Static methods. */\n\nvoid DepOpenSSL::ClassInit()\n{\n\tMS_TRACE();\n\n\tstd::call_once(\n\t  GlobalInitOnce,\n\t  []\n\t  {\n\t\t  MS_DEBUG_TAG(info, \"openssl version: \\\"%s\\\"\", OpenSSL_version(OPENSSL_VERSION));\n\t\t  MS_DEBUG_TAG(info, \"openssl CPU info: \\\"%s\\\"\", OpenSSL_version(OPENSSL_CPU_INFO));\n\n\t\t  // Initialize some crypto stuff.\n\t\t  RAND_poll();\n\t  });\n}\n"
  },
  {
    "path": "worker/src/DepUsrSCTP.cpp",
    "content": "#define MS_CLASS \"DepUsrSCTP\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"DepUsrSCTP.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include <usrsctp.h>\n#include <cstdio> // std::vsnprintf()\n#include <mutex>\n\n/* Static. */\n\nstatic constexpr size_t CheckerInterval{ 10u }; // In ms.\nstatic std::mutex GlobalSyncMutex;\nstatic size_t GlobalInstances{ 0u };\n\n/* Static methods for usrsctp global callbacks. */\n\ninline static int onSendSctpData(void* addr, void* data, size_t len, uint8_t /*tos*/, uint8_t /*setDf*/)\n{\n\tauto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast<uintptr_t>(addr));\n\n\tif (!sctpAssociation)\n\t{\n\t\tMS_WARN_TAG(sctp, \"no SctpAssociation found\");\n\n\t\treturn -1;\n\t}\n\n\tsctpAssociation->OnUsrSctpSendSctpData(data, len);\n\n\t// NOTE: Must not free data, usrsctp lib does it.\n\n\treturn 0;\n}\n\n// Static method for printing usrsctp debug.\ninline static void sctpDebug(const char* format, ...)\n{\n\tchar buffer[10000];\n\tva_list ap;\n\n\tva_start(ap, format);\n\tvsnprintf(buffer, sizeof(buffer), format, ap);\n\n\t// Remove the artificial carriage return set by usrsctp.\n\tbuffer[std::strlen(buffer) - 1] = '\\0';\n\n\tMS_DEBUG_TAG(sctp, \"%s\", buffer);\n\n\tva_end(ap);\n}\n\n/* Class variables. */\n\nthread_local DepUsrSCTP::Checker* DepUsrSCTP::checker{ nullptr };\nuint64_t DepUsrSCTP::numSctpAssociations{ 0u };\nuintptr_t DepUsrSCTP::nextSctpAssociationId{ 0u };\nabsl::flat_hash_map<uintptr_t, RTC::SctpAssociation*> DepUsrSCTP::mapIdSctpAssociation;\n\n/* Static methods. */\n\nvoid DepUsrSCTP::ClassInit()\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_TAG(info, \"usrsctp\");\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\tif (GlobalInstances == 0)\n\t{\n\t\tusrsctp_init_nothreads(0, onSendSctpData, sctpDebug);\n\n\t\t// See https://github.com/sctplab/usrsctp/blob/master/Manual.md#usrsctp_sysctl_set_sctp_sendspace.\n\t\t//\n\t\t// TODO: This doesn't have any effect. So let's comment it.\n\t\t// usrsctp_sysctl_set_sctp_sendspace(std::numeric_limits<uint32_t>::max());\n\t\t// usrsctp_sysctl_set_sctp_recvspace(std::numeric_limits<uint32_t>::max());\n\n\t\t// Disable explicit congestion notifications (ecn).\n\t\tusrsctp_sysctl_set_sctp_ecn_enable(0);\n\n#ifdef SCTP_DEBUG\n\t\tusrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);\n#endif\n\t}\n\n\t++GlobalInstances;\n}\n\nvoid DepUsrSCTP::ClassDestroy()\n{\n\tMS_TRACE();\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\t--GlobalInstances;\n\n\tif (GlobalInstances == 0)\n\t{\n\t\tusrsctp_finish();\n\n\t\tnumSctpAssociations   = 0u;\n\t\tnextSctpAssociationId = 0u;\n\n\t\tDepUsrSCTP::mapIdSctpAssociation.clear();\n\t}\n}\n\nvoid DepUsrSCTP::CreateChecker(SharedInterface* shared)\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepUsrSCTP::checker == nullptr, \"Checker already created\");\n\n\tDepUsrSCTP::checker = new DepUsrSCTP::Checker(shared);\n}\n\nvoid DepUsrSCTP::CloseChecker()\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(DepUsrSCTP::checker != nullptr, \"Checker not created\");\n\n\tdelete DepUsrSCTP::checker;\n}\n\nuintptr_t DepUsrSCTP::GetNextSctpAssociationId()\n{\n\tMS_TRACE();\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\t// NOTE: usrsctp_connect() fails with a value of 0.\n\tif (DepUsrSCTP::nextSctpAssociationId == 0u)\n\t{\n\t\t++DepUsrSCTP::nextSctpAssociationId;\n\t}\n\n\t// In case we've wrapped around and need to find an empty spot from a removed\n\t// SctpAssociation. Assumes we'll never be full.\n\twhile (DepUsrSCTP::mapIdSctpAssociation.find(DepUsrSCTP::nextSctpAssociationId) !=\n\t       DepUsrSCTP::mapIdSctpAssociation.end())\n\t{\n\t\t++DepUsrSCTP::nextSctpAssociationId;\n\n\t\tif (DepUsrSCTP::nextSctpAssociationId == 0u)\n\t\t{\n\t\t\t++DepUsrSCTP::nextSctpAssociationId;\n\t\t}\n\t}\n\n\treturn DepUsrSCTP::nextSctpAssociationId++;\n}\n\nvoid DepUsrSCTP::RegisterSctpAssociation(RTC::SctpAssociation* sctpAssociation)\n{\n\tMS_TRACE();\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\tMS_ASSERT(DepUsrSCTP::checker != nullptr, \"Checker not created\");\n\n\tauto it = DepUsrSCTP::mapIdSctpAssociation.find(sctpAssociation->id);\n\n\tMS_ASSERT(\n\t  it == DepUsrSCTP::mapIdSctpAssociation.end(),\n\t  \"the id of the SctpAssociation is already in the map\");\n\n\tDepUsrSCTP::mapIdSctpAssociation[sctpAssociation->id] = sctpAssociation;\n\n\tif (++DepUsrSCTP::numSctpAssociations == 1u)\n\t{\n\t\tDepUsrSCTP::checker->Start();\n\t}\n}\n\nvoid DepUsrSCTP::DeregisterSctpAssociation(RTC::SctpAssociation* sctpAssociation)\n{\n\tMS_TRACE();\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\tMS_ASSERT(DepUsrSCTP::checker != nullptr, \"Checker not created\");\n\n\tauto found = DepUsrSCTP::mapIdSctpAssociation.erase(sctpAssociation->id);\n\n\tMS_ASSERT(found > 0, \"SctpAssociation not found\");\n\tMS_ASSERT(DepUsrSCTP::numSctpAssociations > 0u, \"numSctpAssociations was not higher than 0\");\n\n\tif (--DepUsrSCTP::numSctpAssociations == 0u)\n\t{\n\t\tDepUsrSCTP::checker->Stop();\n\t}\n}\n\nRTC::SctpAssociation* DepUsrSCTP::RetrieveSctpAssociation(uintptr_t id)\n{\n\tMS_TRACE();\n\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\tauto it = DepUsrSCTP::mapIdSctpAssociation.find(id);\n\n\tif (it == DepUsrSCTP::mapIdSctpAssociation.end())\n\t{\n\t\treturn nullptr;\n\t}\n\n\treturn it->second;\n}\n\n/* DepUsrSCTP::Checker instance methods. */\n\nDepUsrSCTP::Checker::Checker(SharedInterface* shared) : timer(shared->CreateTimer(this))\n{\n\tMS_TRACE();\n}\n\nDepUsrSCTP::Checker::~Checker()\n{\n\tMS_TRACE();\n\n\tdelete this->timer;\n}\n\nvoid DepUsrSCTP::Checker::Start()\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_TAG(sctp, \"usrsctp periodic check started\");\n\n\tthis->lastCalledAtMs = 0u;\n\n\tthis->timer->Start(CheckerInterval, CheckerInterval);\n}\n\nvoid DepUsrSCTP::Checker::Stop()\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_TAG(sctp, \"usrsctp periodic check stopped\");\n\n\tthis->lastCalledAtMs = 0u;\n\n\tthis->timer->Stop();\n}\n\nvoid DepUsrSCTP::Checker::OnTimer(TimerHandleInterface* /*timer*/)\n{\n\tMS_TRACE();\n\n\tauto nowMs          = DepLibUV::GetTimeMs();\n\tconst int elapsedMs = this->lastCalledAtMs ? static_cast<int>(nowMs - this->lastCalledAtMs) : 0;\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\t// Activate liburing usage.\n\t\t// 'usrsctp_handle_timers()' will synchronously call the send/recv\n\t\t// callbacks for the pending data. If there are multiple messages to be\n\t\t// sent over the network then we will send those messages within a single\n\t\t// system call.\n\t\tDepLibUring::SetActive();\n\t}\n#endif\n\n\tusrsctp_handle_timers(elapsedMs);\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\t// Submit all prepared submission entries.\n\t\tDepLibUring::Submit();\n\t}\n#endif\n\n\tthis->lastCalledAtMs = nowMs;\n}\n"
  },
  {
    "path": "worker/src/Logger.cpp",
    "content": "#define MS_CLASS \"Logger\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include <uv.h>\n\n/* Class variables. */\n\nconst uint64_t Logger::Pid{ static_cast<uint64_t>(uv_os_getpid()) };\nthread_local Channel::ChannelSocket* Logger::channel{ nullptr };\nthread_local char Logger::buffer[Logger::BufferSize];\n\n/* Class methods. */\n\nvoid Logger::ClassInit(Channel::ChannelSocket* channel)\n{\n\tLogger::channel = channel;\n\n\tMS_TRACE();\n}\n"
  },
  {
    "path": "worker/src/MediaSoupErrors.cpp",
    "content": "#define MS_CLASS \"MediaSoupError\"\n\n#include \"MediaSoupErrors.hpp\"\n\n/* Class variables. */\n\nthread_local char MediaSoupError::buffer[MediaSoupError::BufferSize];\n"
  },
  {
    "path": "worker/src/RTC/ActiveSpeakerObserver.cpp",
    "content": "#define MS_CLASS \"RTC::ActiveSpeakerObserver\"\n\n#include \"RTC/ActiveSpeakerObserver.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint32_t C1{ 3u };\n\tstatic constexpr uint32_t C2{ 2u };\n\tstatic constexpr uint32_t C3{ 0u };\n\tstatic constexpr uint32_t N1{ 13u };\n\tstatic constexpr uint32_t N2{ 5u };\n\tstatic constexpr uint32_t N3{ 10u };\n\tstatic constexpr uint32_t LongCount{ 1u };\n\tstatic constexpr uint32_t LevelIdleTimeout{ 40u };\n\tstatic constexpr uint64_t SpeakerIdleTimeout{ 60 * 60 * 1000 };\n\tstatic constexpr uint32_t LongThreashold{ 4u };\n\tstatic constexpr uint32_t MaxLevel{ 127u };\n\tstatic constexpr uint32_t MinLevel{ 0u };\n\tstatic constexpr uint32_t MinLevelWindowLen{ 15 * 1000 / 20 };\n\tstatic constexpr uint32_t MediumThreshold{ 7u };\n\tstatic constexpr uint32_t SubunitLengthN1{ (MaxLevel - MinLevel + N1 - 1) / N1 };\n\tstatic constexpr uint32_t ImmediateBuffLen{ LongCount * N3 * N2 };\n\tstatic constexpr uint32_t MediumsBuffLen{ LongCount * N3 };\n\tstatic constexpr uint32_t LongsBuffLen{ LongCount };\n\tstatic constexpr uint32_t LevelsBuffLen{ LongCount * N3 * N2 };\n\tstatic constexpr double MinActivityScore{ 0.0000000001 };\n\n\tstatic inline int64_t binomialCoefficient(int32_t n, int32_t r)\n\t{\n\t\tconst int32_t m = n - r;\n\n\t\tr = std::max(r, m);\n\n\t\tint64_t t{ 1 };\n\n\t\tfor (int64_t i = n, j = 1; i > r; i--, ++j)\n\t\t{\n\t\t\tt = t * i / j;\n\t\t}\n\n\t\treturn t;\n\t}\n\n\tstatic inline double computeActivityScore(\n\t  const uint8_t vL, const uint32_t nR, const double p, const double lambda)\n\t{\n\t\tdouble activityScore = std::log(binomialCoefficient(nR, vL)) + (vL * std::log(p)) +\n\t\t                       ((nR - vL) * std::log(1 - p)) - std::log(lambda) + (lambda * vL);\n\n\t\tactivityScore = std::max(activityScore, MinActivityScore);\n\n\t\treturn activityScore;\n\t}\n\n\tstatic inline bool computeBigs(\n\t  const std::vector<uint8_t>& littles, std::vector<uint8_t>& bigs, uint8_t threashold)\n\t{\n\t\tconst uint32_t littleLen       = littles.size();\n\t\tconst uint32_t bigLen          = bigs.size();\n\t\tconst uint32_t littleLenPerBig = littleLen / bigLen;\n\t\tbool changed{ false };\n\n\t\tfor (uint32_t b = 0u, l = 0u; b < bigLen; ++b)\n\t\t{\n\t\t\tuint8_t sum{ 0u };\n\n\t\t\tfor (const uint32_t lEnd = l + littleLenPerBig; l < lEnd; ++l)\n\t\t\t{\n\t\t\t\tif (littles[l] > threashold)\n\t\t\t\t{\n\t\t\t\t\t++sum;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (bigs[b] != sum)\n\t\t\t{\n\t\t\t\tbigs[b] = sum;\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t}\n\n\t\treturn changed;\n\t}\n\n\tActiveSpeakerObserver::ActiveSpeakerObserver(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::RtpObserver::Listener* listener,\n\t  const FBS::ActiveSpeakerObserver::ActiveSpeakerObserverOptions* options)\n\t  : RTC::RtpObserver(shared, id, listener), interval(options->interval())\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->interval < 100)\n\t\t{\n\t\t\tthis->interval = 100;\n\t\t}\n\t\telse if (this->interval > 5000)\n\t\t{\n\t\t\tthis->interval = 5000;\n\t\t}\n\n\t\tthis->periodicTimer = this->shared->CreateTimer(this);\n\n\t\tthis->periodicTimer->Start(interval, interval);\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tActiveSpeakerObserver::~ActiveSpeakerObserver()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->periodicTimer;\n\n\t\t// Must clear all entries in this->mapProducerSpeakers since RemoveProducer()\n\t\t// won't be called for each existing Producer if the ActiveSpeakerObserver or\n\t\t// its parent Router are directly closed.\n\t\tfor (auto& kv : this->mapProducerSpeakers)\n\t\t{\n\t\t\tauto* producerSpeaker = kv.second;\n\t\t\tdelete producerSpeaker;\n\t\t}\n\t\tthis->mapProducerSpeakers.clear();\n\t}\n\n\tvoid ActiveSpeakerObserver::AddProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (producer->GetKind() != RTC::Media::Kind::AUDIO)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"not an audio Producer\");\n\t\t}\n\n\t\tif (this->mapProducerSpeakers.find(producer->id) != this->mapProducerSpeakers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Producer already in map\");\n\t\t}\n\n\t\tthis->mapProducerSpeakers[producer->id] = new ProducerSpeaker(this->shared, producer);\n\t}\n\n\tvoid ActiveSpeakerObserver::RemoveProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapProducerSpeakers.find(producer->id);\n\n\t\tif (it == this->mapProducerSpeakers.end())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* producerSpeaker = it->second;\n\n\t\tdelete producerSpeaker;\n\n\t\tthis->mapProducerSpeakers.erase(producer->id);\n\n\t\tif (producer->id == this->dominantId)\n\t\t{\n\t\t\tthis->dominantId.erase();\n\n\t\t\tUpdate();\n\t\t}\n\t}\n\n\tvoid ActiveSpeakerObserver::ProducerPaused(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapProducerSpeakers.find(producer->id);\n\n\t\tif (it != this->mapProducerSpeakers.end())\n\t\t{\n\t\t\tauto* producerSpeaker = it->second;\n\n\t\t\tproducerSpeaker->speaker->paused = true;\n\t\t}\n\t}\n\n\tvoid ActiveSpeakerObserver::ProducerResumed(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapProducerSpeakers.find(producer->id);\n\n\t\tif (it != this->mapProducerSpeakers.end())\n\t\t{\n\t\t\tauto* producerSpeaker = it->second;\n\n\t\t\tproducerSpeaker->speaker->paused = false;\n\t\t}\n\t}\n\n\tvoid ActiveSpeakerObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (IsPaused())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tuint8_t level{ 0 };\n\t\tbool voice{ false };\n\n\t\tif (!packet->ReadSsrcAudioLevel(level, voice))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t volume = 127 - level;\n\n\t\tauto it = this->mapProducerSpeakers.find(producer->id);\n\n\t\tif (it != this->mapProducerSpeakers.end())\n\t\t{\n\t\t\tauto* producerSpeaker = it->second;\n\t\t\tconst uint64_t now    = this->shared->GetTimeMs();\n\n\t\t\tproducerSpeaker->speaker->LevelChanged(volume, now);\n\t\t}\n\t}\n\n\tvoid ActiveSpeakerObserver::Paused()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->periodicTimer->Stop();\n\t}\n\n\tvoid ActiveSpeakerObserver::Resumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->periodicTimer->Restart();\n\t}\n\n\tvoid ActiveSpeakerObserver::OnTimer(TimerHandleInterface* /*timer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tUpdate();\n\t}\n\n\tvoid ActiveSpeakerObserver::Update()\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint64_t now = this->shared->GetTimeMs();\n\n\t\tif (now - this->lastLevelIdleTime >= LevelIdleTimeout)\n\t\t{\n\t\t\tif (this->lastLevelIdleTime != 0)\n\t\t\t{\n\t\t\t\tTimeoutIdleLevels(now);\n\t\t\t}\n\n\t\t\tthis->lastLevelIdleTime = now;\n\t\t}\n\n\t\tif (!this->mapProducerSpeakers.empty() && CalculateActiveSpeaker())\n\t\t{\n\t\t\tauto notification = FBS::ActiveSpeakerObserver::CreateDominantSpeakerNotificationDirect(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), this->dominantId.c_str());\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id,\n\t\t\t  FBS::Notification::Event::ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER,\n\t\t\t  FBS::Notification::Body::ActiveSpeakerObserver_DominantSpeakerNotification,\n\t\t\t  notification);\n\t\t}\n\t}\n\n\tbool ActiveSpeakerObserver::CalculateActiveSpeaker()\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::string newDominantId;\n\t\tconst int32_t speakerCount = this->mapProducerSpeakers.size();\n\n\t\tif (speakerCount == 0)\n\t\t{\n\t\t\tnewDominantId = \"\";\n\t\t}\n\t\telse if (speakerCount == 1)\n\t\t{\n\t\t\tauto it               = this->mapProducerSpeakers.begin();\n\t\t\tauto* producerSpeaker = it->second;\n\t\t\tnewDominantId         = producerSpeaker->producer->id;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSpeaker* dominantSpeaker = (this->dominantId.empty())\n\t\t\t                             ? nullptr\n\t\t\t                             : this->mapProducerSpeakers.at(this->dominantId)->speaker;\n\n\t\t\tif (dominantSpeaker == nullptr)\n\t\t\t{\n\t\t\t\tauto it               = this->mapProducerSpeakers.begin();\n\t\t\t\tnewDominantId         = it->first;\n\t\t\t\tauto* producerSpeaker = it->second;\n\t\t\t\tdominantSpeaker       = producerSpeaker->speaker;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnewDominantId = \"\";\n\t\t\t}\n\n\t\t\tdominantSpeaker->EvalActivityScores();\n\t\t\tdouble newDominantC2 = C2;\n\n\t\t\tfor (auto& kv : this->mapProducerSpeakers)\n\t\t\t{\n\t\t\t\tauto* producerSpeaker = kv.second;\n\t\t\t\tauto* speaker         = producerSpeaker->speaker;\n\t\t\t\tconst auto& id        = producerSpeaker->producer->id;\n\n\t\t\t\tif (id == this->dominantId || speaker->paused)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tspeaker->EvalActivityScores();\n\n\t\t\t\tfor (uint8_t interval = 0u; interval < ActiveSpeakerObserver::RelativeSpeachActivitiesLen;\n\t\t\t\t     ++interval)\n\t\t\t\t{\n\t\t\t\t\tthis->relativeSpeachActivities[interval] = std::log(\n\t\t\t\t\t  speaker->GetActivityScore(interval) / dominantSpeaker->GetActivityScore(interval));\n\t\t\t\t}\n\n\t\t\t\tconst double c1 = this->relativeSpeachActivities[0];\n\t\t\t\tconst double c2 = this->relativeSpeachActivities[1];\n\t\t\t\tconst double c3 = this->relativeSpeachActivities[2];\n\n\t\t\t\tif ((c1 > C1) && (c2 > C2) && (c3 > C3) && (c2 > newDominantC2))\n\t\t\t\t{\n\t\t\t\t\tnewDominantC2 = c2;\n\t\t\t\t\tnewDominantId = id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!newDominantId.empty() && newDominantId != this->dominantId)\n\t\t{\n\t\t\tthis->dominantId = newDominantId;\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tvoid ActiveSpeakerObserver::TimeoutIdleLevels(uint64_t now)\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto& kv : this->mapProducerSpeakers)\n\t\t{\n\t\t\tauto* producerSpeaker = kv.second;\n\t\t\tauto* speaker         = producerSpeaker->speaker;\n\t\t\tconst auto& id        = producerSpeaker->producer->id;\n\t\t\tconst uint64_t idle   = now - speaker->lastLevelChangeTime;\n\n\t\t\tif (SpeakerIdleTimeout < idle && (this->dominantId.empty() || id != this->dominantId))\n\t\t\t{\n\t\t\t\tspeaker->paused = true;\n\t\t\t}\n\t\t\telse if (LevelIdleTimeout < idle)\n\t\t\t{\n\t\t\t\tspeaker->LevelTimedOut(now);\n\t\t\t}\n\t\t}\n\t}\n\n\tActiveSpeakerObserver::ProducerSpeaker::ProducerSpeaker(SharedInterface* shared, RTC::Producer* producer)\n\t  : producer(producer), speaker(new Speaker(shared))\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->speaker->paused = producer->IsPaused();\n\t}\n\n\tActiveSpeakerObserver::ProducerSpeaker::~ProducerSpeaker()\n\t{\n\t\tMS_TRACE();\n\n\t\tdelete this->speaker;\n\t}\n\n\tActiveSpeakerObserver::Speaker::Speaker(SharedInterface* shared)\n\t  : immediateActivityScore(MinActivityScore),\n\t    mediumActivityScore(MinActivityScore),\n\t    longActivityScore(MinActivityScore),\n\t    lastLevelChangeTime(shared->GetTimeMs()),\n\t    minLevel(MinLevel),\n\t    nextMinLevel(MinLevel),\n\t    immediates(ImmediateBuffLen, 0),\n\t    mediums(MediumsBuffLen, 0),\n\t    longs(LongsBuffLen, 0),\n\t    levels(LevelsBuffLen, 0)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::EvalActivityScores()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (ComputeImmediates())\n\t\t{\n\t\t\tEvalImmediateActivityScore();\n\n\t\t\tif (ComputeMediums())\n\t\t\t{\n\t\t\t\tEvalMediumActivityScore();\n\n\t\t\t\tif (ComputeLongs())\n\t\t\t\t{\n\t\t\t\t\tEvalLongActivityScore();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdouble ActiveSpeakerObserver::Speaker::GetActivityScore(uint8_t interval) const\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (interval)\n\t\t{\n\t\t\tcase 0u:\n\t\t\t\treturn this->immediateActivityScore;\n\t\t\tcase 1u:\n\t\t\t\treturn this->mediumActivityScore;\n\t\t\tcase 2u:\n\t\t\t\treturn this->longActivityScore;\n\t\t\tdefault:\n\t\t\t\tMS_ABORT(\"interval is invalid\");\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::LevelChanged(uint32_t level, uint64_t now)\n\t{\n\t\tif (this->lastLevelChangeTime <= now)\n\t\t{\n\t\t\tconst uint64_t elapsed = now - this->lastLevelChangeTime;\n\n\t\t\tthis->lastLevelChangeTime = now;\n\n\t\t\tint8_t b{ 0 };\n\n\t\t\tif (level < MinLevel)\n\t\t\t{\n\t\t\t\tb = MinLevel;\n\t\t\t}\n\t\t\telse if (level > MaxLevel)\n\t\t\t{\n\t\t\t\tb = MaxLevel;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tb = level;\n\t\t\t}\n\n\t\t\t// The algorithm expect to have an update every 20 milliseconds. If the\n\t\t\t// Producer is paused, using a different packetization time or using DTX\n\t\t\t// we need to update more than one sample when receiving an audio packet.\n\t\t\tconst uint32_t intervalsUpdated =\n\t\t\t  std::min(std::max(static_cast<uint32_t>(elapsed / 20), 1U), LevelsBuffLen);\n\n\t\t\tfor (uint32_t i{ 0u }; i < intervalsUpdated; ++i)\n\t\t\t{\n\t\t\t\tthis->levels[this->nextLevelIndex] = b;\n\t\t\t\tthis->nextLevelIndex               = (this->nextLevelIndex + 1) % LevelsBuffLen;\n\t\t\t}\n\n\t\t\tUpdateMinLevel(b);\n\t\t}\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::LevelTimedOut(uint64_t now)\n\t{\n\t\tMS_TRACE();\n\n\t\tLevelChanged(MinLevel, now);\n\t}\n\n\tbool ActiveSpeakerObserver::Speaker::ComputeImmediates()\n\t{\n\t\tMS_TRACE();\n\n\t\tconst int8_t minLevel = this->minLevel + SubunitLengthN1;\n\t\tbool changed          = false;\n\n\t\tfor (uint32_t i = 0; i < ImmediateBuffLen; ++i)\n\t\t{\n\t\t\t// this->levels is a circular buffer where new samples are written in the\n\t\t\t// next vector index. this->immediates is a buffer where the most recent\n\t\t\t// value is always in index 0.\n\t\t\tconst size_t levelIndex = this->nextLevelIndex >= (i + 1)\n\t\t\t                            ? this->nextLevelIndex - i - 1\n\t\t\t                            : this->nextLevelIndex + LevelsBuffLen - i - 1;\n\t\t\tuint8_t level           = this->levels[levelIndex];\n\n\t\t\tif (level < minLevel)\n\t\t\t{\n\t\t\t\tlevel = MinLevel;\n\t\t\t}\n\n\t\t\tconst uint8_t immediate = (level / SubunitLengthN1);\n\n\t\t\tif (this->immediates[i] != immediate)\n\t\t\t{\n\t\t\t\tthis->immediates[i] = immediate;\n\t\t\t\tchanged             = true;\n\t\t\t}\n\t\t}\n\n\t\treturn changed;\n\t}\n\n\tbool ActiveSpeakerObserver::Speaker::ComputeMediums()\n\t{\n\t\tMS_TRACE();\n\n\t\treturn computeBigs(this->immediates, this->mediums, MediumThreshold);\n\t}\n\n\tbool ActiveSpeakerObserver::Speaker::ComputeLongs()\n\t{\n\t\tMS_TRACE();\n\n\t\treturn computeBigs(this->mediums, this->longs, LongThreashold);\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::EvalImmediateActivityScore()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->immediateActivityScore = computeActivityScore(this->immediates[0], N1, 0.5, 0.78);\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::EvalMediumActivityScore()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->mediumActivityScore = computeActivityScore(this->mediums[0], N2, 0.5, 24);\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::EvalLongActivityScore()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->longActivityScore = computeActivityScore(this->longs[0], N3, 0.5, 47);\n\t}\n\n\tvoid ActiveSpeakerObserver::Speaker::UpdateMinLevel(int8_t level)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (level == MinLevel)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif ((this->minLevel == MinLevel) || (this->minLevel > level))\n\t\t{\n\t\t\tthis->minLevel              = level;\n\t\t\tthis->nextMinLevel          = MinLevel;\n\t\t\tthis->nextMinLevelWindowLen = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (this->nextMinLevel == MinLevel)\n\t\t\t{\n\t\t\t\tthis->nextMinLevel          = level;\n\t\t\t\tthis->nextMinLevelWindowLen = 1;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->nextMinLevel = std::min<int>(this->nextMinLevel, level);\n\n\t\t\t\tthis->nextMinLevelWindowLen++;\n\n\t\t\t\tif (this->nextMinLevelWindowLen >= MinLevelWindowLen)\n\t\t\t\t{\n\t\t\t\t\tdouble newMinLevel = std::sqrt(static_cast<double>(this->minLevel * this->nextMinLevel));\n\n\t\t\t\t\tif (newMinLevel < MinLevel)\n\t\t\t\t\t{\n\t\t\t\t\t\tnewMinLevel = MinLevel;\n\t\t\t\t\t}\n\t\t\t\t\telse if (newMinLevel > MaxLevel)\n\t\t\t\t\t{\n\t\t\t\t\t\tnewMinLevel = MaxLevel;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->minLevel              = static_cast<int8_t>(newMinLevel);\n\t\t\t\t\tthis->nextMinLevel          = MinLevel;\n\t\t\t\t\tthis->nextMinLevelWindowLen = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/AudioLevelObserver.cpp",
    "content": "#define MS_CLASS \"RTC::AudioLevelObserver\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/AudioLevelObserver.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <absl/container/btree_map.h>\n#include <cmath> // std::lround()\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tAudioLevelObserver::AudioLevelObserver(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::RtpObserver::Listener* listener,\n\t  const FBS::AudioLevelObserver::AudioLevelObserverOptions* options)\n\t  : RTC::RtpObserver(shared, id, listener)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->maxEntries = options->maxEntries();\n\t\tthis->threshold  = options->threshold();\n\t\tthis->interval   = options->interval();\n\n\t\tif (this->threshold > 0)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid threshold value %\" PRIi8, this->threshold);\n\t\t}\n\n\t\tif (this->interval < 250)\n\t\t{\n\t\t\tthis->interval = 250;\n\t\t}\n\t\telse if (this->interval > 5000)\n\t\t{\n\t\t\tthis->interval = 5000;\n\t\t}\n\n\t\tthis->periodicTimer = this->shared->CreateTimer(this);\n\n\t\tthis->periodicTimer->Start(this->interval, this->interval);\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tAudioLevelObserver::~AudioLevelObserver()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->periodicTimer;\n\t}\n\n\tvoid AudioLevelObserver::AddProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (producer->GetKind() != RTC::Media::Kind::AUDIO)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"not an audio Producer\");\n\t\t}\n\n\t\t// Insert into the map.\n\t\tthis->mapProducerDBovs[producer];\n\t}\n\n\tvoid AudioLevelObserver::RemoveProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove from the map.\n\t\tthis->mapProducerDBovs.erase(producer);\n\t}\n\n\tvoid AudioLevelObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (IsPaused())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tuint8_t volume{ 0 };\n\t\tbool voice{ false };\n\n\t\tif (!packet->ReadSsrcAudioLevel(volume, voice))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto& dBovs = this->mapProducerDBovs.at(producer);\n\n\t\tdBovs.totalSum += volume;\n\t\tdBovs.count++;\n\t}\n\n\tvoid AudioLevelObserver::ProducerPaused(RTC::Producer* producer)\n\t{\n\t\t// Remove from the map.\n\t\tthis->mapProducerDBovs.erase(producer);\n\t}\n\n\tvoid AudioLevelObserver::ProducerResumed(RTC::Producer* producer)\n\t{\n\t\t// Insert into the map.\n\t\tthis->mapProducerDBovs[producer];\n\t}\n\n\tvoid AudioLevelObserver::Paused()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->periodicTimer->Stop();\n\n\t\tResetMapProducerDBovs();\n\n\t\tif (!this->silence)\n\t\t{\n\t\t\tthis->silence = true;\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id, FBS::Notification::Event::AUDIOLEVELOBSERVER_SILENCE);\n\t\t}\n\t}\n\n\tvoid AudioLevelObserver::Resumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->periodicTimer->Restart();\n\t}\n\n\tvoid AudioLevelObserver::Update()\n\t{\n\t\tMS_TRACE();\n\n\t\tabsl::btree_multimap<int8_t, RTC::Producer*> mapDBovsProducer;\n\n\t\tfor (auto& kv : this->mapProducerDBovs)\n\t\t{\n\t\t\tauto* producer = kv.first;\n\t\t\tauto& dBovs    = kv.second;\n\n\t\t\tif (dBovs.count < 10)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tauto avgDBov = -1 * static_cast<int8_t>(std::lround(dBovs.totalSum / dBovs.count));\n\n\t\t\tif (avgDBov >= this->threshold)\n\t\t\t{\n\t\t\t\tmapDBovsProducer.insert({ avgDBov, producer });\n\t\t\t}\n\t\t}\n\n\t\t// Clear the map.\n\t\tResetMapProducerDBovs();\n\n\t\tif (!mapDBovsProducer.empty())\n\t\t{\n\t\t\tthis->silence = false;\n\n\t\t\tuint16_t idx{ 0 };\n\t\t\tauto rit = mapDBovsProducer.crbegin();\n\n\t\t\tstd::vector<flatbuffers::Offset<FBS::AudioLevelObserver::Volume>> volumes;\n\n\t\t\tfor (; idx < this->maxEntries && rit != mapDBovsProducer.crend(); ++idx, ++rit)\n\t\t\t{\n\t\t\t\tvolumes.emplace_back(\n\t\t\t\t  FBS::AudioLevelObserver::CreateVolumeDirect(\n\t\t\t\t    this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t\t    rit->second->id.c_str(),\n\t\t\t\t    rit->first));\n\t\t\t}\n\n\t\t\tauto notification = FBS::AudioLevelObserver::CreateVolumesNotificationDirect(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), &volumes);\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id,\n\t\t\t  FBS::Notification::Event::AUDIOLEVELOBSERVER_VOLUMES,\n\t\t\t  FBS::Notification::Body::AudioLevelObserver_VolumesNotification,\n\t\t\t  notification);\n\t\t}\n\t\telse if (!this->silence)\n\t\t{\n\t\t\tthis->silence = true;\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id, FBS::Notification::Event::AUDIOLEVELOBSERVER_SILENCE);\n\t\t}\n\t}\n\n\tvoid AudioLevelObserver::ResetMapProducerDBovs()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto& kv : this->mapProducerDBovs)\n\t\t{\n\t\t\tauto& dBovs = kv.second;\n\n\t\t\tdBovs.totalSum = 0;\n\t\t\tdBovs.count    = 0;\n\t\t}\n\t}\n\n\tinline void AudioLevelObserver::OnTimer(TimerHandleInterface* /*timer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tUpdate();\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/Consumer.cpp",
    "content": "#define MS_CLASS \"RTC::Consumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Consumer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tConsumer::Consumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& producerId,\n\t  Listener* listener,\n\t  const FBS::Transport::ConsumeRequest* data,\n\t  RTC::RtpParameters::Type type)\n\t  : id(id),\n\t    producerId(producerId),\n\t    shared(shared),\n\t    listener(listener),\n\t    kind(RTC::Media::Kind(data->kind())),\n\t    type(type)\n\t{\n\t\tMS_TRACE();\n\n\t\t// This may throw.\n\t\tthis->rtpParameters = RTC::RtpParameters(data->rtpParameters());\n\n\t\tif (this->rtpParameters.encodings.empty())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"empty rtpParameters.encodings\");\n\t\t}\n\n\t\t// All encodings must have SSRCs.\n\t\tfor (auto& encoding : this->rtpParameters.encodings)\n\t\t{\n\t\t\tif (encoding.ssrc == 0)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid encoding in rtpParameters (missing ssrc)\");\n\t\t\t}\n\t\t\telse if (encoding.hasRtx && encoding.rtx.ssrc == 0)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid encoding in rtpParameters (missing rtx.ssrc)\");\n\t\t\t}\n\t\t}\n\n\t\tif (data->consumableRtpEncodings()->size() == 0)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"empty consumableRtpEncodings\");\n\t\t}\n\n\t\tthis->consumableRtpEncodings.reserve(data->consumableRtpEncodings()->size());\n\n\t\tfor (size_t i{ 0 }; i < data->consumableRtpEncodings()->size(); ++i)\n\t\t{\n\t\t\tconst auto* entry = data->consumableRtpEncodings()->Get(i);\n\n\t\t\t// This may throw due the constructor of RTC::RtpEncodingParameters.\n\t\t\tthis->consumableRtpEncodings.emplace_back(entry);\n\n\t\t\t// Verify that it has ssrc field.\n\t\t\tauto& encoding = this->consumableRtpEncodings[i];\n\n\t\t\tif (encoding.ssrc == 0u)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"wrong encoding in consumableRtpEncodings (missing ssrc)\");\n\t\t\t}\n\t\t}\n\n\t\t// Fill RTP header extension ids and their mapped values.\n\t\t// This may throw.\n\t\tfor (auto& exten : this->rtpParameters.headerExtensions)\n\t\t{\n\t\t\tif (exten.id == 0u)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"RTP extension id cannot be 0\");\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.mid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.mid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.rid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.rid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.rrid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.rrid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.absSendTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.absSendTime = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.transportWideCc01 == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.transportWideCc01 = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.ssrcAudioLevel == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.ssrcAudioLevel = exten.id;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t  this->rtpHeaderExtensionIds.dependencyDescriptor == 0u &&\n\t\t\t  exten.type == RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.dependencyDescriptor = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.videoOrientation = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.videoOrientation = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.absCaptureTime = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.playoutDelay == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.playoutDelay = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.mediasoupPacketId == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.mediasoupPacketId = exten.id;\n\t\t\t}\n\t\t}\n\n\t\t// paused is set to false by default.\n\t\tthis->paused = data->paused();\n\n\t\t// Fill supported codec payload types.\n\t\tfor (auto& codec : this->rtpParameters.codecs)\n\t\t{\n\t\t\tif (codec.mimeType.IsMediaCodec())\n\t\t\t{\n\t\t\t\tthis->supportedCodecPayloadTypes[codec.payloadType] = true;\n\t\t\t}\n\t\t}\n\n\t\t// Fill media SSRCs vector.\n\t\tfor (auto& encoding : this->rtpParameters.encodings)\n\t\t{\n\t\t\tthis->mediaSsrcs.push_back(encoding.ssrc);\n\t\t}\n\n\t\t// Fill RTX SSRCs vector.\n\t\tfor (auto& encoding : this->rtpParameters.encodings)\n\t\t{\n\t\t\tif (encoding.hasRtx)\n\t\t\t{\n\t\t\t\tthis->rtxSsrcs.push_back(encoding.rtx.ssrc);\n\t\t\t}\n\t\t}\n\n\t\t// Set the RTCP report generation interval.\n\t\tif (this->kind == RTC::Media::Kind::AUDIO)\n\t\t{\n\t\t\tthis->maxRtcpInterval = RTC::RTCP::MaxAudioIntervalMs;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs;\n\t\t}\n\t}\n\n\tConsumer::~Consumer()\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::BaseConsumerDump> Consumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add rtpParameters.\n\t\tauto rtpParameters = this->rtpParameters.FillBuffer(builder);\n\n\t\t// Add consumableRtpEncodings.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters>> consumableRtpEncodings;\n\t\tconsumableRtpEncodings.reserve(this->consumableRtpEncodings.size());\n\n\t\tfor (const auto& encoding : this->consumableRtpEncodings)\n\t\t{\n\t\t\tconsumableRtpEncodings.emplace_back(encoding.FillBuffer(builder));\n\t\t}\n\n\t\t// Add supportedCodecPayloadTypes.\n\t\tstd::vector<uint8_t> supportedCodecPayloadTypes;\n\n\t\tfor (auto i = 0; i < 128; ++i)\n\t\t{\n\t\t\tif (this->supportedCodecPayloadTypes[i])\n\t\t\t{\n\t\t\t\tsupportedCodecPayloadTypes.push_back(i);\n\t\t\t}\n\t\t}\n\n\t\t// Add traceEventTypes.\n\t\tstd::vector<FBS::Consumer::TraceEventType> traceEventTypes;\n\n\t\tif (this->traceEventTypes.rtp)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Consumer::TraceEventType::RTP);\n\t\t}\n\t\tif (this->traceEventTypes.keyframe)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Consumer::TraceEventType::KEYFRAME);\n\t\t}\n\t\tif (this->traceEventTypes.nack)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Consumer::TraceEventType::NACK);\n\t\t}\n\t\tif (this->traceEventTypes.pli)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Consumer::TraceEventType::PLI);\n\t\t}\n\t\tif (this->traceEventTypes.fir)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Consumer::TraceEventType::FIR);\n\t\t}\n\n\t\treturn FBS::Consumer::CreateBaseConsumerDumpDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  RTC::RtpParameters::TypeToFbs(this->type),\n\t\t  this->producerId.c_str(),\n\t\t  this->kind == RTC::Media::Kind::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO\n\t\t                                        : FBS::RtpParameters::MediaKind::VIDEO,\n\t\t  rtpParameters,\n\t\t  &consumableRtpEncodings,\n\t\t  &supportedCodecPayloadTypes,\n\t\t  &traceEventTypes,\n\t\t  this->paused,\n\t\t  this->producerPaused,\n\t\t  this->priority);\n\t}\n\n\tvoid Consumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_PAUSE:\n\t\t\t{\n\t\t\t\tif (this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst bool wasActive = IsActive();\n\n\t\t\t\tthis->paused = true;\n\n\t\t\t\tMS_DEBUG_DEV(\"Consumer paused [consumerId:%s]\", this->id.c_str());\n\n\t\t\t\tif (wasActive)\n\t\t\t\t{\n\t\t\t\t\tUserOnPaused();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_RESUME:\n\t\t\t{\n\t\t\t\tif (!this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis->paused = false;\n\n\t\t\t\tMS_DEBUG_DEV(\"Consumer resumed [consumerId:%s]\", this->id.c_str());\n\n\t\t\t\tif (IsActive())\n\t\t\t\t{\n\t\t\t\t\tUserOnResumed();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_SET_PRIORITY:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Consumer::SetPriorityRequest>();\n\n\t\t\t\tif (body->priority() < 1u)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"wrong priority (must be higher than 0)\");\n\t\t\t\t}\n\n\t\t\t\tthis->priority = body->priority();\n\n\t\t\t\tauto responseOffset =\n\t\t\t\t  FBS::Consumer::CreateSetPriorityResponse(request->GetBufferBuilder(), this->priority);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_SetPriorityResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_ENABLE_TRACE_EVENT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Consumer::EnableTraceEventRequest>();\n\n\t\t\t\t// Reset traceEventTypes.\n\t\t\t\tstruct TraceEventTypes newTraceEventTypes;\n\n\t\t\t\tfor (const auto& type : *body->events())\n\t\t\t\t{\n\t\t\t\t\tswitch (type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase FBS::Consumer::TraceEventType::KEYFRAME:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.keyframe = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Consumer::TraceEventType::FIR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.fir = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Consumer::TraceEventType::NACK:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.nack = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Consumer::TraceEventType::PLI:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.pli = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Consumer::TraceEventType::RTP:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.rtp = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->traceEventTypes = newTraceEventTypes;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Consumer::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->transportConnected)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->transportConnected = true;\n\n\t\tMS_DEBUG_DEV(\"Transport connected [consumerId:%s]\", this->id.c_str());\n\n\t\tUserOnTransportConnected();\n\t}\n\n\tvoid Consumer::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->transportConnected)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->transportConnected = false;\n\n\t\tMS_DEBUG_DEV(\"Transport disconnected [consumerId:%s]\", this->id.c_str());\n\n\t\tUserOnTransportDisconnected();\n\t}\n\n\tvoid Consumer::ProducerPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->producerPaused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst bool wasActive = IsActive();\n\n\t\tthis->producerPaused = true;\n\n\t\tMS_DEBUG_DEV(\"Producer paused [consumerId:%s]\", this->id.c_str());\n\n\t\tif (wasActive)\n\t\t{\n\t\t\tUserOnPaused();\n\t\t}\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE);\n\t}\n\n\tvoid Consumer::ProducerResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->producerPaused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->producerPaused = false;\n\n\t\tMS_DEBUG_DEV(\"Producer resumed [consumerId:%s]\", this->id.c_str());\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tUserOnResumed();\n\t\t}\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME);\n\t}\n\n\tvoid Consumer::ProducerRtpStreamScores(const std::vector<uint8_t>* scores)\n\t{\n\t\tMS_TRACE();\n\n\t\t// This is gonna be a constant pointer.\n\t\tthis->producerRtpStreamScores = scores;\n\t}\n\n\t// The caller (Router) is supposed to proceed with the deletion of this Consumer\n\t// right after calling this method. Otherwise ugly things may happen.\n\tvoid Consumer::ProducerClosed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->producerClosed = true;\n\n\t\tMS_DEBUG_DEV(\"Producer closed [consumerId:%s]\", this->id.c_str());\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE);\n\n\t\tthis->listener->OnConsumerProducerClosed(this);\n\t}\n\n\tvoid Consumer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->traceEventTypes.keyframe && packet->IsKeyFrame())\n\t\t{\n\t\t\tauto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\t\tauto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx);\n\n\t\t\tauto notification = FBS::Consumer::CreateTraceNotification(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  FBS::Consumer::TraceEventType::KEYFRAME,\n\t\t\t  this->shared->GetTimeMs(),\n\t\t\t  FBS::Common::TraceDirection::DIRECTION_OUT,\n\t\t\t  FBS::Consumer::TraceInfo::KeyFrameTraceInfo,\n\t\t\t  traceInfo.Union());\n\n\t\t\tEmitTraceEvent(notification);\n\t\t}\n\t\telse if (this->traceEventTypes.rtp)\n\t\t{\n\t\t\tauto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\t\tauto traceInfo = FBS::Consumer::CreateRtpTraceInfo(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx);\n\n\t\t\tauto notification = FBS::Consumer::CreateTraceNotification(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  FBS::Consumer::TraceEventType::RTP,\n\t\t\t  this->shared->GetTimeMs(),\n\t\t\t  FBS::Common::TraceDirection::DIRECTION_OUT,\n\t\t\t  FBS::Consumer::TraceInfo::RtpTraceInfo,\n\t\t\t  traceInfo.Union());\n\n\t\t\tEmitTraceEvent(notification);\n\t\t}\n\t}\n\n\tvoid Consumer::EmitTraceEventPliType(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.pli)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Consumer::CreatePliTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc);\n\n\t\tauto notification = FBS::Consumer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Consumer::TraceEventType::PLI,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_IN,\n\t\t  FBS::Consumer::TraceInfo::PliTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tvoid Consumer::EmitTraceEventFirType(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.fir)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Consumer::CreateFirTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc);\n\n\t\tauto notification = FBS::Consumer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Consumer::TraceEventType::FIR,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_IN,\n\t\t  FBS::Consumer::TraceInfo::FirTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tvoid Consumer::EmitTraceEventNackType() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.nack)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto notification = FBS::Consumer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Consumer::TraceEventType::NACK,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_IN);\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tvoid Consumer::EmitTraceEvent(flatbuffers::Offset<FBS::Consumer::TraceNotification>& notification) const\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_TRACE,\n\t\t  FBS::Notification::Body::Consumer_TraceNotification,\n\t\t  notification);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/DataConsumer.cpp",
    "content": "#define MS_CLASS \"RTC::DataConsumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/DataConsumer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tDataConsumer::DataConsumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& dataProducerId,\n\t  RTC::DataConsumer::Listener* listener,\n\t  const FBS::Transport::ConsumeDataRequest* data,\n\t  size_t maxMessageSize)\n\t  : id(id),\n\t    dataProducerId(dataProducerId),\n\t    shared(shared),\n\t    listener(listener),\n\t    maxMessageSize(maxMessageSize)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (data->type())\n\t\t{\n\t\t\tcase FBS::DataProducer::Type::SCTP:\n\t\t\t{\n\t\t\t\tthis->type = DataConsumer::Type::SCTP;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase FBS::DataProducer::Type::DIRECT:\n\t\t\t{\n\t\t\t\tthis->type = DataConsumer::Type::DIRECT;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (this->type == DataConsumer::Type::SCTP)\n\t\t{\n\t\t\tif (!flatbuffers::IsFieldPresent(\n\t\t\t      data, FBS::Transport::ConsumeDataRequest::VT_SCTPSTREAMPARAMETERS))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"missing sctpStreamParameters\");\n\t\t\t}\n\n\t\t\t// This may throw.\n\t\t\tthis->sctpStreamParameters = RTC::SctpStreamParameters(data->sctpStreamParameters());\n\t\t}\n\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_LABEL))\n\t\t{\n\t\t\tthis->label = data->label()->str();\n\t\t}\n\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_PROTOCOL))\n\t\t{\n\t\t\tthis->protocol = data->protocol()->str();\n\t\t}\n\n\t\t// paused is set to false by default.\n\t\tthis->paused = data->paused();\n\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_SUBCHANNELS))\n\t\t{\n\t\t\tfor (const auto subchannel : *data->subchannels())\n\t\t\t{\n\t\t\t\tthis->subchannels.insert(subchannel);\n\t\t\t}\n\t\t}\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tDataConsumer::~DataConsumer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\t}\n\n\tflatbuffers::Offset<FBS::DataConsumer::DumpResponse> DataConsumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpStreamParameters> sctpStreamParameters;\n\n\t\t// Add sctpStreamParameters.\n\t\tif (this->type == DataConsumer::Type::SCTP)\n\t\t{\n\t\t\tsctpStreamParameters = this->sctpStreamParameters.FillBuffer(builder);\n\t\t}\n\n\t\tstd::vector<uint16_t> subchannels;\n\n\t\tsubchannels.reserve(this->subchannels.size());\n\n\t\tfor (auto subchannel : this->subchannels)\n\t\t{\n\t\t\tsubchannels.emplace_back(subchannel);\n\t\t}\n\n\t\treturn FBS::DataConsumer::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  this->dataProducerId.c_str(),\n\t\t  this->type == DataConsumer::Type::SCTP ? FBS::DataProducer::Type::SCTP\n\t\t                                         : FBS::DataProducer::Type::DIRECT,\n\t\t  sctpStreamParameters,\n\t\t  this->label.c_str(),\n\t\t  this->protocol.c_str(),\n\t\t  this->bufferedAmountLowThreshold,\n\t\t  this->paused,\n\t\t  this->dataProducerPaused,\n\t\t  std::addressof(subchannels));\n\t}\n\n\tflatbuffers::Offset<FBS::DataConsumer::GetStatsResponse> DataConsumer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::DataConsumer::CreateGetStatsResponseDirect(\n\t\t  builder,\n\t\t  // timestamp.\n\t\t  this->shared->GetTimeMs(),\n\t\t  // label.\n\t\t  this->label.c_str(),\n\t\t  // protocol.\n\t\t  this->protocol.c_str(),\n\t\t  // messagesSent.\n\t\t  this->messagesSent,\n\t\t  // bytesSent.\n\t\t  this->bytesSent,\n\t\t  // bufferedAmount.\n\t\t  this->bufferedAmount);\n\t}\n\n\tvoid DataConsumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_PAUSE:\n\t\t\t{\n\t\t\t\tif (this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis->paused = true;\n\n\t\t\t\tMS_DEBUG_DEV(\"DataConsumer paused [dataConsumerId:%s]\", this->id.c_str());\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_RESUME:\n\t\t\t{\n\t\t\t\tif (!this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis->paused = false;\n\n\t\t\t\tMS_DEBUG_DEV(\"DataConsumer resumed [dataConsumerId:%s]\", this->id.c_str());\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_GET_BUFFERED_AMOUNT:\n\t\t\t{\n\t\t\t\tif (this->GetType() != RTC::DataConsumer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid DataConsumer type\");\n\t\t\t\t}\n\n\t\t\t\tuint32_t bufferedAmount{ 0 };\n\n\t\t\t\tthis->listener->OnDataConsumerNeedBufferedAmount(this, bufferedAmount);\n\n\t\t\t\tauto responseOffset = FBS::DataConsumer::CreateGetBufferedAmountResponse(\n\t\t\t\t  request->GetBufferBuilder(), bufferedAmount);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_GetBufferedAmountResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD:\n\t\t\t{\n\t\t\t\tif (this->GetType() != DataConsumer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid DataConsumer type\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body =\n\t\t\t\t  request->data->body_as<FBS::DataConsumer::SetBufferedAmountLowThresholdRequest>();\n\n\t\t\t\tthis->bufferedAmountLowThreshold = body->threshold();\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\t// There is less or same buffered data than the given threshold.\n\t\t\t\t// Trigger 'bufferedamountlow' now.\n\t\t\t\tif (this->bufferedAmount <= this->bufferedAmountLowThreshold)\n\t\t\t\t{\n\t\t\t\t\t// Notify the Node DataConsumer.\n\t\t\t\t\tauto bufferedAmountLowOffset = FBS::DataConsumer::CreateBufferedAmountLowNotification(\n\t\t\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), this->bufferedAmount);\n\n\t\t\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t\t\t  this->id,\n\t\t\t\t\t  FBS::Notification::Event::DATACONSUMER_BUFFERED_AMOUNT_LOW,\n\t\t\t\t\t  FBS::Notification::Body::DataConsumer_BufferedAmountLowNotification,\n\t\t\t\t\t  bufferedAmountLowOffset);\n\t\t\t\t}\n\t\t\t\t// Force the trigger of 'bufferedamountlow' once there is less or same\n\t\t\t\t// buffered data than the given threshold.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->forceTriggerBufferedAmountLow = true;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_SEND:\n\t\t\t{\n\t\t\t\tif (this->GetType() != RTC::DataConsumer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid DataConsumer type\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body    = request->data->body_as<FBS::DataConsumer::SendRequest>();\n\t\t\t\tconst uint8_t* data = body->data()->Data();\n\t\t\t\tconst size_t len    = body->data()->size();\n\n\t\t\t\tif (len > this->maxMessageSize)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]\",\n\t\t\t\t\t  this->maxMessageSize,\n\t\t\t\t\t  len);\n\t\t\t\t}\n\n\t\t\t\tconst auto* cb = new onQueuedCallback(\n\t\t\t\t  [&request](bool queued, bool sctpSendBufferFull)\n\t\t\t\t  {\n\t\t\t\t\t  if (queued)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  request->Accept();\n\t\t\t\t\t  }\n\t\t\t\t\t  else\n\t\t\t\t\t  {\n\t\t\t\t\t\t  request->Error(sctpSendBufferFull ? \"sctpsendbufferfull\" : \"message send failed\");\n\t\t\t\t\t  }\n\t\t\t\t  });\n\n\t\t\t\tstatic std::vector<uint16_t> emptySubchannels;\n\n\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t{\n\t\t\t\t\tconst uint16_t streamId =\n\t\t\t\t\t  this->type == DataConsumer::Type::SCTP ? this->sctpStreamParameters.streamId : 0;\n\n\t\t\t\t\t// NOTE: We are creating a copy of the data here, otherwise we cannot\n\t\t\t\t\t// move the Message and pass its ownership to the SCTP stack.\n\t\t\t\t\tRTC::SCTP::Message message(streamId, body->ppid(), std::vector<uint8_t>(data, data + len));\n\n\t\t\t\t\tSendMessage(std::move(message), emptySubchannels, std::nullopt, cb);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tSendMessage(data, len, body->ppid(), emptySubchannels, std::nullopt, cb);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_SET_SUBCHANNELS:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::DataConsumer::SetSubchannelsRequest>();\n\n\t\t\t\tthis->subchannels.clear();\n\n\t\t\t\tfor (const auto subchannel : *body->subchannels())\n\t\t\t\t{\n\t\t\t\t\tthis->subchannels.insert(subchannel);\n\t\t\t\t}\n\n\t\t\t\tstd::vector<uint16_t> subchannels;\n\n\t\t\t\tsubchannels.reserve(this->subchannels.size());\n\n\t\t\t\tfor (auto subchannel : this->subchannels)\n\t\t\t\t{\n\t\t\t\t\tsubchannels.emplace_back(subchannel);\n\t\t\t\t}\n\n\t\t\t\t// Create response.\n\t\t\t\tauto responseOffset = FBS::DataConsumer::CreateSetSubchannelsResponseDirect(\n\t\t\t\t  request->GetBufferBuilder(), std::addressof(subchannels));\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_SetSubchannelsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_ADD_SUBCHANNEL:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::DataConsumer::AddSubchannelRequest>();\n\n\t\t\t\tthis->subchannels.insert(body->subchannel());\n\n\t\t\t\tstd::vector<uint16_t> subchannels;\n\n\t\t\t\tsubchannels.reserve(this->subchannels.size());\n\n\t\t\t\tfor (auto subchannel : this->subchannels)\n\t\t\t\t{\n\t\t\t\t\tsubchannels.emplace_back(subchannel);\n\t\t\t\t}\n\n\t\t\t\t// Create response.\n\t\t\t\tauto responseOffset = FBS::DataConsumer::CreateAddSubchannelResponseDirect(\n\t\t\t\t  request->GetBufferBuilder(), std::addressof(subchannels));\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_AddSubchannelResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATACONSUMER_REMOVE_SUBCHANNEL:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::DataConsumer::RemoveSubchannelRequest>();\n\n\t\t\t\tthis->subchannels.erase(body->subchannel());\n\n\t\t\t\tstd::vector<uint16_t> subchannels;\n\n\t\t\t\tsubchannels.reserve(this->subchannels.size());\n\n\t\t\t\tfor (auto subchannel : this->subchannels)\n\t\t\t\t{\n\t\t\t\t\tsubchannels.emplace_back(subchannel);\n\t\t\t\t}\n\n\t\t\t\t// Create response.\n\t\t\t\tauto responseOffset = FBS::DataConsumer::CreateRemoveSubchannelResponseDirect(\n\t\t\t\t  request->GetBufferBuilder(), std::addressof(subchannels));\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_RemoveSubchannelResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid DataConsumer::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportConnected = true;\n\n\t\tMS_DEBUG_DEV(\"Transport connected [dataConsumerId:%s]\", this->id.c_str());\n\t}\n\n\tvoid DataConsumer::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportConnected = false;\n\n\t\tMS_DEBUG_DEV(\"Transport disconnected [dataConsumerId:%s]\", this->id.c_str());\n\t}\n\n\tvoid DataConsumer::DataProducerPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->dataProducerPaused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->dataProducerPaused = true;\n\n\t\tMS_DEBUG_DEV(\"DataProducer paused [dataConsumerId:%s]\", this->id.c_str());\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_PAUSE);\n\t}\n\n\tvoid DataConsumer::DataProducerResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->dataProducerPaused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->dataProducerPaused = false;\n\n\t\tMS_DEBUG_DEV(\"DataProducer resumed [dataConsumerId:%s]\", this->id.c_str());\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_RESUME);\n\t}\n\n\tvoid DataConsumer::SctpAssociationConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->sctpAssociationConnected = true;\n\n\t\tMS_DEBUG_DEV(\"SctpAssociation connected [dataConsumerId:%s]\", this->id.c_str());\n\t}\n\n\tvoid DataConsumer::SctpAssociationClosed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->sctpAssociationConnected = false;\n\n\t\tMS_DEBUG_DEV(\"SctpAssociation closed [dataConsumerId:%s]\", this->id.c_str());\n\t}\n\n\tvoid DataConsumer::SetSctpAssociationBufferedAmount(uint32_t bufferedAmount)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto previousBufferedAmount = this->bufferedAmount;\n\n\t\tthis->bufferedAmount = bufferedAmount;\n\n\t\tif (\n\t\t  (this->forceTriggerBufferedAmountLow ||\n\t\t   previousBufferedAmount > this->bufferedAmountLowThreshold) &&\n\t\t  this->bufferedAmount <= this->bufferedAmountLowThreshold)\n\t\t{\n\t\t\tthis->forceTriggerBufferedAmountLow = false;\n\n\t\t\t// Notify the Node DataConsumer.\n\t\t\tauto bufferedAmountLowOffset = FBS::DataConsumer::CreateBufferedAmountLowNotification(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), this->bufferedAmount);\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id,\n\t\t\t  FBS::Notification::Event::DATACONSUMER_BUFFERED_AMOUNT_LOW,\n\t\t\t  FBS::Notification::Body::DataConsumer_BufferedAmountLowNotification,\n\t\t\t  bufferedAmountLowOffset);\n\t\t}\n\t}\n\n\tvoid DataConsumer::SctpAssociationSendBufferFull()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::DATACONSUMER_SCTP_SENDBUFFER_FULL);\n\t}\n\n\t// The caller (Router) is supposed to proceed with the deletion of this DataConsumer\n\t// right after calling this method. Otherwise ugly things may happen.\n\tvoid DataConsumer::DataProducerClosed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->dataProducerClosed = true;\n\n\t\tMS_DEBUG_DEV(\"DataProducer closed [dataConsumerId:%s]\", this->id.c_str());\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_CLOSE);\n\n\t\tthis->listener->OnDataConsumerDataProducerClosed(this);\n\t}\n\n\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\tbool DataConsumer::SendMessage(\n\t  const uint8_t* msg,\n\t  size_t len,\n\t  uint32_t ppid,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel,\n\t  const onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// If a required subchannel is given, verify that this data consumer is\n\t\t// subscribed to it.\n\t\tif (\n\t\t  requiredSubchannel.has_value() &&\n\t\t  this->subchannels.find(requiredSubchannel.value()) == this->subchannels.end())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// If subchannels are given, verify that this data consumer is subscribed\n\t\t// to at least one of them.\n\t\tif (!subchannels.empty())\n\t\t{\n\t\t\tbool subchannelMatched{ false };\n\n\t\t\tfor (const auto subchannel : subchannels)\n\t\t\t{\n\t\t\t\tif (this->subchannels.find(subchannel) != this->subchannels.end())\n\t\t\t\t{\n\t\t\t\t\tsubchannelMatched = true;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!subchannelMatched)\n\t\t\t{\n\t\t\t\tif (cb)\n\t\t\t\t{\n\t\t\t\t\t(*cb)(false, false);\n\t\t\t\t\tdelete cb;\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tif (len > this->maxMessageSize)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  message,\n\t\t\t  \"given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]\",\n\t\t\t  len,\n\t\t\t  this->maxMessageSize);\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->messagesSent++;\n\t\tthis->bytesSent += len;\n\n\t\tthis->listener->OnDataConsumerSendMessage(this, msg, len, ppid, cb);\n\n\t\treturn true;\n\t}\n\n\tbool DataConsumer::SendMessage(\n\t  RTC::SCTP::Message message,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel,\n\t  const onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// If a required subchannel is given, verify that this data consumer is\n\t\t// subscribed to it.\n\t\tif (\n\t\t  requiredSubchannel.has_value() &&\n\t\t  this->subchannels.find(requiredSubchannel.value()) == this->subchannels.end())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// If subchannels are given, verify that this data consumer is subscribed\n\t\t// to at least one of them.\n\t\tif (!subchannels.empty())\n\t\t{\n\t\t\tbool subchannelMatched{ false };\n\n\t\t\tfor (const auto subchannel : subchannels)\n\t\t\t{\n\t\t\t\tif (this->subchannels.find(subchannel) != this->subchannels.end())\n\t\t\t\t{\n\t\t\t\t\tsubchannelMatched = true;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!subchannelMatched)\n\t\t\t{\n\t\t\t\tif (cb)\n\t\t\t\t{\n\t\t\t\t\t(*cb)(false, false);\n\t\t\t\t\tdelete cb;\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tconst size_t messageLen = message.GetPayloadLength();\n\n\t\tif (messageLen > this->maxMessageSize)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  message,\n\t\t\t  \"given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]\",\n\t\t\t  messageLen,\n\t\t\t  this->maxMessageSize);\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->messagesSent++;\n\t\tthis->bytesSent += messageLen;\n\n\t\tthis->listener->OnDataConsumerSendMessage(this, std::move(message), cb);\n\n\t\treturn true;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/DataProducer.cpp",
    "content": "#define MS_CLASS \"RTC::DataProducer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/DataProducer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tDataProducer::DataProducer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  size_t maxMessageSize,\n\t  RTC::DataProducer::Listener* listener,\n\t  const FBS::Transport::ProduceDataRequest* data)\n\t  : id(id), shared(shared), maxMessageSize(maxMessageSize), listener(listener)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (data->type())\n\t\t{\n\t\t\tcase FBS::DataProducer::Type::SCTP:\n\t\t\t{\n\t\t\t\tthis->type = DataProducer::Type::SCTP;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FBS::DataProducer::Type::DIRECT:\n\t\t\t{\n\t\t\t\tthis->type = DataProducer::Type::DIRECT;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (this->type == DataProducer::Type::SCTP)\n\t\t{\n\t\t\tif (!flatbuffers::IsFieldPresent(\n\t\t\t      data, FBS::Transport::ProduceDataRequest::VT_SCTPSTREAMPARAMETERS))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"missing sctpStreamParameters\");\n\t\t\t}\n\n\t\t\t// This may throw.\n\t\t\tthis->sctpStreamParameters = RTC::SctpStreamParameters(data->sctpStreamParameters());\n\t\t}\n\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ProduceDataRequest::VT_LABEL))\n\t\t{\n\t\t\tthis->label = data->label()->str();\n\t\t}\n\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ProduceDataRequest::VT_PROTOCOL))\n\t\t{\n\t\t\tthis->protocol = data->protocol()->str();\n\t\t}\n\n\t\tthis->paused = data->paused();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ this);\n\t}\n\n\tDataProducer::~DataProducer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\t}\n\n\tflatbuffers::Offset<FBS::DataProducer::DumpResponse> DataProducer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpStreamParameters> sctpStreamParametersOffset;\n\n\t\t// Add sctpStreamParameters.\n\t\tif (this->type == DataProducer::Type::SCTP)\n\t\t{\n\t\t\tsctpStreamParametersOffset = this->sctpStreamParameters.FillBuffer(builder);\n\t\t}\n\n\t\treturn FBS::DataProducer::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  this->type == DataProducer::Type::SCTP ? FBS::DataProducer::Type::SCTP\n\t\t                                         : FBS::DataProducer::Type::DIRECT,\n\t\t  sctpStreamParametersOffset,\n\t\t  this->label.c_str(),\n\t\t  this->protocol.c_str(),\n\t\t  this->paused);\n\t}\n\n\tflatbuffers::Offset<FBS::DataProducer::GetStatsResponse> DataProducer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::DataProducer::CreateGetStatsResponseDirect(\n\t\t  builder,\n\t\t  // timestamp.\n\t\t  this->shared->GetTimeMs(),\n\t\t  // label.\n\t\t  this->label.c_str(),\n\t\t  // protocol.\n\t\t  this->protocol.c_str(),\n\t\t  // messagesReceived.\n\t\t  this->messagesReceived,\n\t\t  // bytesReceived.\n\t\t  this->bytesReceived);\n\t}\n\n\tvoid DataProducer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::DATAPRODUCER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataProducer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATAPRODUCER_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataProducer_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATAPRODUCER_PAUSE:\n\t\t\t{\n\t\t\t\tif (this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis->paused = true;\n\n\t\t\t\tMS_DEBUG_DEV(\"DataProducer paused [dataProducerId:%s]\", this->id.c_str());\n\n\t\t\t\tthis->listener->OnDataProducerPaused(this);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::DATAPRODUCER_RESUME:\n\t\t\t{\n\t\t\t\tif (!this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tthis->paused = false;\n\n\t\t\t\tMS_DEBUG_DEV(\"DataProducer resumed [dataProducerId:%s]\", this->id.c_str());\n\n\t\t\t\tthis->listener->OnDataProducerResumed(this);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid DataProducer::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (notification->event)\n\t\t{\n\t\t\tcase Channel::ChannelNotification::Event::DATAPRODUCER_SEND:\n\t\t\t{\n\t\t\t\tconst auto* body    = notification->data->body_as<FBS::DataProducer::SendNotification>();\n\t\t\t\tconst uint8_t* data = body->data()->Data();\n\t\t\t\tconst size_t len    = body->data()->size();\n\n\t\t\t\tif (len > this->maxMessageSize)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]\",\n\t\t\t\t\t  this->maxMessageSize,\n\t\t\t\t\t  len);\n\t\t\t\t}\n\n\t\t\t\tstd::vector<uint16_t> subchannels;\n\n\t\t\t\tif (flatbuffers::IsFieldPresent(body, FBS::DataProducer::SendNotification::VT_SUBCHANNELS))\n\t\t\t\t{\n\t\t\t\t\tsubchannels.reserve(body->subchannels()->size());\n\n\t\t\t\t\tfor (const auto subchannel : *body->subchannels())\n\t\t\t\t\t{\n\t\t\t\t\t\tsubchannels.emplace_back(subchannel);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstd::optional<uint16_t> requiredSubchannel{ std::nullopt };\n\n\t\t\t\tif (body->requiredSubchannel().has_value())\n\t\t\t\t{\n\t\t\t\t\trequiredSubchannel = body->requiredSubchannel();\n\t\t\t\t}\n\n\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t{\n\t\t\t\t\tconst uint16_t streamId =\n\t\t\t\t\t  this->type == DataProducer::Type::SCTP ? this->sctpStreamParameters.streamId : 0;\n\n\t\t\t\t\t// NOTE: We are creating a copy of the data here, otherwise we cannot\n\t\t\t\t\t// move the Message and pass its ownership to the SCTP stack.\n\t\t\t\t\tRTC::SCTP::Message message(streamId, body->ppid(), std::vector<uint8_t>(data, data + len));\n\n\t\t\t\t\tReceiveMessage(std::move(message), subchannels, requiredSubchannel);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tReceiveMessage(data, len, body->ppid(), subchannels, requiredSubchannel);\n\t\t\t\t}\n\n\t\t\t\t// Increase receive transmission.\n\t\t\t\tthis->listener->OnDataProducerReceiveData(this, len);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ERROR(\"unknown event '%s'\", notification->eventCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\tvoid DataProducer::ReceiveMessage(\n\t  const uint8_t* msg,\n\t  size_t len,\n\t  uint32_t ppid,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->messagesReceived++;\n\t\tthis->bytesReceived += len;\n\n\t\t// If paused stop here.\n\t\tif (this->paused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->listener->OnDataProducerMessageReceived(\n\t\t  this, msg, len, ppid, subchannels, requiredSubchannel);\n\t}\n\n\tvoid DataProducer::ReceiveMessage(\n\t  RTC::SCTP::Message message,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->messagesReceived++;\n\t\tthis->bytesReceived += message.GetPayloadLength();\n\n\t\t// If paused stop here.\n\t\tif (this->paused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->listener->OnDataProducerMessageReceived(\n\t\t  this, std::move(message), subchannels, requiredSubchannel);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/DirectTransport.cpp",
    "content": "#define MS_CLASS \"RTC::DirectTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/DirectTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/Consts.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\n\tDirectTransport::DirectTransport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  const FBS::DirectTransport::DirectTransportOptions* options)\n\t  : RTC::Transport::Transport(shared, id, listener, options->base())\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ this);\n\t}\n\n\tDirectTransport::~DirectTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell the Transport parent class that we are about to destroy\n\t\t// the class instance.\n\t\tSetDestroying();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\t}\n\n\tflatbuffers::Offset<FBS::DirectTransport::DumpResponse> DirectTransport::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\t// Add base transport dump.\n\t\tauto base = Transport::FillBuffer(builder);\n\n\t\treturn FBS::DirectTransport::CreateDumpResponse(builder, base);\n\t}\n\n\tflatbuffers::Offset<FBS::DirectTransport::GetStatsResponse> DirectTransport::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Base Transport stats.\n\t\tauto base = Transport::FillBufferStats(builder);\n\n\t\treturn FBS::DirectTransport::CreateGetStatsResponse(builder, base);\n\t}\n\n\tvoid DirectTransport::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DirectTransport_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DirectTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Transport::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid DirectTransport::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (notification->event)\n\t\t{\n\t\t\tcase Channel::ChannelNotification::Event::TRANSPORT_SEND_RTCP:\n\t\t\t{\n\t\t\t\tconst auto* body = notification->data->body_as<FBS::Transport::SendRtcpNotification>();\n\t\t\t\tauto len         = body->data()->size();\n\n\t\t\t\t// Increase receive transmission.\n\t\t\t\tRTC::Transport::DataReceived(len);\n\n\t\t\t\tif (len > RTC::Consts::MtuSize + 100)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"given RTCP packet exceeds maximum size [len:%i]\", len);\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* packet = RTC::RTCP::Packet::Parse(body->data()->data(), len);\n\n\t\t\t\tif (!packet)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"received data is not a valid RTCP compound or single packet\");\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Pass the packet to the parent transport.\n\t\t\t\tRTC::Transport::ReceiveRtcpPacket(packet);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Transport::HandleNotification(notification);\n\t\t\t}\n\t\t}\n\t}\n\n\tinline bool DirectTransport::IsConnected() const\n\t{\n\t\treturn true;\n\t}\n\n\tvoid DirectTransport::SendRtpPacket(\n\t  RTC::Consumer* consumer, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!consumer)\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"cannot send RTP packet not associated to a Consumer\");\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(\n\t\t  packet->GetBuffer(), packet->GetLength());\n\n\t\tauto notification = FBS::Consumer::CreateRtpNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), data);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  consumer->id,\n\t\t  FBS::Notification::Event::CONSUMER_RTP,\n\t\t  FBS::Notification::Body::Consumer_RtpNotification,\n\t\t  notification);\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true);\n\t\t\tdelete cb;\n\t\t}\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(packet->GetLength());\n\t}\n\n\tvoid DirectTransport::SendRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Notify the Node DirectTransport.\n\t\tconst auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(\n\t\t  packet->GetData(), packet->GetSize());\n\n\t\tauto notification = FBS::DirectTransport::CreateRtcpNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), data);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::DIRECTTRANSPORT_RTCP,\n\t\t  FBS::Notification::Body::DirectTransport_RtcpNotification,\n\t\t  notification);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(packet->GetSize());\n\t}\n\n\tvoid DirectTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\tconst auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(\n\t\t  packet->GetData(), packet->GetSize());\n\n\t\tauto notification = FBS::DirectTransport::CreateRtcpNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), data);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::DIRECTTRANSPORT_RTCP,\n\t\t  FBS::Notification::Body::DirectTransport_RtcpNotification,\n\t\t  notification);\n\t}\n\n\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tvoid DirectTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Notify the Node DirectTransport.\n\t\tauto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(msg, len);\n\n\t\tauto notification = FBS::DataConsumer::CreateMessageNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), ppid, data);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  dataConsumer->id,\n\t\t  FBS::Notification::Event::DATACONSUMER_MESSAGE,\n\t\t  FBS::Notification::Body::DataConsumer_MessageNotification,\n\t\t  notification);\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true, false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid DirectTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Notify the Node DirectTransport.\n\t\tauto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(\n\t\t  message.GetPayload().data(), message.GetPayloadLength());\n\n\t\tauto notification = FBS::DataConsumer::CreateMessageNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), message.GetPayloadProtocolId(), data);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  dataConsumer->id,\n\t\t  FBS::Notification::Event::DATACONSUMER_MESSAGE,\n\t\t  FBS::Notification::Body::DataConsumer_MessageNotification,\n\t\t  notification);\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true, false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(message.GetPayloadLength());\n\t}\n\n\tbool DirectTransport::SendData(const uint8_t* /*data*/, size_t /*len*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\n\t\treturn false;\n\t}\n\n\tvoid DirectTransport::RecvStreamClosed(uint32_t /*ssrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tvoid DirectTransport::SendStreamClosed(uint32_t /*ssrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/DtlsTransport.cpp",
    "content": "#define MS_CLASS \"RTC::DtlsTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/DtlsTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include <openssl/err.h>\n#include <openssl/evp.h>\n#include <uv.h>\n#include <cstdio>  // std::snprintf(), std::fopen()\n#include <cstring> // std::memcpy(), std::strcmp()\n\n// clang-format off\n// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)\n#define LOG_OPENSSL_ERROR(desc) \\\n\tdo \\\n\t{ \\\n\t\tif (ERR_peek_error() == 0) \\\n\t\t{ \\\n\t\t\tMS_ERROR(\"OpenSSL error [desc:'%s']\", desc); \\\n\t\t} \\\n\t\telse \\\n\t\t{ \\\n\t\t\tint64_t err; \\\n\t\t\twhile ((err = ERR_get_error()) != 0) \\\n\t\t\t{ \\\n\t\t\t\tMS_ERROR(\"OpenSSL error [desc:'%s', error:'%s']\", desc, ERR_error_string(err, nullptr)); \\\n\t\t\t} \\\n\t\t\tERR_clear_error(); \\\n\t\t} \\\n\t} while (false)\n// clang-format on\n\n/* Static methods for OpenSSL callbacks. */\n\ninline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/)\n{\n\tMS_TRACE();\n\n\t// Always valid since DTLS certificates are self-signed.\n\treturn 1;\n}\n\ninline static void onSslInfo(const SSL* ssl, int where, int ret)\n{\n\tstatic_cast<RTC::DtlsTransport*>(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret);\n}\n\n/**\n * This callback is called by OpenSSL when it wants to send DTLS data to the\n * endpoint. Such a data could be a full DTLS message, various DTLS messages,\n * a DTLS message fragment, various DTLS message fragments or a combination of\n * these. It's guaranteed (by observation) that |len| argument corresponds to\n * the entire content of our BIO mem buffer |this->sslBioToNetwork| and it\n * never exceeds our |DtlsMtu| limit.\n */\ninline static long onSslBioOut(\n  BIO* bio,\n  int operationType,\n  const char* argp,\n  size_t len,\n  int /*argi*/,\n  long /*argl*/,\n  int ret,\n  size_t* /*processed*/)\n{\n\tconst long resultOfcallback = (operationType == BIO_CB_RETURN) ? static_cast<long>(ret) : 1;\n\n\t// This callback is called twice for write operations:\n\t// - First one with operationType = BIO_CB_WRITE.\n\t// - Second one with operationType = BIO_CB_RETURN | BIO_CB_WRITE.\n\t// We only care about the former.\n\tif ((operationType == BIO_CB_WRITE) && argp && len > 0)\n\t{\n\t\tauto* dtlsTransport = reinterpret_cast<RTC::DtlsTransport*>(BIO_get_callback_arg(bio));\n\n\t\tdtlsTransport->SendDtlsData(reinterpret_cast<const uint8_t*>(argp), len);\n\t}\n\n\treturn resultOfcallback;\n}\n\ninline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs)\n{\n\tif (timerUs == 0u)\n\t{\n\t\treturn 100000u;\n\t}\n\telse if (timerUs >= 4000000u)\n\t{\n\t\treturn 4000000u;\n\t}\n\telse\n\t{\n\t\treturn 2 * timerUs;\n\t}\n}\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr int DtlsMtu{ 1350 };\n\tstatic constexpr int SslReadBufferSize{ 65536 };\n\t// AES-HMAC: http://tools.ietf.org/html/rfc3711\n\tstatic constexpr size_t SrtpMasterKeyLength{ 16u };\n\tstatic constexpr size_t SrtpMasterSaltLength{ 14u };\n\tstatic constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength };\n\t// AES-GCM: http://tools.ietf.org/html/rfc7714\n\tstatic constexpr size_t SrtpAesGcm256MasterKeyLength{ 32u };\n\tstatic constexpr size_t SrtpAesGcm256MasterSaltLength{ 12u };\n\tstatic constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength +\n\t\t                                                 SrtpAesGcm256MasterSaltLength };\n\tstatic constexpr size_t SrtpAesGcm128MasterKeyLength{ 16u };\n\tstatic constexpr size_t SrtpAesGcm128MasterSaltLength{ 12u };\n\tstatic constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength +\n\t\t                                                 SrtpAesGcm128MasterSaltLength };\n\n\t/* Class variables. */\n\n\tthread_local X509* DtlsTransport::certificate{ nullptr };\n\tthread_local EVP_PKEY* DtlsTransport::privateKey{ nullptr };\n\tthread_local SSL_CTX* DtlsTransport::sslCtx{ nullptr };\n\tthread_local uint8_t DtlsTransport::sslReadBuffer[SslReadBufferSize];\n\tconst absl::flat_hash_map<std::string, DtlsTransport::FingerprintAlgorithm>\n\t  DtlsTransport::String2FingerprintAlgorithm = {\n\t\t  { \"sha-1\",   DtlsTransport::FingerprintAlgorithm::SHA1   },\n\t\t  { \"sha-224\", DtlsTransport::FingerprintAlgorithm::SHA224 },\n\t\t  { \"sha-256\", DtlsTransport::FingerprintAlgorithm::SHA256 },\n\t\t  { \"sha-384\", DtlsTransport::FingerprintAlgorithm::SHA384 },\n\t\t  { \"sha-512\", DtlsTransport::FingerprintAlgorithm::SHA512 }\n  };\n\tconst absl::flat_hash_map<DtlsTransport::FingerprintAlgorithm, std::string>\n\t  DtlsTransport::FingerprintAlgorithm2String = {\n\t\t  { DtlsTransport::FingerprintAlgorithm::SHA1,   \"sha-1\"   },\n\t\t  { DtlsTransport::FingerprintAlgorithm::SHA224, \"sha-224\" },\n\t\t  { DtlsTransport::FingerprintAlgorithm::SHA256, \"sha-256\" },\n\t\t  { DtlsTransport::FingerprintAlgorithm::SHA384, \"sha-384\" },\n\t\t  { DtlsTransport::FingerprintAlgorithm::SHA512, \"sha-512\" }\n  };\n\tconst absl::flat_hash_map<std::string, DtlsTransport::Role> DtlsTransport::String2Role = {\n\t\t{ \"auto\",   DtlsTransport::Role::AUTO   },\n\t\t{ \"client\", DtlsTransport::Role::CLIENT },\n\t\t{ \"server\", DtlsTransport::Role::SERVER }\n\t};\n\tthread_local std::vector<DtlsTransport::Fingerprint> DtlsTransport::localFingerprints;\n\tconst std::vector<DtlsTransport::SrtpCryptoSuiteMapEntry> DtlsTransport::SrtpCryptoSuites = {\n\t\t{ RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM,        \"SRTP_AEAD_AES_256_GCM\"  },\n\t\t{ RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM,        \"SRTP_AEAD_AES_128_GCM\"  },\n\t\t{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, \"SRTP_AES128_CM_SHA1_80\" },\n\t\t{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, \"SRTP_AES128_CM_SHA1_32\" }\n\t};\n\n\t/* Class methods. */\n\n\tvoid DtlsTransport::ClassInit()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Generate a X509 certificate and private key (unless PEM files are provided).\n\t\tif (\n\t\t  Settings::configuration.dtlsCertificateFile.empty() ||\n\t\t  Settings::configuration.dtlsPrivateKeyFile.empty())\n\t\t{\n\t\t\tGenerateCertificateAndPrivateKey();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tReadCertificateAndPrivateKeyFromFiles();\n\t\t}\n\n\t\t// Create a global SSL_CTX.\n\t\tCreateSslCtx();\n\n\t\t// Generate certificate fingerprints.\n\t\tGenerateFingerprints();\n\t}\n\n\tvoid DtlsTransport::ClassDestroy()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (DtlsTransport::privateKey)\n\t\t{\n\t\t\tEVP_PKEY_free(DtlsTransport::privateKey);\n\t\t}\n\n\t\tif (DtlsTransport::certificate)\n\t\t{\n\t\t\tX509_free(DtlsTransport::certificate);\n\t\t}\n\n\t\tif (DtlsTransport::sslCtx)\n\t\t{\n\t\t\tSSL_CTX_free(DtlsTransport::sslCtx);\n\t\t}\n\t}\n\n\tDtlsTransport::Role DtlsTransport::RoleFromFbs(FBS::WebRtcTransport::DtlsRole role)\n\t{\n\t\tswitch (role)\n\t\t{\n\t\t\tcase FBS::WebRtcTransport::DtlsRole::AUTO:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::Role::AUTO;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::DtlsRole::CLIENT:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::Role::CLIENT;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::DtlsRole::SERVER:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::Role::SERVER;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tFBS::WebRtcTransport::DtlsRole DtlsTransport::RoleToFbs(DtlsTransport::Role role)\n\t{\n\t\tswitch (role)\n\t\t{\n\t\t\tcase DtlsTransport::Role::AUTO:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsRole::AUTO;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::Role::CLIENT:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsRole::CLIENT;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::Role::SERVER:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsRole::SERVER;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tFBS::WebRtcTransport::DtlsState DtlsTransport::StateToFbs(DtlsTransport::DtlsState state)\n\t{\n\t\tswitch (state)\n\t\t{\n\t\t\tcase DtlsTransport::DtlsState::NEW:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsState::NEW;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::DtlsState::CONNECTING:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsState::CONNECTING;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::DtlsState::CONNECTED:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsState::CONNECTED;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::DtlsState::FAILED:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsState::FAILED;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::DtlsState::CLOSED:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::DtlsState::CLOSED;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tDtlsTransport::FingerprintAlgorithm DtlsTransport::AlgorithmFromFbs(\n\t  FBS::WebRtcTransport::FingerprintAlgorithm algorithm)\n\t{\n\t\tswitch (algorithm)\n\t\t{\n\t\t\tcase FBS::WebRtcTransport::FingerprintAlgorithm::SHA1:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::FingerprintAlgorithm::SHA1;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::FingerprintAlgorithm::SHA224:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::FingerprintAlgorithm::SHA224;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::FingerprintAlgorithm::SHA256:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::FingerprintAlgorithm::SHA256;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::FingerprintAlgorithm::SHA384:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::FingerprintAlgorithm::SHA384;\n\t\t\t}\n\n\t\t\tcase FBS::WebRtcTransport::FingerprintAlgorithm::SHA512:\n\t\t\t{\n\t\t\t\treturn DtlsTransport::FingerprintAlgorithm::SHA512;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tFBS::WebRtcTransport::FingerprintAlgorithm DtlsTransport::AlgorithmToFbs(\n\t  DtlsTransport::FingerprintAlgorithm algorithm)\n\t{\n\t\tswitch (algorithm)\n\t\t{\n\t\t\tcase DtlsTransport::FingerprintAlgorithm::SHA1:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::FingerprintAlgorithm::SHA1;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::FingerprintAlgorithm::SHA224:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::FingerprintAlgorithm::SHA224;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::FingerprintAlgorithm::SHA256:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::FingerprintAlgorithm::SHA256;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::FingerprintAlgorithm::SHA384:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::FingerprintAlgorithm::SHA384;\n\t\t\t}\n\n\t\t\tcase DtlsTransport::FingerprintAlgorithm::SHA512:\n\t\t\t{\n\t\t\t\treturn FBS::WebRtcTransport::FingerprintAlgorithm::SHA512;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tvoid DtlsTransport::GenerateCertificateAndPrivateKey()\n\t{\n\t\tMS_TRACE();\n\n\t\tint ret{ 0 };\n\t\tX509_NAME* certName{ nullptr };\n\t\tconst std::string subject =\n\t\t  std::string(\"mediasoup\") +\n\t\t  std::to_string(Utils::Crypto::GetRandomUInt<uint32_t>(100000, 999999));\n\n\t\t// Create key with curve.\n\t\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)\n\t\tDtlsTransport::privateKey = EVP_EC_gen(SN_X9_62_prime256v1);\n\n\t\tif (!DtlsTransport::privateKey)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"EVP_EC_gen() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Create the X509 certificate.\n\t\tDtlsTransport::certificate = X509_new();\n\n\t\tif (!DtlsTransport::certificate)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"X509_new() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Set version 3 (note that 0 means version 1).\n\t\tX509_set_version(DtlsTransport::certificate, 2);\n\n\t\t// Set serial number (avoid default 0).\n\t\tASN1_INTEGER_set(\n\t\t  X509_get_serialNumber(DtlsTransport::certificate),\n\t\t  Utils::Crypto::GetRandomUInt<uint64_t>(1000000, 9999999));\n\n\t\t// Set valid period.\n\t\tX509_gmtime_adj(X509_get_notBefore(DtlsTransport::certificate), -315360000); // -10 years.\n\t\tX509_gmtime_adj(X509_get_notAfter(DtlsTransport::certificate), 315360000);   // 10 years.\n\n\t\t// Set the public key for the certificate using the key.\n\t\tret = X509_set_pubkey(DtlsTransport::certificate, DtlsTransport::privateKey);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"X509_set_pubkey() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Set certificate fields.\n\t\tcertName = X509_get_subject_name(DtlsTransport::certificate);\n\n\t\tif (!certName)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"X509_get_subject_name() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tX509_NAME_add_entry_by_txt(\n\t\t  certName, \"O\", MBSTRING_ASC, reinterpret_cast<const uint8_t*>(subject.c_str()), -1, -1, 0);\n\t\tX509_NAME_add_entry_by_txt(\n\t\t  certName, \"CN\", MBSTRING_ASC, reinterpret_cast<const uint8_t*>(subject.c_str()), -1, -1, 0);\n\n\t\t// It is self-signed so set the issuer name to be the same as the subject.\n\t\tret = X509_set_issuer_name(DtlsTransport::certificate, certName);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"X509_set_issuer_name() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Sign the certificate with its own private key.\n\t\tret = X509_sign(DtlsTransport::certificate, DtlsTransport::privateKey, EVP_sha256());\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"X509_sign() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\treturn;\n\n\terror:\n\n\t\tif (DtlsTransport::privateKey)\n\t\t{\n\t\t\tEVP_PKEY_free(DtlsTransport::privateKey);\n\t\t}\n\n\t\tif (DtlsTransport::certificate)\n\t\t{\n\t\t\tX509_free(DtlsTransport::certificate);\n\t\t}\n\n\t\tMS_THROW_ERROR(\"DTLS certificate and private key generation failed\");\n\t}\n\n\tvoid DtlsTransport::ReadCertificateAndPrivateKeyFromFiles()\n\t{\n\t\tMS_TRACE();\n\n\t\tFILE* file{ nullptr };\n\n\t\tfile = fopen(Settings::configuration.dtlsCertificateFile.c_str(), \"r\");\n\n\t\tif (!file)\n\t\t{\n\t\t\tMS_ERROR(\"error reading DTLS certificate file: %s\", std::strerror(errno));\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tDtlsTransport::certificate = PEM_read_X509(file, nullptr, nullptr, nullptr);\n\n\t\tif (!DtlsTransport::certificate)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"PEM_read_X509() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tfclose(file);\n\n\t\tfile = fopen(Settings::configuration.dtlsPrivateKeyFile.c_str(), \"r\");\n\n\t\tif (!file)\n\t\t{\n\t\t\tMS_ERROR(\"error reading DTLS private key file: %s\", std::strerror(errno));\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tDtlsTransport::privateKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);\n\n\t\tif (!DtlsTransport::privateKey)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"PEM_read_PrivateKey() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tfclose(file);\n\n\t\treturn;\n\n\terror:\n\n\t\tMS_THROW_ERROR(\"error reading DTLS certificate and private key PEM files\");\n\t}\n\n\tvoid DtlsTransport::CreateSslCtx()\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::string dtlsSrtpCryptoSuites;\n\t\tint ret;\n\n\t\t/* Set the global DTLS context. */\n\n\t\t// Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).\n\t\tDtlsTransport::sslCtx = SSL_CTX_new(DTLS_method());\n\n\t\tif (!DtlsTransport::sslCtx)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_new() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tret = SSL_CTX_use_certificate(DtlsTransport::sslCtx, DtlsTransport::certificate);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_use_certificate() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tret = SSL_CTX_use_PrivateKey(DtlsTransport::sslCtx, DtlsTransport::privateKey);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_use_PrivateKey() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tret = SSL_CTX_check_private_key(DtlsTransport::sslCtx);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_check_private_key() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Set options.\n\t\tSSL_CTX_set_options(\n\t\t  DtlsTransport::sslCtx,\n\t\t  SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |\n\t\t    SSL_OP_NO_QUERY_MTU);\n\n\t\t// Don't use sessions cache.\n\t\tSSL_CTX_set_session_cache_mode(DtlsTransport::sslCtx, SSL_SESS_CACHE_OFF);\n\n\t\tSSL_CTX_set_verify_depth(DtlsTransport::sslCtx, 4);\n\n\t\t// Require certificate from peer.\n\t\tSSL_CTX_set_verify(\n\t\t  DtlsTransport::sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);\n\n\t\t// Set SSL info callback.\n\t\tSSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo);\n\n\t\t// Set ciphers.\n\t\tret = SSL_CTX_set_cipher_list(\n\t\t  DtlsTransport::sslCtx, \"DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK\");\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_set_cipher_list() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Enable ECDH ciphers.\n\t\t//\n\t\t// NOTE: As per official docs:\n\t\t// \"In OpenSSL 1.1.0, ECDH handling was made automatic. Applications should\n\t\t// not call SSL_CTX_set_ecdh_auto() or similar APIs anymore.\"\n\n\t\t// Set the \"use_srtp\" DTLS extension.\n\t\tfor (auto it = DtlsTransport::SrtpCryptoSuites.begin();\n\t\t     it != DtlsTransport::SrtpCryptoSuites.end();\n\t\t     ++it)\n\t\t{\n\t\t\tif (it != DtlsTransport::SrtpCryptoSuites.begin())\n\t\t\t{\n\t\t\t\tdtlsSrtpCryptoSuites += \":\";\n\t\t\t}\n\n\t\t\tconst SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it);\n\n\t\t\tdtlsSrtpCryptoSuites += cryptoSuiteEntry->name;\n\t\t}\n\n\t\tMS_DEBUG_2TAGS(\n\t\t  dtls, srtp, \"setting SRTP crypto suites for DTLS: %s\", dtlsSrtpCryptoSuites.c_str());\n\n\t\t// NOTE: This function returns 0 on success.\n\t\tret = SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpCryptoSuites.c_str());\n\n\t\tif (ret != 0)\n\t\t{\n\t\t\tMS_ERROR(\n\t\t\t  \"SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'\", dtlsSrtpCryptoSuites.c_str());\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_CTX_set_tlsext_use_srtp() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\treturn;\n\n\terror:\n\n\t\tif (DtlsTransport::sslCtx)\n\t\t{\n\t\t\tSSL_CTX_free(DtlsTransport::sslCtx);\n\t\t\tDtlsTransport::sslCtx = nullptr;\n\t\t}\n\n\t\tMS_THROW_ERROR(\"SSL context creation failed\");\n\t}\n\n\tvoid DtlsTransport::GenerateFingerprints()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (const auto& kv : DtlsTransport::String2FingerprintAlgorithm)\n\t\t{\n\t\t\tconst std::string& algorithmString   = kv.first;\n\t\t\tconst FingerprintAlgorithm algorithm = kv.second;\n\t\t\tuint8_t binaryFingerprint[EVP_MAX_MD_SIZE];\n\t\t\tunsigned int size{ 0 };\n\t\t\tchar hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];\n\t\t\tconst EVP_MD* hashFunction;\n\t\t\tint ret;\n\n\t\t\tswitch (algorithm)\n\t\t\t{\n\t\t\t\tcase FingerprintAlgorithm::SHA1:\n\t\t\t\t{\n\t\t\t\t\thashFunction = EVP_sha1();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FingerprintAlgorithm::SHA224:\n\t\t\t\t{\n\t\t\t\t\thashFunction = EVP_sha224();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FingerprintAlgorithm::SHA256:\n\t\t\t\t{\n\t\t\t\t\thashFunction = EVP_sha256();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FingerprintAlgorithm::SHA384:\n\t\t\t\t{\n\t\t\t\t\thashFunction = EVP_sha384();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FingerprintAlgorithm::SHA512:\n\t\t\t\t{\n\t\t\t\t\thashFunction = EVP_sha512();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"unknown algorithm\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tret = X509_digest(\n\t\t\t  DtlsTransport::certificate, hashFunction, binaryFingerprint, std::addressof(size));\n\n\t\t\tif (ret == 0 || size == 0)\n\t\t\t{\n\t\t\t\tMS_ERROR(\"X509_digest() failed\");\n\t\t\t\tMS_THROW_ERROR(\"Fingerprints generation failed\");\n\t\t\t}\n\n\t\t\t// Convert to hexadecimal format in uppercase with colons.\n\t\t\tfor (unsigned int i{ 0 }; i < size; ++i)\n\t\t\t{\n\t\t\t\tstd::snprintf(hexFingerprint + (i * 3), 4, \"%.2X:\", binaryFingerprint[i]);\n\t\t\t}\n\t\t\thexFingerprint[(size * 3) - 1] = '\\0';\n\n\t\t\tMS_DEBUG_TAG(dtls, \"%-7s fingerprint: %s\", algorithmString.c_str(), hexFingerprint);\n\n\t\t\t// Store it in the vector.\n\t\t\tDtlsTransport::Fingerprint fingerprint;\n\n\t\t\tfingerprint.algorithm = algorithm;\n\t\t\tfingerprint.value     = hexFingerprint;\n\n\t\t\tDtlsTransport::localFingerprints.push_back(fingerprint);\n\t\t}\n\t}\n\n\t/* Instance methods. */\n\n\tDtlsTransport::DtlsTransport(Listener* listener, SharedInterface* shared)\n\t  : listener(listener), shared(shared), ssl(SSL_new(DtlsTransport::sslCtx))\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->ssl)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_new() failed\");\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Set this as custom data.\n\t\tSSL_set_ex_data(this->ssl, 0, static_cast<void*>(this));\n\n\t\tthis->sslBioFromNetwork = BIO_new(BIO_s_mem());\n\n\t\tif (!this->sslBioFromNetwork)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"BIO_new() failed\");\n\n\t\t\tSSL_free(this->ssl);\n\n\t\t\tgoto error;\n\t\t}\n\n\t\tthis->sslBioToNetwork = BIO_new(BIO_s_mem());\n\n\t\tif (!this->sslBioToNetwork)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"BIO_new() failed\");\n\n\t\t\tBIO_free(this->sslBioFromNetwork);\n\t\t\tSSL_free(this->ssl);\n\n\t\t\tgoto error;\n\t\t}\n\n\t\t// Set the MTU so that we don't send packets that are too large with no\n\t\t// fragmentation.\n\t\tSSL_set_mtu(this->ssl, DtlsMtu);\n\t\tDTLS_set_link_mtu(this->ssl, DtlsMtu);\n\n\t\t// We want to monitor OpenSSL write operations into our |sslBioToNetwork|\n\t\t// buffer so we can immediately send those DTLS bytes (containing full DTLS\n\t\t// messages, or valid DTLS fragment messages, or combination of them) to\n\t\t// the endpoint, and hence we honor the configured DTLS MTU.\n\t\tBIO_set_callback_ex(this->sslBioToNetwork, onSslBioOut);\n\t\tBIO_set_callback_arg(this->sslBioToNetwork, reinterpret_cast<char*>(this));\n\t\tSSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork);\n\n\t\t// Set callback handler for setting DTLS timer interval.\n\t\tDTLS_set_timer_cb(this->ssl, onSslDtlsTimer);\n\n\t\t// Set the DTLS timer.\n\t\tthis->timer = this->shared->CreateTimer(this);\n\n\t\treturn;\n\n\terror:\n\n\t\t// NOTE: At this point SSL_set_bio() was not called so we must free BIOs as\n\t\t// well.\n\t\tif (this->sslBioFromNetwork)\n\t\t{\n\t\t\tBIO_free(this->sslBioFromNetwork);\n\t\t}\n\n\t\tif (this->sslBioToNetwork)\n\t\t{\n\t\t\tBIO_free(this->sslBioToNetwork);\n\t\t}\n\n\t\tif (this->ssl)\n\t\t{\n\t\t\tSSL_free(this->ssl);\n\t\t}\n\n\t\t// NOTE: If this is not catched by the caller the program will abort, but\n\t\t// this should never happen.\n\t\tMS_THROW_ERROR(\"DtlsTransport instance creation failed\");\n\t}\n\n\tDtlsTransport::~DtlsTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (IsRunning())\n\t\t{\n\t\t\t// Send close alert to the peer.\n\t\t\tSSL_shutdown(this->ssl);\n\t\t}\n\n\t\tif (this->ssl)\n\t\t{\n\t\t\tSSL_free(this->ssl);\n\n\t\t\tthis->ssl               = nullptr;\n\t\t\tthis->sslBioFromNetwork = nullptr;\n\t\t\tthis->sslBioToNetwork   = nullptr;\n\t\t}\n\n\t\t// Close the DTLS timer.\n\t\tdelete this->timer;\n\t}\n\n\tvoid DtlsTransport::Dump(int indentation) const\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::string state{ \"new\" };\n\t\tstd::string role{ \"none \" };\n\n\t\tswitch (this->state)\n\t\t{\n\t\t\tcase DtlsState::CONNECTING:\n\t\t\t{\n\t\t\t\tstate = \"connecting\";\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase DtlsState::CONNECTED:\n\t\t\t{\n\t\t\t\tstate = \"connected\";\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase DtlsState::FAILED:\n\t\t\t{\n\t\t\t\tstate = \"failed\";\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase DtlsState::CLOSED:\n\t\t\t{\n\t\t\t\tstate = \"closed\";\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tif (this->localRole.has_value())\n\t\t{\n\t\t\tswitch (this->localRole.value())\n\t\t\t{\n\t\t\t\tcase Role::AUTO:\n\t\t\t\t{\n\t\t\t\t\trole = \"auto\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Role::SERVER:\n\t\t\t\t{\n\t\t\t\t\trole = \"server\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Role::CLIENT:\n\t\t\t\t{\n\t\t\t\t\trole = \"client\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tMS_DUMP_CLEAN(indentation, \"<DtlsTransport>\");\n\t\tMS_DUMP_CLEAN(indentation, \"  state: %s\", state.c_str());\n\t\tMS_DUMP_CLEAN(indentation, \"  role: %s\", role.c_str());\n\t\tMS_DUMP_CLEAN(indentation, \"  handshake done: %s\", this->handshakeDone ? \"yes\" : \"no\");\n\t\tMS_DUMP_CLEAN(indentation, \"</DtlsTransport>\");\n\t}\n\n\tvoid DtlsTransport::Run(Role localRole)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  localRole == Role::CLIENT || localRole == Role::SERVER,\n\t\t  \"local DTLS role must be 'client' or 'server'\");\n\n\t\tif (this->localRole.has_value() && localRole == this->localRole.value())\n\t\t{\n\t\t\tMS_ERROR(\"same local DTLS role provided, doing nothing\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If the previous local DTLS role was 'client' or 'server' do reset.\n\t\tif (\n\t\t  this->localRole.has_value() &&\n\t\t  (this->localRole.value() == Role::CLIENT || this->localRole.value() == Role::SERVER))\n\t\t{\n\t\t\tMS_DEBUG_TAG(dtls, \"resetting DTLS due to local role change\");\n\n\t\t\tReset();\n\t\t}\n\n\t\t// Update local role.\n\t\tthis->localRole = localRole;\n\n\t\t// Set state and notify the listener.\n\t\tthis->state = DtlsState::CONNECTING;\n\t\tthis->listener->OnDtlsTransportConnecting(this);\n\n\t\tswitch (this->localRole.value())\n\t\t{\n\t\t\tcase Role::CLIENT:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"running [role:client]\");\n\n\t\t\t\tSSL_set_connect_state(this->ssl);\n\t\t\t\tSSL_do_handshake(this->ssl);\n\t\t\t\tSetTimeout();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Role::SERVER:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"running [role:server]\");\n\n\t\t\t\tSSL_set_accept_state(this->ssl);\n\t\t\t\tSSL_do_handshake(this->ssl);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ABORT(\"invalid local DTLS role\");\n\t\t\t}\n\t\t}\n\t}\n\n\tbool DtlsTransport::SetRemoteFingerprint(const Fingerprint& fingerprint)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->remoteFingerprint = fingerprint;\n\n\t\t// The remote fingerpring may have been set after DTLS handshake was done,\n\t\t// so we may need to process it now.\n\t\tif (this->handshakeDone && this->state != DtlsState::CONNECTED)\n\t\t{\n\t\t\tMS_DEBUG_TAG(dtls, \"handshake already done, processing it right now\");\n\n\t\t\treturn ProcessHandshake();\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tvoid DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tint written;\n\t\tint read;\n\n\t\tif (!IsRunning())\n\t\t{\n\t\t\tMS_ERROR(\"cannot process data while not running\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Write the received DTLS data into the sslBioFromNetwork.\n\t\twritten =\n\t\t  BIO_write(this->sslBioFromNetwork, static_cast<const void*>(data), static_cast<int>(len));\n\n\t\tif (written != static_cast<int>(len))\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  dtls,\n\t\t\t  \"OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)\",\n\t\t\t  static_cast<size_t>(written),\n\t\t\t  len);\n\t\t}\n\n\t\t// Must call SSL_read() to process received DTLS data.\n\t\tread = SSL_read(this->ssl, static_cast<void*>(DtlsTransport::sslReadBuffer), SslReadBufferSize);\n\n\t\t// Check SSL status and return if it is bad/closed.\n\t\tif (!CheckStatus(read))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Set/update the DTLS timeout.\n\t\tif (!SetTimeout())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Application data received. Notify to the listener.\n\t\tif (read > 0)\n\t\t{\n\t\t\t// It is allowed to receive DTLS data even before validating remote\n\t\t\t// fingerprint.\n\t\t\tif (!this->handshakeDone)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"ignoring application data received while DTLS handshake not done\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnDtlsTransportApplicationDataReceived(\n\t\t\t  this, static_cast<const uint8_t*>(DtlsTransport::sslReadBuffer), static_cast<size_t>(read));\n\t\t}\n\t}\n\n\tbool DtlsTransport::SendApplicationData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// We cannot send data to the peer if its remote fingerprint is not\n\t\t// validated.\n\t\tif (this->state != DtlsState::CONNECTED)\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"cannot send application data while DTLS is not fully connected\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif (len == 0)\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"ignoring 0 length data\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tconst int written = SSL_write(this->ssl, static_cast<const void*>(data), static_cast<int>(len));\n\n\t\tif (written < 0)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"SSL_write() failed\");\n\n\t\t\tif (!CheckStatus(written))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\telse if (written != static_cast<int>(len))\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  dtls, \"OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)\", written, len);\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * This method is called within our |onSslBioOut| callback above. As told\n\t * there, it's guaranteed that OpenSSL invokes that callback with all the\n\t * bytes currently written in our BIO mem buffer |this->sslBioToNetwork| so\n\t * we can safely reset/clear that buffer once we have sent the data to the\n\t * endpoint.\n\t */\n\tvoid DtlsTransport::SendDtlsData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\"%zu bytes of DTLS data ready to be sent\", len);\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnDtlsTransportSendData(this, data, len);\n\n\t\t// Clear the BIO buffer.\n\t\tauto ret = BIO_reset(this->sslBioToNetwork);\n\n\t\tif (ret != 1)\n\t\t{\n\t\t\tMS_ERROR(\"BIO_reset() failed [ret:%d]\", ret);\n\t\t}\n\t}\n\n\tvoid DtlsTransport::Reset()\n\t{\n\t\tMS_TRACE();\n\n\t\tint ret;\n\n\t\tif (!IsRunning())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tMS_WARN_TAG(dtls, \"resetting DTLS transport\");\n\n\t\t// Stop the DTLS timer.\n\t\tthis->timer->Stop();\n\n\t\t// NOTE: We need to reset the SSL instance so we need to \"shutdown\" it, but\n\t\t// we don't want to send a DTLS Close Alert to the peer. However this is\n\t\t// gonna happen since SSL_shutdown() will trigger a DTLS Close Alert and\n\t\t// we'll have our onSslBioOut() callback called to deliver it.\n\t\tSSL_shutdown(this->ssl);\n\n\t\tthis->localRole.reset();\n\t\tthis->state            = DtlsState::NEW;\n\t\tthis->handshakeDone    = false;\n\t\tthis->handshakeDoneNow = false;\n\n\t\t// Reset SSL status.\n\t\t// NOTE: For this to properly work, SSL_shutdown() must be called before.\n\t\t// NOTE: This may fail if not enough DTLS handshake data has been received,\n\t\t// but we don't care so just clear the error queue.\n\t\tret = SSL_clear(this->ssl);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tERR_clear_error();\n\t\t}\n\t}\n\n\tbool DtlsTransport::CheckStatus(int returnCode)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst bool wasHandshakeDone = this->handshakeDone;\n\t\tconst int err               = SSL_get_error(this->ssl, returnCode);\n\n\t\tswitch (err)\n\t\t{\n\t\t\tcase SSL_ERROR_NONE:\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_SSL:\n\t\t\t{\n\t\t\t\tLOG_OPENSSL_ERROR(\"SSL status: SSL_ERROR_SSL\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_WANT_READ:\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_WANT_WRITE:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"SSL status: SSL_ERROR_WANT_WRITE\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_WANT_X509_LOOKUP:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"SSL status: SSL_ERROR_WANT_X509_LOOKUP\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_SYSCALL:\n\t\t\t{\n\t\t\t\tLOG_OPENSSL_ERROR(\"SSL status: SSL_ERROR_SYSCALL\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_ZERO_RETURN:\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_WANT_CONNECT:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"SSL status: SSL_ERROR_WANT_CONNECT\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SSL_ERROR_WANT_ACCEPT:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"SSL status: SSL_ERROR_WANT_ACCEPT\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"SSL status: unknown error\");\n\t\t\t}\n\t\t}\n\n\t\t// Check if the handshake (or re-handshake) has been done right now.\n\t\tif (this->handshakeDoneNow)\n\t\t{\n\t\t\tthis->handshakeDoneNow = false;\n\t\t\tthis->handshakeDone    = true;\n\n\t\t\t// Stop the timer.\n\t\t\tthis->timer->Stop();\n\n\t\t\t// Process the handshake just once (ignore if DTLS renegotiation).\n\t\t\tif (!wasHandshakeDone && this->remoteFingerprint.has_value())\n\t\t\t{\n\t\t\t\treturn ProcessHandshake();\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t\t// Check if the peer sent close alert or a fatal error happened.\n\t\telse if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)\n\t\t{\n\t\t\tif (this->state == DtlsState::CONNECTED)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"disconnected\");\n\n\t\t\t\tReset();\n\n\t\t\t\t// Set state and notify the listener.\n\t\t\t\tthis->state = DtlsState::CLOSED;\n\t\t\t\tthis->listener->OnDtlsTransportClosed(this);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"connection failed\");\n\n\t\t\t\tReset();\n\n\t\t\t\t// Set state and notify the listener.\n\t\t\t\tthis->state = DtlsState::FAILED;\n\t\t\t\tthis->listener->OnDtlsTransportFailed(this);\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tbool DtlsTransport::SetTimeout()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->ssl, \"this->ssl is not set\");\n\t\tMS_ASSERT(\n\t\t  this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED,\n\t\t  \"invalid DTLS state\");\n\n\t\tuv_timeval_t dtlsTimeout{ 0, 0 };\n\t\tuint64_t timeoutMs;\n\n\t\t// DTLSv1_get_timeout queries the next DTLS handshake timeout. If there is\n\t\t// a timeout in progress, it sets *out to the time remaining and returns\n\t\t// one. Otherwise, it returns zero.\n\t\tDTLSv1_get_timeout(this->ssl, static_cast<void*>(std::addressof(dtlsTimeout)));\n\n\t\ttimeoutMs = (dtlsTimeout.tv_sec * static_cast<uint64_t>(1000)) + (dtlsTimeout.tv_usec / 1000);\n\n\t\tif (timeoutMs == 0)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"timeout is 0, calling OnTimer() callback directly\");\n\n\t\t\tOnTimer(this->timer);\n\n\t\t\treturn true;\n\t\t}\n\t\telse if (timeoutMs < 30000)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"DTLS timer set in %\" PRIu64 \"ms\", timeoutMs);\n\n\t\t\tthis->timer->Start(timeoutMs);\n\n\t\t\treturn true;\n\t\t}\n\t\t// NOTE: Don't start the timer again if the timeout is greater than 30\n\t\t// seconds.\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"DTLS timeout too high (%\" PRIu64 \"ms), resetting DLTS\", timeoutMs);\n\n\t\t\tReset();\n\n\t\t\t// Set state and notify the listener.\n\t\t\tthis->state = DtlsState::FAILED;\n\t\t\tthis->listener->OnDtlsTransportFailed(this);\n\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tbool DtlsTransport::ProcessHandshake()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->handshakeDone, \"handshake not done yet\");\n\n\t\t// Validate the remote fingerprint.\n\t\tif (!CheckRemoteFingerprint())\n\t\t{\n\t\t\tReset();\n\n\t\t\t// Set state and notify the listener.\n\t\t\tthis->state = DtlsState::FAILED;\n\t\t\tthis->listener->OnDtlsTransportFailed(this);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Get the negotiated SRTP crypto suite.\n\t\tauto srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite();\n\n\t\tif (srtpCryptoSuite)\n\t\t{\n\t\t\t// Extract the SRTP keys (will notify the listener with them).\n\t\t\tExtractSrtpKeys(srtpCryptoSuite.value());\n\n\t\t\treturn true;\n\t\t}\n\n\t\t// NOTE: We assume that \"use_srtp\" DTLS extension is required even if\n\t\t// there is no audio/video.\n\t\tMS_WARN_2TAGS(dtls, srtp, \"SRTP crypto suite not negotiated\");\n\n\t\tReset();\n\n\t\t// Set state and notify the listener.\n\t\tthis->state = DtlsState::FAILED;\n\t\tthis->listener->OnDtlsTransportFailed(this);\n\n\t\treturn false;\n\t}\n\n\tbool DtlsTransport::CheckRemoteFingerprint()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->remoteFingerprint.has_value(), \"remote fingerprint not set\");\n\n\t\tX509* certificate;\n\t\tuint8_t binaryFingerprint[EVP_MAX_MD_SIZE];\n\t\tunsigned int size{ 0 };\n\t\tchar hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];\n\t\tconst EVP_MD* hashFunction;\n\t\tint ret;\n\n\t\tcertificate = SSL_get_peer_certificate(this->ssl);\n\n\t\tif (!certificate)\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"no certificate was provided by the peer\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tswitch (this->remoteFingerprint->algorithm)\n\t\t{\n\t\t\tcase FingerprintAlgorithm::SHA1:\n\t\t\t{\n\t\t\t\thashFunction = EVP_sha1();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FingerprintAlgorithm::SHA224:\n\t\t\t{\n\t\t\t\thashFunction = EVP_sha224();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FingerprintAlgorithm::SHA256:\n\t\t\t{\n\t\t\t\thashFunction = EVP_sha256();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FingerprintAlgorithm::SHA384:\n\t\t\t{\n\t\t\t\thashFunction = EVP_sha384();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase FingerprintAlgorithm::SHA512:\n\t\t\t{\n\t\t\t\thashFunction = EVP_sha512();\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\n\t\t// Compare the remote fingerprint with the value given via signaling.\n\t\tret = X509_digest(certificate, hashFunction, binaryFingerprint, std::addressof(size));\n\n\t\tif (ret == 0 || size == 0)\n\t\t{\n\t\t\tMS_ERROR(\"X509_digest() failed\");\n\n\t\t\tX509_free(certificate);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Convert to hexadecimal format in uppercase with colons.\n\t\tfor (unsigned int i{ 0 }; i < size; ++i)\n\t\t{\n\t\t\tstd::snprintf(hexFingerprint + (i * 3), 4, \"%.2X:\", binaryFingerprint[i]);\n\t\t}\n\t\thexFingerprint[(size * 3) - 1] = '\\0';\n\n\t\tif (this->remoteFingerprint->value != hexFingerprint)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  dtls,\n\t\t\t  \"fingerprint in the remote certificate (%s) does not match the announced one (%s)\",\n\t\t\t  hexFingerprint,\n\t\t\t  this->remoteFingerprint->value.c_str());\n\n\t\t\tX509_free(certificate);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tMS_DEBUG_TAG(dtls, \"valid remote fingerprint\");\n\n\t\t// Get the remote certificate in PEM format.\n\n\t\tBIO* bio = BIO_new(BIO_s_mem());\n\n\t\t// Ensure the underlying BUF_MEM structure is also freed.\n\t\t// NOTE: Avoid stupid \"warning: value computed is not used [-Wunused-value]\"\n\t\t// since BIO_set_close() always returns 1.\n\t\t(void)BIO_set_close(bio, BIO_CLOSE);\n\n\t\tret = PEM_write_bio_X509(bio, certificate);\n\n\t\tif (ret != 1)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"PEM_write_bio_X509() failed\");\n\n\t\t\tX509_free(certificate);\n\t\t\tBIO_free(bio);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tBUF_MEM* mem;\n\n\t\tBIO_get_mem_ptr(bio, std::addressof(mem));\n\n\t\tif (!mem || !mem->data || mem->length == 0u)\n\t\t{\n\t\t\tLOG_OPENSSL_ERROR(\"BIO_get_mem_ptr() failed\");\n\n\t\t\tX509_free(certificate);\n\t\t\tBIO_free(bio);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->remoteCert = std::string(mem->data, mem->length);\n\n\t\tX509_free(certificate);\n\t\tBIO_free(bio);\n\n\t\treturn true;\n\t}\n\n\tvoid DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite)\n\t{\n\t\tMS_TRACE();\n\n\t\tsize_t srtpKeyLength{ 0 };\n\t\tsize_t srtpSaltLength{ 0 };\n\t\tsize_t srtpMasterLength{ 0 };\n\n\t\tswitch (srtpCryptoSuite)\n\t\t{\n\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:\n\t\t\t{\n\t\t\t\tsrtpKeyLength    = SrtpAesGcm256MasterKeyLength;\n\t\t\t\tsrtpSaltLength   = SrtpAesGcm256MasterSaltLength;\n\t\t\t\tsrtpMasterLength = SrtpAesGcm256MasterLength;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:\n\t\t\t{\n\t\t\t\tsrtpKeyLength    = SrtpAesGcm128MasterKeyLength;\n\t\t\t\tsrtpSaltLength   = SrtpAesGcm128MasterSaltLength;\n\t\t\t\tsrtpMasterLength = SrtpAesGcm128MasterLength;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t{\n\t\t\t\tsrtpKeyLength    = SrtpMasterKeyLength;\n\t\t\t\tsrtpSaltLength   = SrtpMasterSaltLength;\n\t\t\t\tsrtpMasterLength = SrtpMasterLength;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tauto* srtpMaterial = new uint8_t[srtpMasterLength * 2];\n\t\tuint8_t* srtpLocalKey{ nullptr };\n\t\tuint8_t* srtpLocalSalt{ nullptr };\n\t\tuint8_t* srtpRemoteKey{ nullptr };\n\t\tuint8_t* srtpRemoteSalt{ nullptr };\n\t\tauto* srtpLocalMasterKey  = new uint8_t[srtpMasterLength];\n\t\tauto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength];\n\t\tint ret;\n\n\t\tret = SSL_export_keying_material(\n\t\t  this->ssl, srtpMaterial, srtpMasterLength * 2, \"EXTRACTOR-dtls_srtp\", 19, nullptr, 0, 0);\n\n\t\tMS_ASSERT(ret != 0, \"SSL_export_keying_material() failed\");\n\t\tMS_ASSERT(this->localRole.has_value(), \"no DTLS role set\");\n\n\t\tswitch (this->localRole.value())\n\t\t{\n\t\t\tcase Role::SERVER:\n\t\t\t{\n\t\t\t\tsrtpRemoteKey  = srtpMaterial;\n\t\t\t\tsrtpLocalKey   = srtpRemoteKey + srtpKeyLength;\n\t\t\t\tsrtpRemoteSalt = srtpLocalKey + srtpKeyLength;\n\t\t\t\tsrtpLocalSalt  = srtpRemoteSalt + srtpSaltLength;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Role::CLIENT:\n\t\t\t{\n\t\t\t\tsrtpLocalKey   = srtpMaterial;\n\t\t\t\tsrtpRemoteKey  = srtpLocalKey + srtpKeyLength;\n\t\t\t\tsrtpLocalSalt  = srtpRemoteKey + srtpKeyLength;\n\t\t\t\tsrtpRemoteSalt = srtpLocalSalt + srtpSaltLength;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ABORT(\"no DTLS role set\");\n\t\t\t}\n\t\t}\n\n\t\t// Create the SRTP local master key.\n\t\tstd::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);\n\t\tstd::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);\n\t\t// Create the SRTP remote master key.\n\t\tstd::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);\n\t\tstd::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);\n\n\t\t// Set state and notify the listener.\n\t\tthis->state = DtlsState::CONNECTED;\n\t\tthis->listener->OnDtlsTransportConnected(\n\t\t  this,\n\t\t  srtpCryptoSuite,\n\t\t  srtpLocalMasterKey,\n\t\t  srtpMasterLength,\n\t\t  srtpRemoteMasterKey,\n\t\t  srtpMasterLength,\n\t\t  this->remoteCert);\n\n\t\tdelete[] srtpMaterial;\n\t\tdelete[] srtpLocalMasterKey;\n\t\tdelete[] srtpRemoteMasterKey;\n\t}\n\n\tstd::optional<RTC::SrtpSession::CryptoSuite> DtlsTransport::GetNegotiatedSrtpCryptoSuite()\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::optional<RTC::SrtpSession::CryptoSuite> negotiatedSrtpCryptoSuite;\n\n\t\t// Ensure that the SRTP crypto suite has been negotiated.\n\t\t// NOTE: This is a OpenSSL type.\n\t\tconst SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl);\n\n\t\tif (!sslSrtpCryptoSuite)\n\t\t{\n\t\t\treturn negotiatedSrtpCryptoSuite;\n\t\t}\n\n\t\t// Get the negotiated SRTP crypto suite.\n\t\tfor (const auto& srtpCryptoSuite : DtlsTransport::SrtpCryptoSuites)\n\t\t{\n\t\t\tconst SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite);\n\n\t\t\tif (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0)\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(dtls, srtp, \"chosen SRTP crypto suite: %s\", cryptoSuiteEntry->name);\n\n\t\t\t\tnegotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite;\n\t\t\t}\n\t\t}\n\n\t\tMS_ASSERT(\n\t\t  negotiatedSrtpCryptoSuite.has_value(), \"chosen SRTP crypto suite is not an available one\");\n\n\t\treturn negotiatedSrtpCryptoSuite;\n\t}\n\n\tvoid DtlsTransport::OnSslInfo(int where, int ret)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst int w = where & -SSL_ST_MASK;\n\t\tconst char* role;\n\n\t\tif ((w & SSL_ST_CONNECT) != 0)\n\t\t{\n\t\t\trole = \"client\";\n\t\t}\n\t\telse if ((w & SSL_ST_ACCEPT) != 0)\n\t\t{\n\t\t\trole = \"server\";\n\t\t}\n\t\telse\n\t\t{\n\t\t\trole = \"undefined\";\n\t\t}\n\n\t\tif ((where & SSL_CB_LOOP) != 0)\n\t\t{\n\t\t\tMS_DEBUG_TAG(dtls, \"[role:%s, action:'%s']\", role, SSL_state_string_long(this->ssl));\n\t\t}\n\t\telse if ((where & SSL_CB_ALERT) != 0)\n\t\t{\n\t\t\tconst char* alertType;\n\n\t\t\tswitch (*SSL_alert_type_string(ret))\n\t\t\t{\n\t\t\t\tcase 'W':\n\t\t\t\t{\n\t\t\t\t\talertType = \"warning\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'F':\n\t\t\t\t{\n\t\t\t\t\talertType = \"fatal\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\talertType = \"undefined\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ((where & SSL_CB_READ) != 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(dtls, \"received DTLS %s alert: %s\", alertType, SSL_alert_desc_string_long(ret));\n\t\t\t}\n\t\t\telse if ((where & SSL_CB_WRITE) != 0)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"sending DTLS %s alert: %s\", alertType, SSL_alert_desc_string_long(ret));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"DTLS %s alert: %s\", alertType, SSL_alert_desc_string_long(ret));\n\t\t\t}\n\t\t}\n\t\telse if ((where & SSL_CB_EXIT) != 0)\n\t\t{\n\t\t\tif (ret == 0)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"[role:%s, failed:'%s']\", role, SSL_state_string_long(this->ssl));\n\t\t\t}\n\t\t\telse if (ret < 0)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(dtls, \"role: %s, waiting:'%s']\", role, SSL_state_string_long(this->ssl));\n\t\t\t}\n\t\t}\n\t\telse if ((where & SSL_CB_HANDSHAKE_START) != 0)\n\t\t{\n\t\t\tMS_DEBUG_TAG(dtls, \"DTLS handshake start\");\n\t\t}\n\t\telse if ((where & SSL_CB_HANDSHAKE_DONE) != 0)\n\t\t{\n\t\t\tMS_DEBUG_TAG(dtls, \"DTLS handshake done\");\n\n\t\t\tthis->handshakeDoneNow = true;\n\t\t}\n\n\t\t// NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here\n\t\t// upon receipt of a close alert does not work (the flag is set after this\n\t\t// callback).\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tvoid DtlsTransport::OnTimer(TimerHandleInterface* /*timer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Workaround for https://github.com/openssl/openssl/issues/7998.\n\t\tif (this->handshakeDone)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"handshake is done so return\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// DTLSv1_handle_timeout is called when a DTLS handshake timeout expires.\n\t\t// If no timeout had expired, it returns 0. Otherwise, it retransmits the\n\t\t// previous flight of handshake messages and returns 1. If too many timeouts\n\t\t// had expired without progress or an error occurs, it returns -1.\n\t\tauto ret = DTLSv1_handle_timeout(this->ssl);\n\n\t\tif (ret == 1)\n\t\t{\n\t\t\t// Set the DTLS timer again.\n\t\t\tSetTimeout();\n\t\t}\n\t\telse if (ret == -1)\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"DTLSv1_handle_timeout() failed\");\n\n\t\t\tReset();\n\n\t\t\t// Set state and notify the listener.\n\t\t\tthis->state = DtlsState::FAILED;\n\t\t\tthis->listener->OnDtlsTransportFailed(this);\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/ICE/IceCandidate.cpp",
    "content": "#define MS_CLASS \"RTC::ICE::IceCandidate\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/ICE/IceCandidate.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\t/* Class methods. */\n\n\t\tIceCandidate::CandidateType IceCandidate::CandidateTypeFromFbs(\n\t\t  FBS::WebRtcTransport::IceCandidateType type)\n\t\t{\n\t\t\tswitch (type)\n\t\t\t{\n\t\t\t\tcase FBS::WebRtcTransport::IceCandidateType::HOST:\n\t\t\t\t{\n\t\t\t\t\treturn IceCandidate::CandidateType::HOST;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\tFBS::WebRtcTransport::IceCandidateType IceCandidate::CandidateTypeToFbs(\n\t\t  IceCandidate::CandidateType type)\n\t\t{\n\t\t\tswitch (type)\n\t\t\t{\n\t\t\t\tcase IceCandidate::CandidateType::HOST:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceCandidateType::HOST;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\tIceCandidate::TcpCandidateType IceCandidate::TcpCandidateTypeFromFbs(\n\t\t  FBS::WebRtcTransport::IceCandidateTcpType type)\n\t\t{\n\t\t\tswitch (type)\n\t\t\t{\n\t\t\t\tcase FBS::WebRtcTransport::IceCandidateTcpType::PASSIVE:\n\t\t\t\t{\n\t\t\t\t\treturn IceCandidate::TcpCandidateType::PASSIVE;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\tFBS::WebRtcTransport::IceCandidateTcpType IceCandidate::TcpCandidateTypeToFbs(\n\t\t  IceCandidate::TcpCandidateType type)\n\t\t{\n\t\t\tswitch (type)\n\t\t\t{\n\t\t\t\tcase IceCandidate::TcpCandidateType::PASSIVE:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceCandidateTcpType::PASSIVE;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tflatbuffers::Offset<FBS::WebRtcTransport::IceCandidate> IceCandidate::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto protocol = TransportTuple::ProtocolToFbs(this->protocol);\n\t\t\tauto type     = CandidateTypeToFbs(this->type);\n\t\t\tflatbuffers::Optional<FBS::WebRtcTransport::IceCandidateTcpType> tcpType;\n\n\t\t\tif (this->protocol == Protocol::TCP)\n\t\t\t{\n\t\t\t\ttcpType.emplace(TcpCandidateTypeToFbs(this->tcpType));\n\t\t\t}\n\n\t\t\treturn FBS::WebRtcTransport::CreateIceCandidateDirect(\n\t\t\t  builder,\n\t\t\t  // foundation.\n\t\t\t  this->foundation.c_str(),\n\t\t\t  // priority.\n\t\t\t  this->priority,\n\t\t\t  // address.\n\t\t\t  this->address.c_str(),\n\t\t\t  // protocol.\n\t\t\t  protocol,\n\t\t\t  // port.\n\t\t\t  this->port,\n\t\t\t  // type.\n\t\t\t  type,\n\t\t\t  // tcpType.\n\t\t\t  tcpType);\n\t\t}\n\t} // namespace ICE\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/ICE/IceServer.cpp",
    "content": "#define MS_CLASS \"RTC::ICE::IceServer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/ICE/IceServer.hpp\"\n#include \"Logger.hpp\"\n#include <string_view>\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr size_t StunResponseFactoryBufferLength{ 65536 };\n\t\tstatic thread_local uint8_t StunResponseFactoryBuffer[StunResponseFactoryBufferLength];\n\t\tstatic constexpr size_t MaxTuples{ 8 };\n\t\tstatic constexpr uint8_t ConsentCheckMinTimeoutSec{ 10u };\n\t\tstatic constexpr uint8_t ConsentCheckMaxTimeoutSec{ 60u };\n\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tstd::unordered_map<IceServer::IceState, std::string> IceServer::iceStateToString =\n\t\t{\n\t\t\t{ IceServer::IceState::NEW,          \"new\"          },\n\t\t\t{ IceServer::IceState::CONNECTED,    \"connected\"    },\n\t\t\t{ IceServer::IceState::COMPLETED,    \"completed\"    },\n\t\t\t{ IceServer::IceState::DISCONNECTED, \"disconnected\" },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tconst std::string& IceServer::IceStateToString(IceState iceState)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn IceServer::iceStateToString.at(iceState);\n\t\t}\n\n\t\tFBS::WebRtcTransport::IceState IceServer::IceStateToFbs(IceServer::IceState state)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (state)\n\t\t\t{\n\t\t\t\tcase IceServer::IceState::NEW:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceState::NEW;\n\t\t\t\t}\n\n\t\t\t\tcase IceServer::IceState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceState::CONNECTED;\n\t\t\t\t}\n\n\t\t\t\tcase IceServer::IceState::COMPLETED:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceState::COMPLETED;\n\t\t\t\t}\n\n\t\t\t\tcase IceServer::IceState::DISCONNECTED:\n\t\t\t\t{\n\t\t\t\t\treturn FBS::WebRtcTransport::IceState::DISCONNECTED;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIceServer::IceServer(\n\t\t  Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  const std::string& usernameFragment,\n\t\t  const std::string& password,\n\t\t  uint8_t consentTimeoutSec)\n\t\t  : listener(listener), shared(shared), usernameFragment(usernameFragment), password(password)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (consentTimeoutSec == 0u)\n\t\t\t{\n\t\t\t\t// 0 means disabled so it's a valid value.\n\t\t\t}\n\t\t\telse if (consentTimeoutSec < ConsentCheckMinTimeoutSec)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"consentTimeoutSec cannot be lower than %\" PRIu8 \" seconds, fixing it\",\n\t\t\t\t  ConsentCheckMinTimeoutSec);\n\n\t\t\t\tconsentTimeoutSec = ConsentCheckMinTimeoutSec;\n\t\t\t}\n\t\t\telse if (consentTimeoutSec > ConsentCheckMaxTimeoutSec)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"consentTimeoutSec cannot be higher than %\" PRIu8 \" seconds, fixing it\",\n\t\t\t\t  ConsentCheckMaxTimeoutSec);\n\n\t\t\t\tconsentTimeoutSec = ConsentCheckMaxTimeoutSec;\n\t\t\t}\n\n\t\t\tthis->consentTimeoutMs = consentTimeoutSec * 1000;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment);\n\t\t}\n\n\t\tIceServer::~IceServer()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Here we must notify the listener about the removal of current\n\t\t\t// usernameFragments (and also the old one if any) and all tuples.\n\n\t\t\tthis->listener->OnIceServerLocalUsernameFragmentRemoved(this, usernameFragment);\n\n\t\t\tif (!this->oldUsernameFragment.empty())\n\t\t\t{\n\t\t\t\tthis->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);\n\t\t\t}\n\n\t\t\t// Clear all tuples.\n\t\t\tthis->isRemovingTuples = true;\n\n\t\t\tfor (const auto& it : this->tuples)\n\t\t\t{\n\t\t\t\tauto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnIceServerTupleRemoved(this, storedTuple);\n\t\t\t}\n\n\t\t\tthis->isRemovingTuples = false;\n\n\t\t\t// Clear all tuples.\n\t\t\t// NOTE: Do it after notifying the listener since the listener may need to\n\t\t\t// use/read the tuple being removed so we cannot free it yet.\n\t\t\tthis->tuples.clear();\n\n\t\t\t// Unset selected tuple.\n\t\t\tthis->selectedTuple = nullptr;\n\n\t\t\t// Delete the ICE consent check timer.\n\t\t\tdelete this->consentCheckTimer;\n\t\t\tthis->consentCheckTimer = nullptr;\n\t\t}\n\n\t\tvoid IceServer::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<IceServer>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  state: %s\", IceServer::IceStateToString(this->state).c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tuples:\");\n\t\t\tfor (const auto& tuple : this->tuples)\n\t\t\t{\n\t\t\t\ttuple.Dump(indentation + 2);\n\t\t\t}\n\t\t\tif (this->selectedTuple)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  selected tuple:\");\n\t\t\t\tthis->selectedTuple->Dump(indentation + 2);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"  consent timeout (ms): %\" PRIu16, this->consentTimeoutMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  remote nomination: %\" PRIu32, this->remoteNomination);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</IceServer>\");\n\t\t}\n\n\t\tvoid IceServer::ProcessStunPacket(const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (packet->GetClass())\n\t\t\t{\n\t\t\t\tcase RTC::ICE::StunPacket::Class::REQUEST:\n\t\t\t\t{\n\t\t\t\t\tProcessStunRequest(packet, tuple);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::ICE::StunPacket::Class::INDICATION:\n\t\t\t\t{\n\t\t\t\t\tProcessStunIndication(packet);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE:\n\t\t\t\tcase RTC::ICE::StunPacket::Class::ERROR_RESPONSE:\n\t\t\t\t{\n\t\t\t\t\tProcessStunResponse(packet);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"unknown STUN class %\" PRIu16 \", discarded\",\n\t\t\t\t\t  static_cast<uint16_t>(packet->GetClass()));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid IceServer::RestartIce(const std::string& usernameFragment, const std::string& password)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->oldUsernameFragment.empty())\n\t\t\t{\n\t\t\t\tthis->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);\n\t\t\t}\n\n\t\t\tthis->oldUsernameFragment = this->usernameFragment;\n\t\t\tthis->usernameFragment    = usernameFragment;\n\n\t\t\tthis->oldPassword = this->password;\n\t\t\tthis->password    = password;\n\n\t\t\tthis->remoteNomination = 0u;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment);\n\n\t\t\t// NOTE: Do not call listener->OnIceServerLocalUsernameFragmentRemoved()\n\t\t\t// yet with old usernameFragment. Wait until we receive a STUN packet\n\t\t\t// with the new one.\n\n\t\t\t// Restart ICE consent check (if running) to give some time to the\n\t\t\t// client to establish ICE again.\n\t\t\tif (IsConsentCheckSupported() && IsConsentCheckRunning())\n\t\t\t{\n\t\t\t\tRestartConsentCheck();\n\t\t\t}\n\t\t}\n\n\t\tbool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn HasTuple(tuple) != nullptr;\n\t\t}\n\n\t\tvoid IceServer::RemoveTuple(RTC::TransportTuple* tuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// If IceServer is removing a tuple or all tuples (for instance in the\n\t\t\t// destructor), the OnIceServerTupleRemoved() callback may end triggering\n\t\t\t// new calls to RemoveTuple(). We must ignore it to avoid double-free issues.\n\t\t\tif (this->isRemovingTuples)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tRTC::TransportTuple* removedTuple{ nullptr };\n\n\t\t\t// Find the removed tuple.\n\t\t\tauto it = this->tuples.begin();\n\n\t\t\tfor (; it != this->tuples.end(); ++it)\n\t\t\t{\n\t\t\t\tRTC::TransportTuple* storedTuple = std::addressof(*it);\n\n\t\t\t\tif (storedTuple->Compare(tuple))\n\t\t\t\t{\n\t\t\t\t\tremovedTuple = storedTuple;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If not found, ignore.\n\t\t\tif (!removedTuple)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Notify the listener.\n\t\t\tthis->isRemovingTuples = true;\n\t\t\tthis->listener->OnIceServerTupleRemoved(this, removedTuple);\n\t\t\tthis->isRemovingTuples = false;\n\n\t\t\t// Remove it from the list of tuples.\n\t\t\t// NOTE: Do it after notifying the listener since the listener may need to\n\t\t\t// use/read the tuple being removed so we cannot free it yet.\n\t\t\tthis->tuples.erase(it);\n\n\t\t\t// If this is the selected tuple, do things.\n\t\t\tif (removedTuple == this->selectedTuple)\n\t\t\t{\n\t\t\t\tthis->selectedTuple = nullptr;\n\n\t\t\t\t// Mark the first tuple as selected tuple (if any) but only if state was\n\t\t\t\t// 'connected' or 'completed'.\n\t\t\t\tif (\n\t\t\t\t  (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED) &&\n\t\t\t\t  this->tuples.begin() != this->tuples.end())\n\t\t\t\t{\n\t\t\t\t\tSetSelectedTuple(std::addressof(*this->tuples.begin()));\n\n\t\t\t\t\t// Restart ICE consent check to let the client send new consent requests\n\t\t\t\t\t// on the new selected tuple.\n\t\t\t\t\tif (IsConsentCheckSupported())\n\t\t\t\t\t{\n\t\t\t\t\t\tRestartConsentCheck();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Or just emit 'disconnected'.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Update state.\n\t\t\t\t\tthis->state = IceState::DISCONNECTED;\n\n\t\t\t\t\t// Reset remote nomination.\n\t\t\t\t\tthis->remoteNomination = 0u;\n\n\t\t\t\t\t// Notify the listener.\n\t\t\t\t\tthis->listener->OnIceServerDisconnected(this);\n\n\t\t\t\t\tif (IsConsentCheckSupported() && IsConsentCheckRunning())\n\t\t\t\t\t{\n\t\t\t\t\t\tStopConsentCheck();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid IceServer::ProcessStunRequest(const RTC::ICE::StunPacket* request, RTC::TransportTuple* tuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_DEV(\"processing STUN request\");\n\n\t\t\t// Must be a Binding method.\n\t\t\tif (request->GetMethod() != RTC::ICE::StunPacket::Method::BINDING)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"STUN request with unknown method %#.3x => 400\",\n\t\t\t\t  static_cast<unsigned int>(request->GetMethod()));\n\n\t\t\t\t// Reply 400.\n\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t  StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 400, \"unknown method\");\n\n\t\t\t\tresponse->Protect();\n\n\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\tdelete response;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Must have FINGERPRINT attribute.\n\t\t\tif (!request->HasAttribute(StunPacket::AttributeType::FINGERPRINT))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"STUN Binding request without FINGERPRINT attribute => 400\");\n\n\t\t\t\t// Reply 400.\n\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t  StunResponseFactoryBuffer,\n\t\t\t\t  sizeof(StunResponseFactoryBuffer),\n\t\t\t\t  400,\n\t\t\t\t  \"missing FINGERPRINT attribute in STUN Binding request\");\n\n\t\t\t\tresponse->Protect();\n\n\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\tdelete response;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// PRIORITY attribute is required.\n\t\t\tif (!request->HasAttribute(StunPacket::AttributeType::PRIORITY))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"STUN Binding request without PRIORITY attribute => 400\");\n\n\t\t\t\t// Reply 400.\n\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t  StunResponseFactoryBuffer,\n\t\t\t\t  sizeof(StunResponseFactoryBuffer),\n\t\t\t\t  400,\n\t\t\t\t  \"missing PRIORITY attribute in STUN Binding request\");\n\n\t\t\t\tresponse->Protect();\n\n\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\tdelete response;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check authentication.\n\t\t\tswitch (request->CheckAuthentication(this->usernameFragment, this->password))\n\t\t\t{\n\t\t\t\tcase RTC::ICE::StunPacket::AuthenticationResult::OK:\n\t\t\t\t{\n\t\t\t\t\tif (!this->oldUsernameFragment.empty() && !this->oldPassword.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(ice, \"new ICE credentials applied\");\n\n\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\tthis->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment);\n\n\t\t\t\t\t\tthis->oldUsernameFragment.clear();\n\t\t\t\t\t\tthis->oldPassword.clear();\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::ICE::StunPacket::AuthenticationResult::UNAUTHORIZED:\n\t\t\t\t{\n\t\t\t\t\t// We may have changed our usernameFragment and password, so check the\n\t\t\t\t\t// old ones.\n\t\t\t\t\tif (\n\t\t\t\t\t  !this->oldUsernameFragment.empty() && !this->oldPassword.empty() &&\n\t\t\t\t\t  request->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) ==\n\t\t\t\t\t    RTC::ICE::StunPacket::AuthenticationResult::OK)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(ice, \"using old ICE credentials\");\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_WARN_TAG(ice, \"wrong authentication in STUN Binding request => 401\");\n\n\t\t\t\t\t// Reply 401.\n\t\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t\t  StunResponseFactoryBuffer,\n\t\t\t\t\t  sizeof(StunResponseFactoryBuffer),\n\t\t\t\t\t  401,\n\t\t\t\t\t  \"wrong authentication in STUN Binding request\");\n\n\t\t\t\t\tresponse->Protect();\n\n\t\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\t\tdelete response;\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::ICE::StunPacket::AuthenticationResult::BAD_MESSAGE:\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(ice, \"cannot check authentication in STUN Binding request => 400\");\n\n\t\t\t\t\t// Reply 400.\n\t\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t\t  StunResponseFactoryBuffer,\n\t\t\t\t\t  sizeof(StunResponseFactoryBuffer),\n\t\t\t\t\t  400,\n\t\t\t\t\t  \"cannot check authentication in STUN Binding request\");\n\n\t\t\t\t\tresponse->Protect();\n\n\t\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\t\tdelete response;\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// The remote peer must be ICE controlling.\n\t\t\tif (request->GetIceControlled())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"peer indicates ICE-CONTROLLED in STUN Binding request => 487\");\n\n\t\t\t\t// Reply 487 (Role Conflict).\n\t\t\t\tauto* response = request->CreateErrorResponse(\n\t\t\t\t  StunResponseFactoryBuffer,\n\t\t\t\t  sizeof(StunResponseFactoryBuffer),\n\t\t\t\t  487,\n\t\t\t\t  \"invalid ICE-CONTROLLED attribute in STUN Binding request\");\n\n\t\t\t\tresponse->Protect();\n\n\t\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\t\tdelete response;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"valid STUN Binding request [priority:%\" PRIu32 \", useCandidate:%s]\",\n\t\t\t  static_cast<uint32_t>(request->GetPriority()),\n\t\t\t  request->HasAttribute(RTC::ICE::StunPacket::AttributeType::USE_CANDIDATE) ? \"true\" : \"false\");\n\n\t\t\t// Create a success response.\n\t\t\tauto* response =\n\t\t\t  request->CreateSuccessResponse(StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer));\n\n\t\t\t// Add XOR-MAPPED-ADDRESS.\n\t\t\tresponse->AddXorMappedAddress(tuple->GetRemoteAddress());\n\n\t\t\tif (this->oldPassword.empty())\n\t\t\t{\n\t\t\t\tresponse->Protect(this->password);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tresponse->Protect(this->oldPassword);\n\t\t\t}\n\n\t\t\tthis->listener->OnIceServerSendStunPacket(this, response, tuple);\n\n\t\t\tdelete response;\n\n\t\t\t// Handle the tuple.\n\t\t\tHandleTuple(\n\t\t\t  tuple,\n\t\t\t  request->HasAttribute(StunPacket::AttributeType::USE_CANDIDATE),\n\t\t\t  request->HasAttribute(StunPacket::AttributeType::NOMINATION),\n\t\t\t  request->GetNomination());\n\n\t\t\t// If state is 'connected' or 'completed' after handling the tuple, then\n\t\t\t// start or restart ICE consent check (if supported).\n\t\t\tif (IsConsentCheckSupported() && (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED))\n\t\t\t{\n\t\t\t\tif (IsConsentCheckRunning())\n\t\t\t\t{\n\t\t\t\t\tRestartConsentCheck();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tStartConsentCheck();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid IceServer::ProcessStunIndication(const RTC::ICE::StunPacket* /*indication*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_DEV(\"STUN indication received, ignored\");\n\n\t\t\t// Nothig else to do. We just ignore STUN indications.\n\t\t}\n\n\t\tvoid IceServer::ProcessStunResponse(const RTC::ICE::StunPacket* response)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (response->GetClass() == RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"ignoring received STUN successs response\", responseType.c_str());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstatic thread_local std::string_view errorReasonPhrase;\n\n\t\t\t\tresponse->GetErrorCode(errorReasonPhrase);\n\n\t\t\t\tMS_DEBUG_DEV(\"ignoring received STUN error response [errorCode:%\" PRIu16 \", reasonPhrase:\\\"%.*s\\\"\"], static_cast<int>(errorReasonPhrase.size()), errorReasonPhrase.data());\n\t\t\t}\n\n\t\t\t// Nothig else to do. We just ignore STUN responses because we do not\n\t\t\t// generate STUN requests.\n\t\t}\n\n\t\tvoid IceServer::MayForceSelectedTuple(const RTC::TransportTuple* tuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state != IceState::CONNECTED && this->state != IceState::COMPLETED)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"cannot force selected tuple if not in state 'connected' or 'completed'\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto* storedTuple = HasTuple(tuple);\n\n\t\t\tif (!storedTuple)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"cannot force selected tuple if the given tuple was not already a valid one\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tSetSelectedTuple(storedTuple);\n\t\t}\n\n\t\tvoid IceServer::HandleTuple(\n\t\t  RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase IceState::NEW:\n\t\t\t\t{\n\t\t\t\t\t// There shouldn't be a selected tuple.\n\t\t\t\t\tMS_ASSERT(!this->selectedTuple, \"state is 'new' but there is selected tuple\");\n\n\t\t\t\t\tif (!hasUseCandidate && !hasNomination)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t  \"transition from state 'new' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%\" PRIu32\n\t\t\t\t\t\t  \"]\",\n\t\t\t\t\t\t  hasUseCandidate ? \"true\" : \"false\",\n\t\t\t\t\t\t  hasNomination ? \"true\" : \"false\",\n\t\t\t\t\t\t  nomination);\n\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple = AddTuple(tuple);\n\n\t\t\t\t\t\t// Update state.\n\t\t\t\t\t\tthis->state = IceState::CONNECTED;\n\n\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\tthis->listener->OnIceServerConnected(this);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple          = AddTuple(tuple);\n\t\t\t\t\t\tconst auto isNewNomination = hasNomination && nomination > this->remoteNomination;\n\n\t\t\t\t\t\tif (isNewNomination || !hasNomination)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"transition from state 'new' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%\" PRIu32\n\t\t\t\t\t\t\t  \"]\",\n\t\t\t\t\t\t\t  hasUseCandidate ? \"true\" : \"false\",\n\t\t\t\t\t\t\t  hasNomination ? \"true\" : \"false\",\n\t\t\t\t\t\t\t  nomination);\n\n\t\t\t\t\t\t\t// Update state.\n\t\t\t\t\t\t\tthis->state = IceState::COMPLETED;\n\n\t\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t\t// Update nomination.\n\t\t\t\t\t\t\tif (isNewNomination)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->remoteNomination = nomination;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\t\tthis->listener->OnIceServerCompleted(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase IceState::DISCONNECTED:\n\t\t\t\t{\n\t\t\t\t\t// There shouldn't be a selected tuple.\n\t\t\t\t\tMS_ASSERT(!this->selectedTuple, \"state is 'disconnected' but there is selected tuple\");\n\n\t\t\t\t\tif (!hasUseCandidate && !hasNomination)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t  \"transition from state 'disconnected' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%\" PRIu32\n\t\t\t\t\t\t  \"]\",\n\t\t\t\t\t\t  hasUseCandidate ? \"true\" : \"false\",\n\t\t\t\t\t\t  hasNomination ? \"true\" : \"false\",\n\t\t\t\t\t\t  nomination);\n\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple = AddTuple(tuple);\n\n\t\t\t\t\t\t// Update state.\n\t\t\t\t\t\tthis->state = IceState::CONNECTED;\n\n\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\tthis->listener->OnIceServerConnected(this);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple          = AddTuple(tuple);\n\t\t\t\t\t\tconst auto isNewNomination = hasNomination && nomination > this->remoteNomination;\n\n\t\t\t\t\t\tif (isNewNomination || !hasNomination)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"transition from state 'disconnected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%\" PRIu32\n\t\t\t\t\t\t\t  \"]\",\n\t\t\t\t\t\t\t  hasUseCandidate ? \"true\" : \"false\",\n\t\t\t\t\t\t\t  hasNomination ? \"true\" : \"false\",\n\t\t\t\t\t\t\t  nomination);\n\n\t\t\t\t\t\t\t// Update state.\n\t\t\t\t\t\t\tthis->state = IceState::COMPLETED;\n\n\t\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t\t// Update nomination.\n\t\t\t\t\t\t\tif (isNewNomination)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->remoteNomination = nomination;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\t\tthis->listener->OnIceServerCompleted(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase IceState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\t// There should be some tuples.\n\t\t\t\t\tMS_ASSERT(!this->tuples.empty(), \"state is 'connected' but there are no tuples\");\n\n\t\t\t\t\t// There should be a selected tuple.\n\t\t\t\t\tMS_ASSERT(this->selectedTuple, \"state is 'connected' but there is not selected tuple\");\n\n\t\t\t\t\tif (!hasUseCandidate && !hasNomination)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tAddTuple(tuple);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t  \"transition from state 'connected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%\" PRIu32\n\t\t\t\t\t\t  \"]\",\n\t\t\t\t\t\t  hasUseCandidate ? \"true\" : \"false\",\n\t\t\t\t\t\t  hasNomination ? \"true\" : \"false\",\n\t\t\t\t\t\t  nomination);\n\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple          = AddTuple(tuple);\n\t\t\t\t\t\tconst auto isNewNomination = hasNomination && nomination > this->remoteNomination;\n\n\t\t\t\t\t\tif (isNewNomination || !hasNomination)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Update state.\n\t\t\t\t\t\t\tthis->state = IceState::COMPLETED;\n\n\t\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t\t// Update nomination.\n\t\t\t\t\t\t\tif (isNewNomination)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->remoteNomination = nomination;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Notify the listener.\n\t\t\t\t\t\t\tthis->listener->OnIceServerCompleted(this);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase IceState::COMPLETED:\n\t\t\t\t{\n\t\t\t\t\t// There should be some tuples.\n\t\t\t\t\tMS_ASSERT(!this->tuples.empty(), \"state is 'completed' but there are no tuples\");\n\n\t\t\t\t\t// There should be a selected tuple.\n\t\t\t\t\tMS_ASSERT(this->selectedTuple, \"state is 'completed' but there is not selected tuple\");\n\n\t\t\t\t\tif (!hasUseCandidate && !hasNomination)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tAddTuple(tuple);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Store the tuple.\n\t\t\t\t\t\tauto* storedTuple          = AddTuple(tuple);\n\t\t\t\t\t\tconst auto isNewNomination = hasNomination && nomination > this->remoteNomination;\n\n\t\t\t\t\t\t// When in completed state, update selected tuple if there is ICE\n\t\t\t\t\t\t// nomination or useCandidate.\n\t\t\t\t\t\tif (isNewNomination || hasUseCandidate)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Mark it as selected tuple.\n\t\t\t\t\t\t\tSetSelectedTuple(storedTuple);\n\n\t\t\t\t\t\t\t// Update nomination.\n\t\t\t\t\t\t\tif (isNewNomination)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->remoteNomination = nomination;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tRTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* storedTuple = HasTuple(tuple);\n\n\t\t\tif (storedTuple)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"tuple already exists\");\n\n\t\t\t\treturn storedTuple;\n\t\t\t}\n\n\t\t\t// Add the new tuple at the beginning of the list.\n\t\t\tthis->tuples.push_front(*tuple);\n\n\t\t\tstoredTuple = std::addressof(*this->tuples.begin());\n\n\t\t\t// If it is UDP then we must store the remote address (until now it is\n\t\t\t// just a pointer that will be freed soon).\n\t\t\tif (storedTuple->GetProtocol() == TransportTuple::Protocol::UDP)\n\t\t\t{\n\t\t\t\tstoredTuple->StoreUdpRemoteAddress();\n\t\t\t}\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnIceServerTupleAdded(this, storedTuple);\n\n\t\t\t// Don't allow more than MaxTuples.\n\t\t\tif (this->tuples.size() > MaxTuples)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"too too many tuples, removing the oldest non selected one\");\n\n\t\t\t\t// Find the oldest tuple which is neither the added one nor the selected\n\t\t\t\t// one (if any), and remove it.\n\t\t\t\tRTC::TransportTuple* removedTuple{ nullptr };\n\t\t\t\tauto it = this->tuples.rbegin();\n\n\t\t\t\tfor (; it != this->tuples.rend(); ++it)\n\t\t\t\t{\n\t\t\t\t\tRTC::TransportTuple* otherStoredTuple = std::addressof(*it);\n\n\t\t\t\t\tif (otherStoredTuple != storedTuple && otherStoredTuple != this->selectedTuple)\n\t\t\t\t\t{\n\t\t\t\t\t\tremovedTuple = otherStoredTuple;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// This should not happen by design.\n\t\t\t\tMS_ASSERT(removedTuple, \"couldn't find any tuple to be removed\");\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->isRemovingTuples = true;\n\t\t\t\tthis->listener->OnIceServerTupleRemoved(this, removedTuple);\n\t\t\t\tthis->isRemovingTuples = false;\n\n\t\t\t\t// Remove it from the list of tuples.\n\t\t\t\t// NOTE: Do it after notifying the listener since the listener may need to\n\t\t\t\t// use/read the tuple being removed so we cannot free it yet.\n\t\t\t\t// NOTE: This trick is needed since it is a reverse_iterator and\n\t\t\t\t// erase() requires a iterator, const_iterator or bidirectional_iterator.\n\t\t\t\tthis->tuples.erase(std::next(it).base());\n\t\t\t}\n\n\t\t\t// Return the address of the inserted tuple.\n\t\t\treturn storedTuple;\n\t\t}\n\n\t\tRTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Check the current selected tuple (if any).\n\t\t\tif (this->selectedTuple && this->selectedTuple->Compare(tuple))\n\t\t\t{\n\t\t\t\treturn this->selectedTuple;\n\t\t\t}\n\n\t\t\t// Otherwise check other stored tuples.\n\t\t\tfor (const auto& it : this->tuples)\n\t\t\t{\n\t\t\t\tauto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));\n\n\t\t\t\tif (storedTuple->Compare(tuple))\n\t\t\t\t{\n\t\t\t\t\treturn storedTuple;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nullptr;\n\t\t}\n\n\t\tvoid IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// If already the selected tuple do nothing.\n\t\t\tif (storedTuple == this->selectedTuple)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->selectedTuple = storedTuple;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnIceServerSelectedTuple(this, this->selectedTuple);\n\t\t}\n\n\t\tvoid IceServer::StartConsentCheck()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(IsConsentCheckSupported(), \"ICE consent check not supported\");\n\t\t\tMS_ASSERT(!IsConsentCheckRunning(), \"ICE consent check already running\");\n\t\t\tMS_ASSERT(this->selectedTuple, \"no selected tuple\");\n\n\t\t\t// Create the ICE consent check timer if it doesn't exist.\n\t\t\tif (!this->consentCheckTimer)\n\t\t\t{\n\t\t\t\tthis->consentCheckTimer = this->shared->CreateTimer(this);\n\t\t\t}\n\n\t\t\tthis->consentCheckTimer->Start(this->consentTimeoutMs);\n\t\t}\n\n\t\tvoid IceServer::RestartConsentCheck()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(IsConsentCheckSupported(), \"ICE consent check not supported\");\n\t\t\tMS_ASSERT(IsConsentCheckRunning(), \"ICE consent check not running\");\n\t\t\tMS_ASSERT(this->selectedTuple, \"no selected tuple\");\n\n\t\t\tthis->consentCheckTimer->Restart();\n\t\t}\n\n\t\tvoid IceServer::StopConsentCheck()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(IsConsentCheckSupported(), \"ICE consent check not supported\");\n\t\t\tMS_ASSERT(IsConsentCheckRunning(), \"ICE consent check not running\");\n\n\t\t\tthis->consentCheckTimer->Stop();\n\t\t}\n\n\t\tinline void IceServer::OnTimer(TimerHandleInterface* timer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (timer == this->consentCheckTimer)\n\t\t\t{\n\t\t\t\tMS_ASSERT(IsConsentCheckSupported(), \"ICE consent check not supported\");\n\n\t\t\t\t// State must be 'connected' or 'completed'.\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  this->state == IceState::COMPLETED || this->state == IceState::CONNECTED,\n\t\t\t\t  \"ICE consent check timer fired but state is neither 'completed' nor 'connected'\");\n\n\t\t\t\t// There should be a selected tuple.\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  this->selectedTuple, \"ICE consent check timer fired but there is not selected tuple\");\n\n\t\t\t\tMS_WARN_TAG(ice, \"ICE consent expired due to timeout, moving to 'disconnected' state\");\n\n\t\t\t\t// Update state.\n\t\t\t\tthis->state = IceState::DISCONNECTED;\n\n\t\t\t\t// Reset remote nomination.\n\t\t\t\tthis->remoteNomination = 0u;\n\n\t\t\t\t// Clear all tuples.\n\t\t\t\tthis->isRemovingTuples = true;\n\n\t\t\t\tfor (const auto& it : this->tuples)\n\t\t\t\t{\n\t\t\t\t\tauto* storedTuple = const_cast<RTC::TransportTuple*>(std::addressof(it));\n\n\t\t\t\t\t// Notify the listener.\n\t\t\t\t\tthis->listener->OnIceServerTupleRemoved(this, storedTuple);\n\t\t\t\t}\n\n\t\t\t\tthis->isRemovingTuples = false;\n\n\t\t\t\t// Clear all tuples.\n\t\t\t\t// NOTE: Do it after notifying the listener since the listener may need to\n\t\t\t\t// use/read the tuple being removed so we cannot free it yet.\n\t\t\t\tthis->tuples.clear();\n\n\t\t\t\t// Unset selected tuple.\n\t\t\t\tthis->selectedTuple = nullptr;\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnIceServerDisconnected(this);\n\t\t\t}\n\t\t}\n\t} // namespace ICE\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/ICE/StunPacket.cpp",
    "content": "#define MS_CLASS \"RTC::ICE::StunPacket\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/ICE/StunPacket.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstdio>  // std::snprintf()\n#include <cstring> // std::memcmp(), std::memcpy(), std::memset()\n#include <string>\n\nnamespace RTC\n{\n\tnamespace ICE\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr size_t AttributeFactoryBufferLength{ 65536 };\n\t\tstatic thread_local uint8_t AttributeFactoryBuffer[AttributeFactoryBufferLength];\n\n\t\t/* Class variables. */\n\n\t\tconst uint8_t StunPacket::MagicCookie[] = { 0x21, 0x12, 0xA4, 0x42 };\n\n\t\t/* Class methods. */\n\n\t\tbool StunPacket::IsStun(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\treturn (\n\t\t\t  // STUN headers are 20 bytes.\n\t\t\t  (bufferLength >= StunPacket::FixedHeaderLength) &&\n\t\t\t  // @see RFC 7983.\n\t\t\t  (buffer[0] < 3) &&\n\t\t\t  // Magic Cookie must match.\n\t\t\t  (buffer[4] == StunPacket::MagicCookie[0]) && (buffer[5] == StunPacket::MagicCookie[1]) &&\n\t\t\t  (buffer[6] == StunPacket::MagicCookie[2]) && (buffer[7] == StunPacket::MagicCookie[3]));\n\t\t}\n\n\t\tStunPacket* StunPacket::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!StunPacket::IsStun(buffer, bufferLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"not a STUN Packet\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* packet = new StunPacket(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// `bufferLength` must be the exact length of the STUN Packet, so let's\n\t\t\t// assign it immediately.\n\t\t\tpacket->SetLength(bufferLength);\n\n\t\t\t// Get STUN Message Type field.\n\t\t\tconst uint16_t typeField = Utils::Byte::Get2Bytes(buffer, 0);\n\n\t\t\t// Get STUN class.\n\t\t\tconst auto klass =\n\t\t\t  static_cast<StunPacket::Class>(((buffer[0] & 0x01) << 1) | ((buffer[1] & 0x10) >> 4));\n\n\t\t\t// Get STUN method.\n\t\t\tconst auto method = static_cast<StunPacket::Method>(\n\t\t\t  (typeField & 0x000f) | ((typeField & 0x00e0) >> 1) | ((typeField & 0x3E00) >> 2));\n\n\t\t\tpacket->klass  = klass;\n\t\t\tpacket->method = method;\n\n\t\t\tif (!packet->Validate(/*storeAttributes*/ true))\n\t\t\t{\n\t\t\t\tdelete packet;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tStunPacket* StunPacket::Factory(\n\t\t  uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  StunPacket::Class klass,\n\t\t  StunPacket::Method method,\n\t\t  const uint8_t* transactionId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < StunPacket::FixedHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"no space for fixed header\");\n\t\t\t}\n\n\t\t\tauto* packet = new StunPacket(buffer, bufferLength);\n\n\t\t\tstd::memset(packet->GetFixedHeaderPointer(), 0x00, packet->GetLength());\n\n\t\t\tpacket->klass  = klass;\n\t\t\tpacket->method = method;\n\n\t\t\t// Merge class and method fields into type.\n\t\t\tauto typeField = (static_cast<uint16_t>(method) & 0x0f80) << 2;\n\n\t\t\ttypeField |= (static_cast<uint16_t>(method) & 0x0070) << 1;\n\t\t\ttypeField |= (static_cast<uint16_t>(method) & 0x000f);\n\t\t\ttypeField |= (static_cast<uint16_t>(klass) & 0x02) << 7;\n\t\t\ttypeField |= (static_cast<uint16_t>(klass) & 0x01) << 4;\n\n\t\t\t// Set type field.\n\t\t\tUtils::Byte::Set2Bytes(packet->GetFixedHeaderPointer(), 0, typeField);\n\n\t\t\t// NOTE: No need to write message length since it's already 0.\n\n\t\t\t// Set magic Cookie.\n\t\t\tstd::memcpy(packet->GetFixedHeaderPointer() + 4, StunPacket::MagicCookie, 4);\n\n\t\t\tif (transactionId)\n\t\t\t{\n\t\t\t\tstd::memcpy(packet->GetTransactionIdPointer(), transactionId, StunPacket::TransactionIdLength);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tUtils::Crypto::WriteRandomBytes(\n\t\t\t\t  packet->GetTransactionIdPointer(), StunPacket::TransactionIdLength);\n\t\t\t}\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum STUN Packet length.\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tStunPacket* StunPacket::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn Factory(buffer, bufferLength, klass, method, nullptr);\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tStunPacket::StunPacket(uint8_t* buffer, size_t bufferLength)\n\t\t  : Serializable(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(StunPacket::FixedHeaderLength);\n\t\t}\n\n\t\tStunPacket::~StunPacket()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid StunPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ICE::StunPacket>\");\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %zu (buffer length: %zu)\", GetLength(), GetBufferLength());\n\n\t\t\tstd::string klass;\n\n\t\t\tswitch (this->klass)\n\t\t\t{\n\t\t\t\tcase Class::REQUEST:\n\t\t\t\t{\n\t\t\t\t\tklass = \"request\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Class::INDICATION:\n\t\t\t\t{\n\t\t\t\t\tklass = \"indication\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Class::SUCCESS_RESPONSE:\n\t\t\t\t{\n\t\t\t\t\tklass = \"success response\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Class::ERROR_RESPONSE:\n\t\t\t\t{\n\t\t\t\t\tklass = \"error response\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase Class::UNSET:\n\t\t\t\t{\n\t\t\t\t\tklass = \"(unset)\";\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this->method == Method::BINDING)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  method and class: Binding %s\", klass.c_str());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// This prints the unknown method number. Example: TURN Allocate => 0x003.\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  method & class: %s with unknown method %#.3x\",\n\t\t\t\t  klass.c_str(),\n\t\t\t\t  static_cast<uint16_t>(this->method));\n\t\t\t}\n\n\t\t\tchar transactionId[(12 * 2) + 1]; // 12 bytes × 2 hex chars + null terminator.\n\n\t\t\tfor (uint8_t i{ 0 }; i < 12; ++i)\n\t\t\t{\n\t\t\t\tstd::snprintf(transactionId + (i * 2), 3, \"%02X\", GetTransactionId()[i]);\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  transaction id: 0x%s\", transactionId);\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  attributes length: %zu\", GetAttributesLength());\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  <Attributes>\");\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::USERNAME))\n\t\t\t{\n\t\t\t\tconst auto username = GetUsername();\n\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation + 1, \"  username: \\\"%.*s\\\"\", static_cast<int>(username.size()), username.data());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::PRIORITY))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  priority: %\" PRIu32, GetPriority());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::ICE_CONTROLLING))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  ice controlling: %\" PRIu64, GetIceControlling());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::ICE_CONTROLLED))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  ice controlled: %\" PRIu64, GetIceControlled());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::USE_CANDIDATE))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  use candidate: yes\");\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::NOMINATION))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  nomination: %\" PRIu32, GetNomination());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::SOFTWARE))\n\t\t\t{\n\t\t\t\tconst auto software = GetSoftware();\n\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation + 1, \"  software: \\\"%.*s\\\"\", static_cast<int>(software.size()), software.data());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::XOR_MAPPED_ADDRESS))\n\t\t\t{\n\t\t\t\tstruct sockaddr_storage xorMappedAddressStorage{};\n\n\t\t\t\tif (GetXorMappedAddress(std::addressof(xorMappedAddressStorage)))\n\t\t\t\t{\n\t\t\t\t\tint family;\n\t\t\t\t\tuint16_t port;\n\t\t\t\t\tstd::string ip;\n\n\t\t\t\t\tUtils::IP::GetAddressInfo(\n\t\t\t\t\t  reinterpret_cast<sockaddr*>(std::addressof(xorMappedAddressStorage)), family, ip, port);\n\n\t\t\t\t\tif (family == AF_INET)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  xor mapped address: IPv4 %s:%\" PRIu16, ip.c_str(), port);\n\t\t\t\t\t}\n\t\t\t\t\telse if (family == AF_INET6)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1, \"  xor mapped address: IPv6 [%s]:%\" PRIu16, ip.c_str(), port);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::ERROR_CODE))\n\t\t\t{\n\t\t\t\tstd::string_view reasonPhrase{};\n\n\t\t\t\tconst auto errorCode = GetErrorCode(reasonPhrase);\n\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation + 1,\n\t\t\t\t  \"  error code: %\" PRIu16 \" (reason phrase: \\\"%.*s\\\")\",\n\t\t\t\t  errorCode,\n\t\t\t\t  static_cast<int>(reasonPhrase.size()),\n\t\t\t\t  reasonPhrase.data());\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY))\n\t\t\t{\n\t\t\t\tchar messageIntegrity[41];\n\n\t\t\t\tfor (size_t i{ 0 }; i < StunPacket::MessageIntegrityAttributeLength; ++i)\n\t\t\t\t{\n\t\t\t\t\tstd::snprintf(messageIntegrity + (i * 2), 3, \"%.2x\", GetMessageIntegrity()[i]);\n\t\t\t\t}\n\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  message integrity: %s\", messageIntegrity);\n\t\t\t}\n\n\t\t\tif (HasAttribute(StunPacket::AttributeType::FINGERPRINT))\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  fingerprint: %\" PRIu32, GetFingerprint());\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  </Attributes>\");\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ICE::StunPacket>\");\n\t\t}\n\n\t\tStunPacket* StunPacket::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedPacket = new StunPacket(buffer, bufferLength);\n\n\t\t\tSerializable::CloneInto(clonedPacket);\n\n\t\t\t// Clone private members.\n\t\t\tclonedPacket->klass      = this->klass;\n\t\t\tclonedPacket->method     = this->method;\n\t\t\tclonedPacket->attributes = this->attributes;\n\n\t\t\treturn clonedPacket;\n\t\t}\n\n\t\tvoid StunPacket::AddUsername(const std::string_view username)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tif (username.length() > StunPacket::UsernameAttributeMaxLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"Attribute USERNAME must be at most %zu bytes\", StunPacket::UsernameAttributeMaxLength);\n\t\t\t}\n\n\t\t\tStoreNewAttribute(StunPacket::AttributeType::USERNAME, username.data(), username.length());\n\t\t}\n\n\t\tvoid StunPacket::AddPriority(uint32_t priority)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tUtils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, priority);\n\n\t\t\tStoreNewAttribute(StunPacket::AttributeType::PRIORITY, AttributeFactoryBuffer, sizeof(priority));\n\t\t}\n\n\t\tvoid StunPacket::AddIceControlling(uint64_t iceControlling)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tUtils::Byte::Set8Bytes(AttributeFactoryBuffer, 0, iceControlling);\n\n\t\t\tStoreNewAttribute(\n\t\t\t  StunPacket::AttributeType::ICE_CONTROLLING, AttributeFactoryBuffer, sizeof(iceControlling));\n\t\t}\n\n\t\tvoid StunPacket::AddIceControlled(uint64_t iceControlled)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tUtils::Byte::Set8Bytes(AttributeFactoryBuffer, 0, iceControlled);\n\n\t\t\tStoreNewAttribute(\n\t\t\t  StunPacket::AttributeType::ICE_CONTROLLED, AttributeFactoryBuffer, sizeof(iceControlled));\n\t\t}\n\n\t\tvoid StunPacket::AddUseCandidate()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tStoreNewAttribute(StunPacket::AttributeType::USE_CANDIDATE, nullptr, 0);\n\t\t}\n\n\t\tvoid StunPacket::AddNomination(uint32_t nomination)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tUtils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, nomination);\n\n\t\t\tStoreNewAttribute(\n\t\t\t  StunPacket::AttributeType::NOMINATION, AttributeFactoryBuffer, sizeof(nomination));\n\t\t}\n\n\t\tvoid StunPacket::AddSoftware(const std::string_view software)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tif (software.length() > StunPacket::SoftwareAttributeMaxLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"Attribute SOFTWARE must be at most %zu bytes\", StunPacket::SoftwareAttributeMaxLength);\n\t\t\t}\n\n\t\t\tStoreNewAttribute(StunPacket::AttributeType::SOFTWARE, software.data(), software.length());\n\t\t}\n\n\t\tbool StunPacket::GetXorMappedAddress(struct sockaddr_storage* xorMappedAddressStorage) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memset(xorMappedAddressStorage, 0x00, sizeof(struct sockaddr_storage));\n\n\t\t\tconst auto* attribute = GetAttribute(StunPacket::AttributeType::XOR_MAPPED_ADDRESS);\n\n\t\t\tif (!attribute)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst auto* attributeValue = GetAttributeValue(attribute);\n\t\t\tconst uint8_t family       = attributeValue[1];\n\t\t\tuint16_t port;\n\n\t\t\tstd::memcpy(std::addressof(port), attributeValue + 2, 2);\n\n\t\t\t// XOR with the first 2 bytes of the Magic Cookie.\n\t\t\tport = ntohs(port) ^ (StunPacket::MagicCookie[0] << 8 | StunPacket::MagicCookie[1]);\n\n\t\t\t// IPv4.\n\t\t\tif (family == 0x01)\n\t\t\t{\n\t\t\t\tif (attribute->len != StunPacket::XorMappedAddressIPv4Length)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"cannot get XOR_MAPPED_ADDRESS Attribute value, length of the Attribute is not %zu\",\n\t\t\t\t\t  StunPacket::XorMappedAddressIPv4Length);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tauto* addr4 = reinterpret_cast<struct sockaddr_in*>(xorMappedAddressStorage);\n\n\t\t\t\taddr4->sin_family = AF_INET;\n\t\t\t\taddr4->sin_port   = htons(port);\n\n\t\t\t\tuint32_t addr;\n\t\t\t\tstd::memcpy(std::addressof(addr), attributeValue + 4, 4);\n\n\t\t\t\t// XOR with each byte of the Magic Cookie.\n\t\t\t\tauto* addrBytes = reinterpret_cast<uint8_t*>(&addr);\n\n\t\t\t\tfor (size_t i{ 0 }; i < sizeof(StunPacket::MagicCookie); ++i)\n\t\t\t\t{\n\t\t\t\t\taddrBytes[i] ^= StunPacket::MagicCookie[i];\n\t\t\t\t}\n\n\t\t\t\taddr4->sin_addr.s_addr = addr;\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// IPv6.\n\t\t\telse if (family == 0x02)\n\t\t\t{\n\t\t\t\tif (attribute->len != StunPacket::XorMappedAddressIPv6Length)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"cannot get XOR_MAPPED_ADDRESS Attribute value, length of the Attribute is not %zu\",\n\t\t\t\t\t  StunPacket::XorMappedAddressIPv6Length);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tauto* addr6 = reinterpret_cast<struct sockaddr_in6*>(xorMappedAddressStorage);\n\n\t\t\t\taddr6->sin6_family = AF_INET6;\n\t\t\t\taddr6->sin6_port   = htons(port);\n\n\t\t\t\tconst auto* transactionId = GetTransactionId();\n\n\t\t\t\t// XOR with first 4 bytes with each byte of the Magic Cookie.\n\t\t\t\tfor (size_t i{ 0 }; i < sizeof(StunPacket::MagicCookie); ++i)\n\t\t\t\t{\n\t\t\t\t\taddr6->sin6_addr.s6_addr[i] =\n\t\t\t\t\t  attributeValue[sizeof(StunPacket::MagicCookie) + i] ^ StunPacket::MagicCookie[i];\n\t\t\t\t}\n\n\t\t\t\t// XOR remaining 12 bytes with each byte of the Transaction id.\n\t\t\t\tfor (size_t i{ 0 }; i < StunPacket::TransactionIdLength; ++i)\n\t\t\t\t{\n\t\t\t\t\taddr6->sin6_addr.s6_addr[sizeof(StunPacket::MagicCookie) + i] =\n\t\t\t\t\t  attributeValue[(sizeof(StunPacket::MagicCookie) + sizeof(StunPacket::MagicCookie)) + i] ^\n\t\t\t\t\t  transactionId[i];\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// Unknown family.\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(ice, \"cannot get XOR_MAPPED_ADDRESS Attribute value, unknown family\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tvoid StunPacket::AddXorMappedAddress(const struct sockaddr* xorMappedAddress)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tswitch (xorMappedAddress->sa_family)\n\t\t\t{\n\t\t\t\tcase AF_INET:\n\t\t\t\t{\n\t\t\t\t\t// Set first byte to 0.\n\t\t\t\t\tAttributeFactoryBuffer[0] = 0;\n\t\t\t\t\t// Set inet family.\n\t\t\t\t\tAttributeFactoryBuffer[1] = 0x01;\n\t\t\t\t\t// Set port and XOR it.\n\t\t\t\t\tstd::memcpy(\n\t\t\t\t\t  AttributeFactoryBuffer + 2,\n\t\t\t\t\t  &(reinterpret_cast<const struct sockaddr_in*>(xorMappedAddress))->sin_port,\n\t\t\t\t\t  2);\n\t\t\t\t\tAttributeFactoryBuffer[2] ^= StunPacket::MagicCookie[0];\n\t\t\t\t\tAttributeFactoryBuffer[3] ^= StunPacket::MagicCookie[1];\n\t\t\t\t\t// Set address and XOR it.\n\t\t\t\t\tstd::memcpy(\n\t\t\t\t\t  AttributeFactoryBuffer + 4,\n\t\t\t\t\t  &(reinterpret_cast<const struct sockaddr_in*>(xorMappedAddress))->sin_addr.s_addr,\n\t\t\t\t\t  4);\n\t\t\t\t\tAttributeFactoryBuffer[4] ^= StunPacket::MagicCookie[0];\n\t\t\t\t\tAttributeFactoryBuffer[5] ^= StunPacket::MagicCookie[1];\n\t\t\t\t\tAttributeFactoryBuffer[6] ^= StunPacket::MagicCookie[2];\n\t\t\t\t\tAttributeFactoryBuffer[7] ^= StunPacket::MagicCookie[3];\n\n\t\t\t\t\tStoreNewAttribute(\n\t\t\t\t\t  StunPacket::AttributeType::XOR_MAPPED_ADDRESS,\n\t\t\t\t\t  AttributeFactoryBuffer,\n\t\t\t\t\t  StunPacket::XorMappedAddressIPv4Length);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase AF_INET6:\n\t\t\t\t{\n\t\t\t\t\t// Set first byte to 0.\n\t\t\t\t\tAttributeFactoryBuffer[0] = 0;\n\t\t\t\t\t// Set inet family.\n\t\t\t\t\tAttributeFactoryBuffer[1] = 0x02;\n\t\t\t\t\t// Set port and XOR it.\n\t\t\t\t\tstd::memcpy(\n\t\t\t\t\t  AttributeFactoryBuffer + 2,\n\t\t\t\t\t  &(reinterpret_cast<const struct sockaddr_in6*>(xorMappedAddress))->sin6_port,\n\t\t\t\t\t  2);\n\t\t\t\t\tAttributeFactoryBuffer[2] ^= StunPacket::MagicCookie[0];\n\t\t\t\t\tAttributeFactoryBuffer[3] ^= StunPacket::MagicCookie[1];\n\t\t\t\t\t// Set address and XOR it.\n\t\t\t\t\tstd::memcpy(\n\t\t\t\t\t  AttributeFactoryBuffer + 4,\n\t\t\t\t\t  &(reinterpret_cast<const struct sockaddr_in6*>(xorMappedAddress))->sin6_addr.s6_addr,\n\t\t\t\t\t  16);\n\t\t\t\t\tconst auto* transactionId = GetTransactionId();\n\n\t\t\t\t\tAttributeFactoryBuffer[4] ^= StunPacket::MagicCookie[0];\n\t\t\t\t\tAttributeFactoryBuffer[5] ^= StunPacket::MagicCookie[1];\n\t\t\t\t\tAttributeFactoryBuffer[6] ^= StunPacket::MagicCookie[2];\n\t\t\t\t\tAttributeFactoryBuffer[7] ^= StunPacket::MagicCookie[3];\n\t\t\t\t\tAttributeFactoryBuffer[8] ^= transactionId[0];\n\t\t\t\t\tAttributeFactoryBuffer[9] ^= transactionId[1];\n\t\t\t\t\tAttributeFactoryBuffer[10] ^= transactionId[2];\n\t\t\t\t\tAttributeFactoryBuffer[11] ^= transactionId[3];\n\t\t\t\t\tAttributeFactoryBuffer[12] ^= transactionId[4];\n\t\t\t\t\tAttributeFactoryBuffer[13] ^= transactionId[5];\n\t\t\t\t\tAttributeFactoryBuffer[14] ^= transactionId[6];\n\t\t\t\t\tAttributeFactoryBuffer[15] ^= transactionId[7];\n\t\t\t\t\tAttributeFactoryBuffer[16] ^= transactionId[8];\n\t\t\t\t\tAttributeFactoryBuffer[17] ^= transactionId[9];\n\t\t\t\t\tAttributeFactoryBuffer[18] ^= transactionId[10];\n\t\t\t\t\tAttributeFactoryBuffer[19] ^= transactionId[11];\n\n\t\t\t\t\tStoreNewAttribute(\n\t\t\t\t\t  StunPacket::AttributeType::XOR_MAPPED_ADDRESS,\n\t\t\t\t\t  AttributeFactoryBuffer,\n\t\t\t\t\t  StunPacket::XorMappedAddressIPv6Length);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"unknown IP family\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid StunPacket::AddErrorCode(uint16_t errorCode, const std::string_view reasonPhrase)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tconst auto codeClass  = static_cast<uint8_t>(errorCode / 100);\n\t\t\tconst auto codeNumber = static_cast<uint8_t>(errorCode) - (codeClass * 100);\n\n\t\t\tUtils::Byte::Set2Bytes(AttributeFactoryBuffer, 0, 0);\n\t\t\tUtils::Byte::Set1Byte(AttributeFactoryBuffer, 2, codeClass);\n\t\t\tUtils::Byte::Set1Byte(AttributeFactoryBuffer, 3, codeNumber);\n\n\t\t\tstd::memcpy(AttributeFactoryBuffer + 4, reasonPhrase.data(), reasonPhrase.length());\n\n\t\t\tStoreNewAttribute(\n\t\t\t  StunPacket::AttributeType::ERROR_CODE, AttributeFactoryBuffer, 4 + reasonPhrase.length());\n\t\t}\n\n\t\tStunPacket::AuthenticationResult StunPacket::CheckAuthentication(\n\t\t  const std::string_view usernameFragment1, const std::string_view& password) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* messageIntegrity = GetMessageIntegrity();\n\t\t\tconst auto hasFingerprint    = HasAttribute(StunPacket::AttributeType::FINGERPRINT);\n\n\t\t\tswitch (this->klass)\n\t\t\t{\n\t\t\t\tcase StunPacket::Class::REQUEST:\n\t\t\t\tcase StunPacket::Class::INDICATION:\n\t\t\t\t{\n\t\t\t\t\t// usernameFragment1 must not be empty.\n\t\t\t\t\tif (usernameFragment1.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  ice, \"cannot authenticate request or indication, empty usernameFragment1 given\");\n\n\t\t\t\t\t\treturn StunPacket::AuthenticationResult::BAD_MESSAGE;\n\t\t\t\t\t}\n\n\t\t\t\t\t// USERNAME Attribute must be present.\n\t\t\t\t\tif (!HasAttribute(StunPacket::AttributeType::USERNAME))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(ice, \"cannot authenticate request or indication, missing USERNAME Attribute\");\n\n\t\t\t\t\t\treturn StunPacket::AuthenticationResult::BAD_MESSAGE;\n\t\t\t\t\t}\n\n\t\t\t\t\t// MESSAGE-INTEGRITY Attribute must be present.\n\t\t\t\t\tif (!messageIntegrity)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  ice, \"cannot authenticate request or indication, missing MESSAGE-INTEGRITY Attribute\");\n\n\t\t\t\t\t\treturn StunPacket::AuthenticationResult::BAD_MESSAGE;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check that the USERNAME Attribute begins with the first username\n\t\t\t\t\t// fragment plus \":\".\n\t\t\t\t\tconst auto username = GetUsername();\n\n\t\t\t\t\tif (\n\t\t\t\t\t  username.length() <= usernameFragment1.length() ||\n\t\t\t\t\t  username.at(usernameFragment1.length()) != ':' ||\n\t\t\t\t\t  username.compare(0, usernameFragment1.length(), usernameFragment1.data()) != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn StunPacket::AuthenticationResult::UNAUTHORIZED;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase StunPacket::Class::SUCCESS_RESPONSE:\n\t\t\t\tcase StunPacket::Class::ERROR_RESPONSE:\n\t\t\t\t{\n\t\t\t\t\t// MESSAGE-INTEGRITY Attribute must be present.\n\t\t\t\t\tif (!messageIntegrity)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t  \"cannot authenticate success response or error response, missing MESSAGE-INTEGRITY Attribute\");\n\n\t\t\t\t\t\treturn StunPacket::AuthenticationResult::BAD_MESSAGE;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"cannot authenticate STUN Packet, unknown STUN class %\" PRIu16,\n\t\t\t\t\t  static_cast<uint16_t>(this->klass));\n\n\t\t\t\t\treturn StunPacket::AuthenticationResult::BAD_MESSAGE;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto* fixedHeader = GetFixedHeaderPointer();\n\n\t\t\t// If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY\n\t\t\t// calculation, so the message length field must be modified (and later\n\t\t\t// restored).\n\t\t\tif (hasFingerprint)\n\t\t\t{\n\t\t\t\t// Set the message length field by removing the length of the\n\t\t\t\t// FINGERPRINT Attribute (4 + 4).\n\t\t\t\t// NOTE: We cannot use SetMessageLength() because CheckAuthentication()\n\t\t\t\t// is marked as a `const` method.\n\t\t\t\tUtils::Byte::Set2Bytes(fixedHeader, 2, static_cast<uint16_t>(GetAttributesLength() - 4 - 4));\n\t\t\t}\n\n\t\t\t// Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY\n\t\t\t// rules, this is, by checking the bytes from 0 to the beginning of the\n\t\t\t// MESSAGE-INTEGRITY Attribute.\n\t\t\tconst uint8_t* computedMessageIntegrity = Utils::Crypto::GetHmacSha1(\n\t\t\t  password.data(), password.length(), fixedHeader, (messageIntegrity - 4) - fixedHeader);\n\n\t\t\tStunPacket::AuthenticationResult result;\n\n\t\t\t// Compare the computed HMAC-SHA1 with the MESSAGE-INTEGRITY in the STUN\n\t\t\t// Packet.\n\t\t\tif (std::memcmp(messageIntegrity, computedMessageIntegrity, StunPacket::FixedHeaderLength) == 0)\n\t\t\t{\n\t\t\t\tresult = StunPacket::AuthenticationResult::OK;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tresult = StunPacket::AuthenticationResult::UNAUTHORIZED;\n\t\t\t}\n\n\t\t\t// Restore the message length field.\n\t\t\t// NOTE: We cannot use SetMessageLength() because CheckAuthentication()\n\t\t\t// is marked as a `const` method.\n\t\t\tif (hasFingerprint)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set2Bytes(fixedHeader, 2, static_cast<uint16_t>(GetAttributesLength()));\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tStunPacket::AuthenticationResult StunPacket::CheckAuthentication(std::string_view password) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn CheckAuthentication({}, password);\n\t\t}\n\n\t\tvoid StunPacket::Protect(const std::string_view password)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertNotProtected();\n\n\t\t\tconst auto currentLength = GetLength();\n\t\t\tconst size_t addedLength = 4 + StunPacket::MessageIntegrityAttributeLength + 4 + 4;\n\n\t\t\t// We need to add Attribute(s) so we must increase the length of the\n\t\t\t// STUN Packet.\n\t\t\t// NOTE: This may throw.\n\t\t\tSetLength(GetLength() + addedLength);\n\t\t\t// Once we know it doesn't throw (so there is space in the buffer), let's\n\t\t\t// revert it because code below will do it when needed.\n\t\t\tSetLength(currentLength);\n\n\t\t\t// Add MESSAGE-INTEGRITY Attribute (only if password was given).\n\t\t\tif (!password.empty())\n\t\t\t{\n\t\t\t\t// When must include the length of MESSAGE-INTEGRITY Attribute in\n\t\t\t\t// message length field of the STUN Packet.\n\t\t\t\tSetMessageLength(GetMessageLength() + 4 + StunPacket::MessageIntegrityAttributeLength);\n\n\t\t\t\t// Calculate the HMAC-SHA1 of the STUN Packet according to\n\t\t\t\t// MESSAGE-INTEGRITY rules.\n\t\t\t\tconst uint8_t* computedMessageIntegrity =\n\t\t\t\t  Utils::Crypto::GetHmacSha1(password.data(), password.length(), GetBuffer(), currentLength);\n\n\t\t\t\tStoreNewAttribute(\n\t\t\t\t  StunPacket::AttributeType::MESSAGE_INTEGRITY,\n\t\t\t\t  computedMessageIntegrity,\n\t\t\t\t  StunPacket::MessageIntegrityAttributeLength);\n\t\t\t}\n\n\t\t\t// Add FINGERPRINT Attribute.\n\n\t\t\t// When must include the length of FINGERPRINT Attribute in\n\t\t\t// message length field of the STUN Packet.\n\t\t\tSetMessageLength(GetMessageLength() + 4 + 4);\n\n\t\t\t// Compute the CRC32 of the STUN Packet up to (but excluding) the\n\t\t\t// FINGERPRINT Attribute and XOR it with 0x5354554e.\n\t\t\tconst uint32_t computedFingerprint =\n\t\t\t  Utils::Crypto::GetCRC32(GetBuffer(), GetLength()) ^ 0x5354554e;\n\n\t\t\tUtils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, computedFingerprint);\n\n\t\t\tStoreNewAttribute(StunPacket::AttributeType::FINGERPRINT, AttributeFactoryBuffer, 4);\n\t\t}\n\n\t\tvoid StunPacket::Protect()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tProtect({});\n\t\t}\n\n\t\tStunPacket* StunPacket::CreateSuccessResponse(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->klass != StunPacket::Class::REQUEST)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"cannot create a success response, original STUN Packet is not a request\");\n\t\t\t}\n\n\t\t\tauto* successResponse = Factory(\n\t\t\t  buffer, bufferLength, StunPacket::Class::SUCCESS_RESPONSE, this->method, GetTransactionId());\n\n\t\t\treturn successResponse;\n\t\t}\n\n\t\tStunPacket* StunPacket::CreateErrorResponse(\n\t\t  uint8_t* buffer, size_t bufferLength, uint16_t errorCode, const std::string_view& reasonPhrase) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->klass != StunPacket::Class::REQUEST)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"cannot create an error response, original STUN Packet is not a request\");\n\t\t\t}\n\n\t\t\tauto* errorResponse = Factory(\n\t\t\t  buffer, bufferLength, StunPacket::Class::ERROR_RESPONSE, this->method, GetTransactionId());\n\n\t\t\terrorResponse->AddErrorCode(errorCode, reasonPhrase);\n\n\t\t\treturn errorResponse;\n\t\t}\n\n\t\tbool StunPacket::Validate(bool storeAttributes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* fixedHeader = GetFixedHeaderPointer();\n\n\t\t\t// Get message length field.\n\t\t\tconst auto msgLength = GetMessageLength();\n\n\t\t\t// Message length field must be total length minus header's 20 bytes, and\n\t\t\t// must be multiple of 4 Bytes.\n\t\t\t// NOTE: Message length is effectively the total length of the Attributes\n\t\t\t// (with all paddings).\n\t\t\tif (static_cast<size_t>(msgLength) != GetAttributesLength() || !Utils::Byte::IsPaddedTo4Bytes(msgLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"invalid STUN Packet, message length field (%\" PRIu16\n\t\t\t\t  \") does not match given buffer length or it's not multiple of 4 bytes\",\n\t\t\t\t  msgLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (!ParseAttributes(storeAttributes))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"invalid STUN Packet, invalid Attributes\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// If it has FINGERPRINT Attribute then verify it.\n\t\t\tconst auto* fingerprintAttr = GetAttribute(StunPacket::AttributeType::FINGERPRINT);\n\n\t\t\tif (fingerprintAttr)\n\t\t\t{\n\t\t\t\t// Compute the CRC32 of the received STUN Packet up to (but excluding)\n\t\t\t\t// the FINGERPRINT Attribute and XOR it with 0x5354554e.\n\t\t\t\tconst auto computedFingerprint =\n\t\t\t\t  Utils::Crypto::GetCRC32(\n\t\t\t\t    fixedHeader, StunPacket::FixedHeaderLength + fingerprintAttr->offset) ^\n\t\t\t\t  0x5354554e;\n\n\t\t\t\t// Compare with the FINGERPRINT value in the STUN Packet.\n\t\t\t\tif (GetFingerprint() != computedFingerprint)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"invalid STUN Packet, computed fingerprint value does not match the value in the FINGERPRINT Attribute\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool StunPacket::ParseAttributes(bool storeAttributes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint8_t* attributesStart = GetAttributesPointer();\n\t\t\tconst uint8_t* attributesEnd   = attributesStart + GetAttributesLength();\n\t\t\tauto* ptr                      = const_cast<uint8_t*>(attributesStart);\n\n\t\t\t// Ensure there are at least 4 remaining bytes (Attribute with 0 length).\n\t\t\twhile (ptr + 4 <= attributesEnd)\n\t\t\t{\n\t\t\t\t// NOTE: We cannot cast `ptr` to `StunPacket::Attribute*` here because\n\t\t\t\t// `StunPacket::Attribute` requires 8-byte alignment (due to its `size_t`\n\t\t\t\t// member) but `ptr` points into a network buffer with no guaranteed\n\t\t\t\t// alignment, making the cast undefined behavior.\n\n\t\t\t\t// Read Attribute type and length.\n\t\t\t\tconst auto attrType = static_cast<StunPacket::AttributeType>(Utils::Byte::Get2Bytes(ptr, 0));\n\t\t\t\tconst uint16_t attrLen = Utils::Byte::Get2Bytes(ptr, 2);\n\n\t\t\t\t// Offset of the Attribute from the start of the attributes.\n\t\t\t\tconst auto attrOffset = static_cast<size_t>((ptr - attributesStart));\n\n\t\t\t\t// Ensure the Attribute length is not greater than the remaining length.\n\t\t\t\tif (ptr + 4 + attrLen > attributesEnd)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"invalid STUN Packet, not enough space for the announced value of the Attribute with type %\" PRIu16,\n\t\t\t\t\t  static_cast<uint16_t>(attrType));\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// FINGERPRINT must be the last Attribute.\n\t\t\t\tif (storeAttributes && HasAttribute(StunPacket::AttributeType::FINGERPRINT))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute after FINGERPRINT is not allowed\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// After a MESSAGE-INTEGRITY Attribute only FINGERPRINT is allowed.\n\t\t\t\tif (\n\t\t\t\t  storeAttributes && HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY) &&\n\t\t\t\t  attrType != StunPacket::AttributeType::FINGERPRINT)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  ice,\n\t\t\t\t\t  \"invalid STUN Packet, Attribute after MESSAGE-INTEGRITY other than FINGERPRINT is not allowed\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tswitch (attrType)\n\t\t\t\t{\n\t\t\t\t\tcase StunPacket::AttributeType::USERNAME:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen > StunPacket::UsernameAttributeMaxLength)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"invalid STUN Packet, Attribute USERNAME must be at most %zu bytes\",\n\t\t\t\t\t\t\t  StunPacket::UsernameAttributeMaxLength);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::PRIORITY:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute PRIORITY must be 4 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::ICE_CONTROLLING:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 8)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  ice, \"invalid STUN Packet, Attribute ICE-CONTROLLING must be 8 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::ICE_CONTROLLED:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 8)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute ICE-CONTROLLED must be 8 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::USE_CANDIDATE:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute USE-CANDIDATE must be 0 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::NOMINATION:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute NOMINATION must be 4 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::SOFTWARE:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen > StunPacket::SoftwareAttributeMaxLength)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"invalid STUN Packet, Attribute SOFTWARE must be at most %zu bytes length\",\n\t\t\t\t\t\t\t  StunPacket::SoftwareAttributeMaxLength);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::XOR_MAPPED_ADDRESS:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != StunPacket::XorMappedAddressIPv4Length && attrLen != StunPacket::XorMappedAddressIPv6Length)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"invalid STUN Packet, Attribute XOR_MAPPED_ADDRESS-CODE must be %zu or %zu bytes length\",\n\t\t\t\t\t\t\t  StunPacket::XorMappedAddressIPv4Length,\n\t\t\t\t\t\t\t  StunPacket::XorMappedAddressIPv6Length);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::ERROR_CODE:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen < 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute ERROR-CODE must be >= 4 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::MESSAGE_INTEGRITY:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != StunPacket::MessageIntegrityAttributeLength)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  ice,\n\t\t\t\t\t\t\t  \"invalid STUN Packet, Attribute MESSAGE-INTEGRITY must be %zu bytes length\",\n\t\t\t\t\t\t\t  StunPacket::MessageIntegrityAttributeLength);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase StunPacket::AttributeType::FINGERPRINT:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (attrLen != 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(ice, \"invalid STUN Packet, Attribute FINGERPRINT must be 4 bytes length\");\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\"unknown Attribute with type %\" PRIu16, attrType);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Move to next Attribute.\n\t\t\t\tptr += Utils::Byte::PadTo4Bytes(static_cast<size_t>(4 + attrLen));\n\t\t\t}\n\n\t\t\t// Ensure we read the Attributes length entirely.\n\t\t\tif (ptr != attributesStart + GetAttributesLength())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"invalid STUN Packet, computed length of Attributes (%zu) does not match announced length (%zu)\",\n\t\t\t\t  static_cast<size_t>(ptr - attributesStart),\n\t\t\t\t  GetAttributesLength());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool StunPacket::StoreParsedAttribute(AttributeType type, uint16_t len, size_t offset)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->attributes.try_emplace(type, type, len, offset).second)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  ice,\n\t\t\t\t  \"cannot store parsed Attribute with type %\" PRIu16\n\t\t\t\t  \", there is an Attribute with same type already in the map\",\n\t\t\t\t  static_cast<uint16_t>(type));\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid StunPacket::StoreNewAttribute(AttributeType type, const void* data, uint16_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  (data && len) || (!data && !len),\n\t\t\t  \"data and len must either both have a value or both be empty/zero\");\n\n\t\t\tif (this->attributes.find(type) != this->attributes.end())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t  \"cannot store new Attribute with type %\" PRIu16\n\t\t\t\t  \", there is an Attribute with same type already in the map\",\n\t\t\t\t  static_cast<uint16_t>(type));\n\t\t\t}\n\n\t\t\t// Add the Attribute at the end of the STUN Packet.\n\n\t\t\tconst auto attrTotalPaddedLength = Utils::Byte::PadTo4Bytes(static_cast<size_t>(4 + len));\n\n\t\t\t// Get the pointer in which the new Attribute must be written.\n\t\t\t// NOTE: Do this before updating lengths.\n\t\t\tauto* attrPtr = GetAttributesPointer() + GetAttributesLength();\n\n\t\t\t// First update STUN Packet length (it may throw).\n\t\t\tSetLength(GetLength() + attrTotalPaddedLength);\n\n\t\t\t// Also update the message length field.\n\t\t\tSetMessageLength(GetAttributesLength());\n\n\t\t\tUtils::Byte::Set2Bytes(attrPtr, 0, static_cast<uint16_t>(type));\n\t\t\tUtils::Byte::Set2Bytes(attrPtr, 2, len);\n\n\t\t\tif (data)\n\t\t\t{\n\t\t\t\tstd::memcpy(attrPtr + 4, data, len);\n\t\t\t\t// Fill padding bytes with zeroes.\n\t\t\t\tstd::memset(attrPtr + 4 + len, 0x00, attrTotalPaddedLength - len);\n\t\t\t}\n\n\t\t\tconst auto [it, inserted] = this->attributes.try_emplace(type, type, len, 0);\n\t\t\tauto& attribute           = it->second;\n\n\t\t\t// Update stored Attribute's offset.\n\t\t\tattribute.offset = attrPtr - GetAttributesPointer();\n\n\t\t\tMS_ASSERT(inserted, \"Attribute not inserted in the map (this shouldn't happen)\");\n\t\t}\n\n\t\tvoid StunPacket::AssertNotProtected() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (IsProtected())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"STUN Packet is protected\");\n\t\t\t}\n\t\t}\n\t} // namespace ICE\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/KeyFrameRequestManager.cpp",
    "content": "#define MS_CLASS \"KeyFrameRequestManager\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/KeyFrameRequestManager.hpp\"\n#include \"Logger.hpp\"\n\nstatic constexpr uint32_t KeyFrameRetransmissionWaitTime{ 1000u };\n\n/* PendingKeyFrameInfo methods. */\n\nRTC::PendingKeyFrameInfo::PendingKeyFrameInfo(\n  PendingKeyFrameInfo::Listener* listener, SharedInterface* shared, uint32_t ssrc)\n  : listener(listener), ssrc(ssrc), timer(shared->CreateTimer(this))\n{\n\tMS_TRACE();\n\n\tthis->timer->Start(KeyFrameRetransmissionWaitTime);\n}\n\nRTC::PendingKeyFrameInfo::~PendingKeyFrameInfo()\n{\n\tMS_TRACE();\n\n\tthis->timer->Stop();\n\tdelete this->timer;\n}\n\nvoid RTC::PendingKeyFrameInfo::OnTimer(TimerHandleInterface* timer)\n{\n\tMS_TRACE();\n\n\tif (timer == this->timer)\n\t{\n\t\tthis->listener->OnKeyFrameRequestTimeout(this);\n\t}\n}\n\n/* KeyFrameRequestDelayer methods. */\n\nRTC::KeyFrameRequestDelayer::KeyFrameRequestDelayer(\n  KeyFrameRequestDelayer::Listener* listener, SharedInterface* shared, uint32_t ssrc, uint32_t delay)\n  : listener(listener), ssrc(ssrc), timer(shared->CreateTimer(this))\n{\n\tMS_TRACE();\n\n\tthis->timer->Start(delay);\n}\n\nRTC::KeyFrameRequestDelayer::~KeyFrameRequestDelayer()\n{\n\tMS_TRACE();\n\n\tthis->timer->Stop();\n\tdelete this->timer;\n}\n\nvoid RTC::KeyFrameRequestDelayer::OnTimer(TimerHandleInterface* timer)\n{\n\tMS_TRACE();\n\n\tif (timer == this->timer)\n\t{\n\t\tthis->listener->OnKeyFrameDelayTimeout(this);\n\t}\n}\n\n/* KeyFrameRequestManager methods. */\n\nRTC::KeyFrameRequestManager::KeyFrameRequestManager(\n  KeyFrameRequestManager::Listener* listener, SharedInterface* shared, uint32_t keyFrameRequestDelay)\n  : listener(listener), shared(shared), keyFrameRequestDelay(keyFrameRequestDelay)\n{\n\tMS_TRACE();\n}\n\nRTC::KeyFrameRequestManager::~KeyFrameRequestManager()\n{\n\tMS_TRACE();\n\n\tfor (auto& kv : this->mapSsrcPendingKeyFrameInfo)\n\t{\n\t\tauto* pendingKeyFrameInfo = kv.second;\n\n\t\tdelete pendingKeyFrameInfo;\n\t}\n\tthis->mapSsrcPendingKeyFrameInfo.clear();\n\n\tfor (auto& kv : this->mapSsrcKeyFrameRequestDelayer)\n\t{\n\t\tauto* keyFrameRequestDelayer = kv.second;\n\n\t\tdelete keyFrameRequestDelayer;\n\t}\n\tthis->mapSsrcKeyFrameRequestDelayer.clear();\n}\n\nvoid RTC::KeyFrameRequestManager::KeyFrameNeeded(uint32_t ssrc)\n{\n\tMS_TRACE();\n\n\tif (this->keyFrameRequestDelay > 0u)\n\t{\n\t\tauto it = this->mapSsrcKeyFrameRequestDelayer.find(ssrc);\n\n\t\t// There is a delayer for the given ssrc, so enable it and return.\n\t\tif (it != this->mapSsrcKeyFrameRequestDelayer.end())\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"there is a delayer for the given ssrc, enabling it and returning\");\n\n\t\t\tauto* keyFrameRequestDelayer = it->second;\n\n\t\t\tkeyFrameRequestDelayer->SetKeyFrameRequested(true);\n\n\t\t\treturn;\n\t\t}\n\t\t// Otherwise create a delayer (not yet enabled) and continue.\n\t\telse\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"creating a delayer for the given ssrc\");\n\n\t\t\tthis->mapSsrcKeyFrameRequestDelayer[ssrc] =\n\t\t\t  new KeyFrameRequestDelayer(this, this->shared, ssrc, this->keyFrameRequestDelay);\n\t\t}\n\t}\n\n\tauto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc);\n\n\t// There is a pending key frame for the given ssrc.\n\tif (it != this->mapSsrcPendingKeyFrameInfo.end())\n\t{\n\t\tauto* pendingKeyFrameInfo = it->second;\n\n\t\t// Re-request the key frame if not received on time.\n\t\tpendingKeyFrameInfo->SetRetryOnTimeout(true);\n\n\t\treturn;\n\t}\n\n\tthis->mapSsrcPendingKeyFrameInfo[ssrc] = new PendingKeyFrameInfo(this, this->shared, ssrc);\n\n\tthis->listener->OnKeyFrameNeeded(this, ssrc);\n}\n\nvoid RTC::KeyFrameRequestManager::ForceKeyFrameNeeded(uint32_t ssrc)\n{\n\tMS_TRACE();\n\n\tif (this->keyFrameRequestDelay > 0u)\n\t{\n\t\t// Create/replace a delayer for this ssrc.\n\t\tauto it = this->mapSsrcKeyFrameRequestDelayer.find(ssrc);\n\n\t\t// There is a delayer for the given ssrc, so enable it and return.\n\t\tif (it != this->mapSsrcKeyFrameRequestDelayer.end())\n\t\t{\n\t\t\tauto* keyFrameRequestDelayer = it->second;\n\n\t\t\tdelete keyFrameRequestDelayer;\n\t\t}\n\n\t\tthis->mapSsrcKeyFrameRequestDelayer[ssrc] =\n\t\t  new KeyFrameRequestDelayer(this, this->shared, ssrc, this->keyFrameRequestDelay);\n\t}\n\n\tauto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc);\n\n\t// There is a pending key frame for the given ssrc.\n\tif (it != this->mapSsrcPendingKeyFrameInfo.end())\n\t{\n\t\tauto* pendingKeyFrameInfo = it->second;\n\n\t\tpendingKeyFrameInfo->SetRetryOnTimeout(true);\n\t\tpendingKeyFrameInfo->Restart();\n\t}\n\telse\n\t{\n\t\tthis->mapSsrcPendingKeyFrameInfo[ssrc] = new PendingKeyFrameInfo(this, this->shared, ssrc);\n\t}\n\n\tthis->listener->OnKeyFrameNeeded(this, ssrc);\n}\n\nvoid RTC::KeyFrameRequestManager::KeyFrameReceived(uint32_t ssrc)\n{\n\tMS_TRACE();\n\n\tauto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc);\n\n\t// There is no pending key frame for the given ssrc.\n\tif (it == this->mapSsrcPendingKeyFrameInfo.end())\n\t{\n\t\treturn;\n\t}\n\n\tauto* pendingKeyFrameInfo = it->second;\n\n\tdelete pendingKeyFrameInfo;\n\n\tthis->mapSsrcPendingKeyFrameInfo.erase(it);\n}\n\nvoid RTC::KeyFrameRequestManager::OnKeyFrameRequestTimeout(PendingKeyFrameInfo* pendingKeyFrameInfo)\n{\n\tMS_TRACE();\n\n\tauto it = this->mapSsrcPendingKeyFrameInfo.find(pendingKeyFrameInfo->GetSsrc());\n\n\tMS_ASSERT(\n\t  it != this->mapSsrcPendingKeyFrameInfo.end(), \"PendingKeyFrameInfo not present in the map\");\n\n\tif (!pendingKeyFrameInfo->GetRetryOnTimeout())\n\t{\n\t\tdelete pendingKeyFrameInfo;\n\n\t\tthis->mapSsrcPendingKeyFrameInfo.erase(it);\n\n\t\treturn;\n\t}\n\n\t// Best effort in case the PLI/FIR was lost. Do not retry on timeout.\n\tpendingKeyFrameInfo->SetRetryOnTimeout(false);\n\tpendingKeyFrameInfo->Restart();\n\n\tMS_DEBUG_DEV(\"requesting key frame on timeout\");\n\n\tthis->listener->OnKeyFrameNeeded(this, pendingKeyFrameInfo->GetSsrc());\n}\n\nvoid RTC::KeyFrameRequestManager::OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer)\n{\n\tMS_TRACE();\n\n\tauto it = this->mapSsrcKeyFrameRequestDelayer.find(keyFrameRequestDelayer->GetSsrc());\n\n\tMS_ASSERT(\n\t  it != this->mapSsrcKeyFrameRequestDelayer.end(), \"KeyFrameRequestDelayer not present in the map\");\n\n\tauto ssrc              = keyFrameRequestDelayer->GetSsrc();\n\tauto keyFrameRequested = keyFrameRequestDelayer->GetKeyFrameRequested();\n\n\tdelete keyFrameRequestDelayer;\n\n\tthis->mapSsrcKeyFrameRequestDelayer.erase(it);\n\n\t// Ask for a new key frame as normal if needed.\n\tif (keyFrameRequested)\n\t{\n\t\tMS_DEBUG_DEV(\"requesting key frame after delay timeout\");\n\n\t\tKeyFrameNeeded(ssrc);\n\t}\n}\n"
  },
  {
    "path": "worker/src/RTC/NackGenerator.cpp",
    "content": "#define MS_CLASS \"RTC::NackGenerator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/NackGenerator.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t MaxPacketAge{ 10000u };\n\tstatic constexpr size_t MaxNackPackets{ 1000u };\n\tstatic constexpr uint32_t DefaultRtt{ 100u };\n\tstatic constexpr uint8_t MaxNackRetries{ 10u };\n\tstatic constexpr uint64_t TimerInterval{ 40u };\n\n\t/* Instance methods. */\n\n\tNackGenerator::NackGenerator(Listener* listener, SharedInterface* shared, unsigned int sendNackDelayMs)\n\t  : listener(listener),\n\t    shared(shared),\n\t    sendNackDelayMs(sendNackDelayMs),\n\t    timer(shared->CreateTimer(this)),\n\t    rtt(DefaultRtt)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tNackGenerator::~NackGenerator()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Close the timer.\n\t\tdelete this->timer;\n\t}\n\n\t/**\n\t * Returns true if this is a found nacked packet. False otherwise.\n\t *\n\t * NOTE: It also returns true if packet comes via RTX and contains a sequence\n\t * number higher than the highest seen.\n\t */\n\tbool NackGenerator::ReceivePacket(const RTC::RTP::Packet* packet, bool isRecovered)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint16_t seq    = packet->GetSequenceNumber();\n\t\tconst bool isKeyFrame = packet->IsKeyFrame();\n\n\t\tif (!this->started)\n\t\t{\n\t\t\tthis->started = true;\n\t\t\tthis->lastSeq = seq;\n\n\t\t\tif (isKeyFrame)\n\t\t\t{\n\t\t\t\tthis->keyFrameList.insert(seq);\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Obviously never nacked, so ignore.\n\t\tif (seq == this->lastSeq)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t// May be an out of order packet, or already handled retransmitted packet,\n\t\t// or a retransmitted packet.\n\t\tif (SeqManager<uint16_t>::IsSeqLowerThan(seq, this->lastSeq))\n\t\t{\n\t\t\t// It was a nacked packet.\n\t\t\tif (this->nackList.erase(seq) != 0u)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"NACKed packet received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", recovered:%s]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  isRecovered ? \"true\" : \"false\");\n\n\t\t\t\t// NOTE: Accept the packet since it was in the `nackList`, regardless\n\t\t\t\t// the NACK requesting this packet was not sent yet (this is, if\n\t\t\t\t// nackInfo.retries == 0) because we would request it later anyway.\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Out of order packet or already handled NACKed packet.\n\t\t\tif (!isRecovered)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"ignoring older packet not present in the NACK list [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// If we are here it means that we may have lost some packets so seq is\n\t\t// newer than the latest seq seen.\n\n\t\tif (isKeyFrame)\n\t\t{\n\t\t\tthis->keyFrameList.insert(seq);\n\t\t}\n\n\t\t// Remove old keyframes.\n\t\t{\n\t\t\tauto it = this->keyFrameList.lower_bound(seq - MaxPacketAge);\n\n\t\t\tif (it != this->keyFrameList.begin())\n\t\t\t{\n\t\t\t\tthis->keyFrameList.erase(this->keyFrameList.begin(), it);\n\t\t\t}\n\t\t}\n\n\t\tif (isRecovered)\n\t\t{\n\t\t\tconst auto inserted = this->recoveredList.insert(seq).second;\n\n\t\t\t// Packet already recovered, ignore it.\n\t\t\tif (!inserted)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Remove old ones so we don't accumulate recovered packets.\n\t\t\tauto it = this->recoveredList.lower_bound(seq - MaxPacketAge);\n\n\t\t\tif (it != this->recoveredList.begin())\n\t\t\t{\n\t\t\t\tthis->recoveredList.erase(this->recoveredList.begin(), it);\n\t\t\t}\n\n\t\t\t// NOTE: It may happen that this packet received via RTX contains a real\n\t\t\t// RTP packet that (with highest seq not seen yet) whose transmission\n\t\t\t// failed so we didn't receive it. So do not return false here but let\n\t\t\t// the packet go through.\n\t\t}\n\n\t\tAddPacketsToNackList(this->lastSeq + 1, seq);\n\n\t\tthis->lastSeq = seq;\n\n\t\t// Check if there are any nacks that are waiting for this seq number.\n\t\tconst std::vector<uint16_t> nackBatch = GetNackBatch(NackFilter::SEQ);\n\n\t\tif (!nackBatch.empty())\n\t\t{\n\t\t\tthis->listener->OnNackGeneratorNackRequired(nackBatch);\n\t\t}\n\n\t\t// This is important. Otherwise the running timer (filter:TIME) would be\n\t\t// interrupted and NACKs would never been sent more than once for each seq.\n\t\tif (!this->timer->IsActive())\n\t\t{\n\t\t\tMayRunTimer();\n\t\t}\n\n\t\t// libwebrtc may use RTX for probation and such packets may contain\n\t\t// RTX-encoded real RTP packets that were sent before but didn't arrive yet\n\t\t// to us or they were lost. Let's deal with them as normal packets.\n\t\treturn isRecovered ? true : false;\n\t}\n\n\tvoid NackGenerator::AddPacketsToNackList(uint16_t seqStart, uint16_t seqEnd)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove old packets.\n\t\tauto it = this->nackList.lower_bound(seqEnd - MaxPacketAge);\n\n\t\tthis->nackList.erase(this->nackList.begin(), it);\n\n\t\t// If the nack list is too large, remove packets from the nack list until\n\t\t// the latest first packet of a keyframe. If the list is still too large,\n\t\t// clear it and request a keyframe.\n\t\tconst uint16_t numNewNacks = seqEnd - seqStart;\n\n\t\tif (static_cast<uint16_t>(this->nackList.size()) + numNewNacks > MaxNackPackets)\n\t\t{\n\t\t\twhile (RemoveNackItemsUntilKeyFrame() &&\n\t\t\t       static_cast<uint16_t>(this->nackList.size()) + numNewNacks > MaxNackPackets)\n\t\t\t{\n\t\t\t}\n\n\t\t\tif (static_cast<uint16_t>(this->nackList.size()) + numNewNacks > MaxNackPackets)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtx, \"NACK list full, clearing it and requesting a key frame [seqEnd:%\" PRIu16 \"]\", seqEnd);\n\n\t\t\t\tthis->nackList.clear();\n\t\t\t\tthis->listener->OnNackGeneratorKeyFrameRequired();\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tfor (uint16_t seq = seqStart; seq != seqEnd; ++seq)\n\t\t{\n\t\t\tMS_ASSERT(this->nackList.find(seq) == this->nackList.end(), \"packet already in the NACK list\");\n\n\t\t\t// Do not send NACK for packets that are already recovered by RTX.\n\t\t\tif (this->recoveredList.find(seq) != this->recoveredList.end())\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis->nackList.emplace(\n\t\t\t  seq,\n\t\t\t  NackInfo{\n\t\t\t    this->shared->GetTimeMs(),\n\t\t\t    seq,\n\t\t\t    seq,\n\t\t\t  });\n\t\t}\n\t}\n\n\tbool NackGenerator::RemoveNackItemsUntilKeyFrame()\n\t{\n\t\tMS_TRACE();\n\n\t\twhile (!this->keyFrameList.empty())\n\t\t{\n\t\t\tauto it = this->nackList.lower_bound(*this->keyFrameList.begin());\n\n\t\t\tif (it != this->nackList.begin())\n\t\t\t{\n\t\t\t\t// We have found a keyframe that actually is newer than at least one\n\t\t\t\t// packet in the nack list.\n\t\t\t\tthis->nackList.erase(this->nackList.begin(), it);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// If this keyframe is so old it does not remove any packets from the list,\n\t\t\t// remove it from the list of keyframes and try the next keyframe.\n\t\t\tthis->keyFrameList.erase(this->keyFrameList.begin());\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tstd::vector<uint16_t> NackGenerator::GetNackBatch(NackFilter filter)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\t\tstd::vector<uint16_t> nackBatch;\n\n\t\tauto it = this->nackList.begin();\n\n\t\twhile (it != this->nackList.end())\n\t\t{\n\t\t\tNackInfo& nackInfo = it->second;\n\t\t\tconst uint16_t seq = nackInfo.seq;\n\n\t\t\tif (this->sendNackDelayMs > 0 && nowMs - nackInfo.createdAtMs < this->sendNackDelayMs)\n\t\t\t{\n\t\t\t\t++it;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t  filter == NackFilter::SEQ && nackInfo.sentAtMs == 0 &&\n\t\t\t  (nackInfo.sendAtSeq == this->lastSeq ||\n\t\t\t   SeqManager<uint16_t>::IsSeqHigherThan(this->lastSeq, nackInfo.sendAtSeq)))\n\t\t\t{\n\t\t\t\tnackBatch.emplace_back(seq);\n\t\t\t\tnackInfo.retries++;\n\t\t\t\tnackInfo.sentAtMs = nowMs;\n\n\t\t\t\tif (nackInfo.retries >= MaxNackRetries)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtx,\n\t\t\t\t\t  \"sequence number removed from the NACK list due to max retries [filter:seq, seq:%\" PRIu16\n\t\t\t\t\t  \"]\",\n\t\t\t\t\t  seq);\n\n\t\t\t\t\tit = this->nackList.erase(it);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++it;\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t  filter == NackFilter::TIME &&\n\t\t\t  (nackInfo.sentAtMs == 0 ||\n\t\t\t   nowMs - nackInfo.sentAtMs >= (this->rtt > 0u ? this->rtt : DefaultRtt)))\n\t\t\t{\n\t\t\t\tnackBatch.emplace_back(seq);\n\t\t\t\tnackInfo.retries++;\n\t\t\t\tnackInfo.sentAtMs = nowMs;\n\n\t\t\t\tif (nackInfo.retries >= MaxNackRetries)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtx,\n\t\t\t\t\t  \"sequence number removed from the NACK list due to max retries [filter:time, seq:%\" PRIu16\n\t\t\t\t\t  \"]\",\n\t\t\t\t\t  seq);\n\n\t\t\t\t\tit = this->nackList.erase(it);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++it;\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t++it;\n\t\t}\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\tif (!nackBatch.empty())\n\t\t{\n\t\t\tstd::ostringstream seqsStream;\n\t\t\tstd::copy(\n\t\t\t  nackBatch.begin(), nackBatch.end() - 1, std::ostream_iterator<uint32_t>(seqsStream, \",\"));\n\t\t\tseqsStream << nackBatch.back();\n\n\t\t\tif (filter == NackFilter::SEQ)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"[filter:SEQ, asking seqs:%s]\", seqsStream.str().c_str());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"[filter:TIME, asking seqs:%s]\", seqsStream.str().c_str());\n\t\t\t}\n\t\t}\n#endif\n\n\t\treturn nackBatch;\n\t}\n\n\tvoid NackGenerator::Reset()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->nackList.clear();\n\t\tthis->keyFrameList.clear();\n\t\tthis->recoveredList.clear();\n\t\tthis->started = false;\n\t\tthis->lastSeq = 0u;\n\t}\n\n\tinline void NackGenerator::MayRunTimer() const\n\t{\n\t\tif (this->nackList.empty())\n\t\t{\n\t\t\tthis->timer->Stop();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->timer->Start(TimerInterval);\n\t\t}\n\t}\n\n\tinline void NackGenerator::OnTimer(TimerHandleInterface* /*timer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst std::vector<uint16_t> nackBatch = GetNackBatch(NackFilter::TIME);\n\n\t\tif (!nackBatch.empty())\n\t\t{\n\t\t\tthis->listener->OnNackGeneratorNackRequired(nackBatch);\n\t\t}\n\n\t\tMayRunTimer();\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/PipeConsumer.cpp",
    "content": "#define MS_CLASS \"RTC::PipeConsumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/PipeConsumer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <limits> // std::numeric_limits\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t TargetLayerRetransmissionBufferSize{ 15u };\n\n\t/* Class methods */\n\n\tvoid PipeConsumer::StorePacketInTargetLayerRetransmissionBuffer(\n\t  std::map<uint16_t, RTC::RTP::SharedPacket, RTC::SeqManager<uint16_t>::SeqLowerThan>&\n\t    targetLayerRetransmissionBuffer,\n\t  RTC::RTP::Packet* packet,\n\t  RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"storing packet in target layer retransmission buffer [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t  packet->GetSsrc(),\n\t\t  packet->GetSequenceNumber(),\n\t\t  packet->GetTimestamp());\n\n\t\t// Store original packet into the buffer. Only clone once and only if\n\t\t// necessary.\n\t\tif (!sharedPacket.HasPacket())\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\t\t// Assert that, if sharedPacket was already filled, both packet and\n\t\t// sharedPacket are the very same RTP packet.\n\t\telse\n\t\t{\n\t\t\tsharedPacket.AssertSamePacket(packet);\n\t\t}\n\n\t\ttargetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket;\n\n\t\tif (targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize)\n\t\t{\n\t\t\ttargetLayerRetransmissionBuffer.erase(targetLayerRetransmissionBuffer.begin());\n\t\t}\n\t}\n\n\t/* Instance methods. */\n\n\tPipeConsumer::PipeConsumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& producerId,\n\t  RTC::Consumer::Listener* listener,\n\t  const FBS::Transport::ConsumeRequest* data)\n\t  : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::PIPE)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure there are as many encodings as consumable encodings.\n\t\tif (this->rtpParameters.encodings.size() != this->consumableRtpEncodings.size())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"number of rtpParameters.encodings and consumableRtpEncodings do not match\");\n\t\t}\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tthis->keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType);\n\n\t\t// Create RtpStreamSend instances.\n\t\tCreateRtpStreams();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tPipeConsumer::~PipeConsumer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\tdelete rtpStream;\n\t\t}\n\n\t\tthis->rtpStreams.clear();\n\t\tthis->mapMappedSsrcSsrc.clear();\n\t\tthis->mapSsrcRtpStream.clear();\n\t\tthis->mapRtpStreamSyncRequired.clear();\n\t\tthis->mapRtpStreamRtpSeqManager.clear();\n\t\tthis->mapRtpStreamTargetLayerRetransmissionBuffer.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::DumpResponse> PipeConsumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Call the parent method.\n\t\tauto base = RTC::Consumer::FillBuffer(builder);\n\n\t\t// Add rtpStreams.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Dump>> rtpStreams;\n\t\trtpStreams.reserve(this->rtpStreams.size());\n\n\t\tfor (const auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtpStreams.emplace_back(rtpStream->FillBuffer(builder));\n\t\t}\n\n\t\tauto dump = FBS::Consumer::CreateConsumerDumpDirect(builder, base, &rtpStreams);\n\n\t\treturn FBS::Consumer::CreateDumpResponse(builder, dump);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> PipeConsumer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Stats>> rtpStreams;\n\t\trtpStreams.reserve(this->rtpStreams.size());\n\n\t\t// Add stats of our send streams.\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtpStreams.emplace_back(rtpStream->FillBufferStats(builder));\n\t\t}\n\n\t\treturn FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> PipeConsumer::FillBufferScore(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->producerRtpStreamScores, \"producerRtpStreamScores not set\");\n\n\t\t// NOTE: Hardcoded values in PipeTransport.\n\t\treturn FBS::Consumer::CreateConsumerScoreDirect(builder, 10, 10, this->producerRtpStreamScores);\n\t}\n\n\tvoid PipeConsumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME:\n\t\t\t{\n\t\t\t\tif (IsActive())\n\t\t\t\t{\n\t\t\t\t\tRequestKeyFrame();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS:\n\t\t\t{\n\t\t\t\t// Accept with empty preferred layers object.\n\n\t\t\t\tauto responseOffset =\n\t\t\t\t  FBS::Consumer::CreateSetPreferredLayersResponse(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Consumer::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid PipeConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tvoid PipeConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tvoid PipeConsumer::ProducerRtpStreamScore(\n\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tvoid PipeConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tuint8_t PipeConsumer::GetBitratePriority() const\n\t{\n\t\tMS_TRACE();\n\n\t\t// PipeConsumer does not play the BWE game.\n\t\treturn 0u;\n\t}\n\n\tuint32_t PipeConsumer::IncreaseLayer(uint32_t /*bitrate*/, bool /*considerLoss*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// PipeConsumer does not play the BWE game.\n\t\treturn 0u;\n\t}\n\n\tvoid PipeConsumer::ApplyLayers()\n\t{\n\t\tMS_TRACE();\n\n\t\t// PipeConsumer does not play the BWE game.\n\t}\n\n\tuint32_t PipeConsumer::GetDesiredBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\t// PipeConsumer does not play the BWE game.\n\t\treturn 0u;\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tvoid PipeConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.consumerId = this->id;\n#endif\n\n\t\tauto ssrc           = this->mapMappedSsrcSsrc.at(packet->GetSsrc());\n\t\tauto* rtpStream     = this->mapSsrcRtpStream.at(ssrc);\n\t\tauto& syncRequired  = this->mapRtpStreamSyncRequired.at(rtpStream);\n\t\tauto& rtpSeqManager = this->mapRtpStreamRtpSeqManager.at(rtpStream);\n\t\tauto& targetLayerRetransmissionBuffer =\n\t\t  this->mapRtpStreamTargetLayerRetransmissionBuffer.at(rtpStream);\n\n\t\tif (!IsActive())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE);\n#endif\n\n\t\t\trtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If we need to sync, support key frames and this is not a key frame,\n\t\t// ignore the packet.\n\t\tif (syncRequired && this->keyFrameSupported && !packet->IsKeyFrame())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);\n#endif\n\n\t\t\t// NOTE: No need to drop the packet in the RTP sequence manager since\n\t\t\t// here we are blocking all packets but the key frame that would trigger\n\t\t\t// sync below.\n\n\t\t\t// Store the packet for the scenario in which this packet is part of the\n\t\t\t// key frame and it arrived before the first packet of the key frame.\n\t\t\tStorePacketInTargetLayerRetransmissionBuffer(\n\t\t\t  targetLayerRetransmissionBuffer, packet, sharedPacket);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto payloadType = packet->GetPayloadType();\n\n\t\t// NOTE: This may happen if this Consumer supports just some codecs of\n\t\t// those in the corresponding Producer.\n\t\tif (!this->supportedCodecPayloadTypes[payloadType])\n\t\t{\n\t\t\tMS_WARN_DEV(\"payload type not supported [payloadType:%\" PRIu8 \"]\", payloadType);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE);\n#endif\n\n\t\t\trtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Packets with only padding are not forwarded.\n\t\tif (packet->GetPayloadLength() == 0)\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD);\n#endif\n\n\t\t\trtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Whether this is the first packet after re-sync.\n\t\tconst bool isSyncPacket = syncRequired;\n\n\t\t// Whether packets stored in the target layer retransmission buffer must be\n\t\t// sent once this packet is sent.\n\t\tbool sendPacketsInTargetLayerRetransmissionBuffer{ false };\n\n\t\t// Sync sequence number and timestamp if required.\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tif (packet->IsKeyFrame())\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"sync key frame received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\tsendPacketsInTargetLayerRetransmissionBuffer = true;\n\t\t\t}\n\n\t\t\trtpSeqManager.Sync(packet->GetSequenceNumber() - 1);\n\n\t\t\tsyncRequired = false;\n\t\t}\n\n\t\t// Update RTP seq number and timestamp.\n\t\tuint16_t seq;\n\n\t\trtpSeqManager.Input(packet->GetSequenceNumber(), seq);\n\n\t\t// Save original packet fields.\n\t\tauto origSsrc = packet->GetSsrc();\n\t\tauto origSeq  = packet->GetSequenceNumber();\n\n\t\t// Rewrite packet.\n\t\tpacket->SetSsrc(ssrc);\n\t\tpacket->SetSequenceNumber(seq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.sendRtpTimestamp = packet->GetTimestamp();\n\t\tpacket->logger.sendSeqNumber    = seq;\n#endif\n\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp,\n\t\t\t  \"sending sync packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSsrc,\n\t\t\t  origSeq);\n\t\t}\n\n\t\tconst RTC::RTP::RtpStreamSend::ReceivePacketResult result =\n\t\t  rtpStream->ReceivePacket(packet, sharedPacket);\n\n\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t{\n\t\t\t// Send the packet.\n\t\t\tthis->listener->OnConsumerSendRtpPacket(this, packet);\n\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventRtpAndKeyFrameTypes(packet);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  rtp,\n\t\t\t  \"failed to send packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSsrc,\n\t\t\t  origSeq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED);\n#endif\n\t\t}\n\n\t\t// Restore packet fields.\n\t\tpacket->SetSsrc(origSsrc);\n\t\tpacket->SetSequenceNumber(origSeq);\n\n\t\t// If sharedPacket doesn't have a packet inside and it has been stored we\n\t\t// need to clone the packet into it.\n\t\tif (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED)\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\n\t\t// If sent packet was the first packet of a key frame, let's send buffered\n\t\t// packets belonging to the same key frame that arrived earlier due to\n\t\t// packet misorder.\n\t\tif (sendPacketsInTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\t// NOTE: Only send buffered packets if the first packet containing the\n\t\t\t// key frame was sent.\n\t\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t\t{\n\t\t\t\tfor (auto& kv : targetLayerRetransmissionBuffer)\n\t\t\t\t{\n\t\t\t\t\tauto& bufferedSharedPacket = kv.second;\n\t\t\t\t\tauto* bufferedPacket       = bufferedSharedPacket.GetPacket();\n\n\t\t\t\t\tif (bufferedPacket->GetSequenceNumber() > origSeq)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"sending packet buffered in the target layer retransmission buffer [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t\t\t\t  \"] after sending first packet of the key frame [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  bufferedPacket->GetSsrc(),\n\t\t\t\t\t\t  bufferedPacket->GetSequenceNumber(),\n\t\t\t\t\t\t  bufferedPacket->GetTimestamp(),\n\t\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\t\t\tSendRtpPacket(bufferedPacket, bufferedSharedPacket);\n\n\t\t\t\t\t\t// Be sure that the target layer retransmission buffer has not been\n\t\t\t\t\t\t// emptied as a result of sending this packet. If so, exit the loop.\n\t\t\t\t\t\tif (targetLayerRetransmissionBuffer.empty())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"target layer retransmission buffer emptied while iterating it, exiting the loop\");\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttargetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tbool PipeConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Special condition for PipeConsumer since this method will be called in a\n\t\t// loop for each stream in this PipeConsumer.\n\t\tif (\n\t\t  nowMs != this->lastRtcpSentTime &&\n\t\t  static_cast<float>((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tstd::vector<RTCP::SenderReport*> senderReports;\n\t\tstd::vector<RTCP::SdesChunk*> sdesChunks;\n\t\tstd::vector<RTCP::DelaySinceLastRr::SsrcInfo*> delaySinceLastRrSsrcInfos;\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\tauto* report = rtpStream->GetRtcpSenderReport(nowMs);\n\n\t\t\tif (!report)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tsenderReports.push_back(report);\n\n\t\t\t// Build SDES chunk for this sender.\n\t\t\tauto* sdesChunk = rtpStream->GetRtcpSdesChunk();\n\t\t\tsdesChunks.push_back(sdesChunk);\n\n\t\t\tauto* delaySinceLastRrSsrcInfo = rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs);\n\n\t\t\tif (delaySinceLastRrSsrcInfo)\n\t\t\t{\n\t\t\t\tdelaySinceLastRrSsrcInfos.push_back(delaySinceLastRrSsrcInfo);\n\t\t\t}\n\t\t}\n\n\t\t// RTCP Compound packet buffer cannot hold the data.\n\t\tif (!packet->Add(senderReports, sdesChunks, delaySinceLastRrSsrcInfos))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->lastRtcpSentTime = nowMs;\n\n\t\treturn true;\n\t}\n\n\tvoid PipeConsumer::NeedWorstRemoteFractionLost(uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\tauto fractionLost = rtpStream->GetFractionLost();\n\n\t\t\t// If our fraction lost is worse than the given one, update it.\n\t\t\tworstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost);\n\t\t}\n\t}\n\n\tvoid PipeConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventNackType();\n\n\t\tauto ssrc       = nackPacket->GetMediaSsrc();\n\t\tauto* rtpStream = this->mapSsrcRtpStream.at(ssrc);\n\n\t\trtpStream->ReceiveNack(nackPacket);\n\t}\n\n\tvoid PipeConsumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (messageType)\n\t\t{\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t{\n\t\t\t\tEmitTraceEventPliType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t{\n\t\t\t\tEmitTraceEventFirType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tauto* rtpStream = this->mapSsrcRtpStream.at(ssrc);\n\n\t\trtpStream->ReceiveKeyFrameRequest(messageType);\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid PipeConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto* rtpStream = this->mapSsrcRtpStream.at(report->GetSsrc());\n\n\t\trtpStream->ReceiveRtcpReceiverReport(report);\n\t}\n\n\tvoid PipeConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtpStream->ReceiveRtcpXrReceiverReferenceTime(report);\n\t\t}\n\t}\n\n\tuint32_t PipeConsumer::GetTransmissionRate(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tuint32_t rate{ 0u };\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trate += rtpStream->GetBitrate(nowMs);\n\t\t}\n\n\t\treturn rate;\n\t}\n\n\tfloat PipeConsumer::GetRtt() const\n\t{\n\t\tMS_TRACE();\n\n\t\tfloat rtt{ 0 };\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtt = std::max(rtpStream->GetRtt(), rtt);\n\t\t}\n\n\t\treturn rtt;\n\t}\n\n\tvoid PipeConsumer::UserOnTransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto& kv : this->mapRtpStreamSyncRequired)\n\t\t{\n\t\t\tkv.second = true;\n\t\t}\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t\t{\n\t\t\t\trtpStream->Resume();\n\t\t\t}\n\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid PipeConsumer::UserOnTransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtpStream->Pause();\n\t\t}\n\n\t\tfor (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\tauto& targetLayerRetransmissionBuffer = kv.second;\n\n\t\t\ttargetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tvoid PipeConsumer::UserOnPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t{\n\t\t\trtpStream->Pause();\n\t\t}\n\n\t\tfor (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\tauto& targetLayerRetransmissionBuffer = kv.second;\n\n\t\t\ttargetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tvoid PipeConsumer::UserOnResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (auto& kv : this->mapRtpStreamSyncRequired)\n\t\t{\n\t\t\tkv.second = true;\n\t\t}\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tfor (auto* rtpStream : this->rtpStreams)\n\t\t\t{\n\t\t\t\trtpStream->Resume();\n\t\t\t}\n\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid PipeConsumer::CreateRtpStreams()\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE: Here we know that SSRCs in Consumer's rtpParameters must be the same\n\t\t// as in the given consumableRtpEncodings.\n\t\tfor (size_t idx{ 0u }; idx < this->rtpParameters.encodings.size(); ++idx)\n\t\t{\n\t\t\tauto& encoding           = this->rtpParameters.encodings[idx];\n\t\t\tconst auto* mediaCodec   = this->rtpParameters.GetCodecForEncoding(encoding);\n\t\t\tauto& consumableEncoding = this->consumableRtpEncodings[idx];\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp, \"[ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \"]\", encoding.ssrc, mediaCodec->payloadType);\n\n\t\t\t// Set stream params.\n\t\t\tRTC::RTP::RtpStream::Params params;\n\n\t\t\tparams.encodingIdx    = idx;\n\t\t\tparams.ssrc           = encoding.ssrc;\n\t\t\tparams.payloadType    = mediaCodec->payloadType;\n\t\t\tparams.mimeType       = mediaCodec->mimeType;\n\t\t\tparams.clockRate      = mediaCodec->clockRate;\n\t\t\tparams.cname          = this->rtpParameters.rtcp.cname;\n\t\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\t\tparams.temporalLayers = encoding.temporalLayers;\n\n\t\t\t// Check in band FEC in codec parameters.\n\t\t\tif (mediaCodec->parameters.HasInteger(\"useinbandfec\") && mediaCodec->parameters.GetInteger(\"useinbandfec\") == 1)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtp, \"in band FEC enabled\");\n\n\t\t\t\tparams.useInBandFec = true;\n\t\t\t}\n\n\t\t\t// Check DTX in codec parameters.\n\t\t\tif (mediaCodec->parameters.HasInteger(\"usedtx\") && mediaCodec->parameters.GetInteger(\"usedtx\") == 1)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\t\tparams.useDtx = true;\n\t\t\t}\n\n\t\t\t// Check DTX in the encoding.\n\t\t\tif (encoding.dtx)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\t\tparams.useDtx = true;\n\t\t\t}\n\n\t\t\tfor (const auto& fb : mediaCodec->rtcpFeedback)\n\t\t\t{\n\t\t\t\tif (!params.useNack && fb.type == \"nack\" && fb.parameter.empty())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"NACK supported\");\n\n\t\t\t\t\tparams.useNack = true;\n\t\t\t\t}\n\t\t\t\telse if (!params.usePli && fb.type == \"nack\" && fb.parameter == \"pli\")\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"PLI supported\");\n\n\t\t\t\t\tparams.usePli = true;\n\t\t\t\t}\n\t\t\t\telse if (!params.useFir && fb.type == \"ccm\" && fb.parameter == \"fir\")\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"FIR supported\");\n\n\t\t\t\t\tparams.useFir = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto* rtpStream =\n\t\t\t  new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid);\n\n\t\t\t// If the Consumer is paused, tell the RtpStreamSend.\n\t\t\tif (IsPaused() || IsProducerPaused())\n\t\t\t{\n\t\t\t\trtpStream->Pause();\n\t\t\t}\n\n\t\t\tconst auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\n\t\t\tif (rtxCodec && encoding.hasRtx)\n\t\t\t{\n\t\t\t\trtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc);\n\t\t\t}\n\n\t\t\tthis->rtpStreams.push_back(rtpStream);\n\t\t\tthis->mapMappedSsrcSsrc[consumableEncoding.ssrc] = encoding.ssrc;\n\t\t\tthis->mapSsrcRtpStream[encoding.ssrc]            = rtpStream;\n\t\t\tthis->mapRtpStreamSyncRequired[rtpStream]        = false;\n\n\t\t\t// Let's choose an initial output seq number between 1000 and 32768 to avoid\n\t\t\t// libsrtp bug:\n\t\t\t// https://github.com/versatica/mediasoup/issues/1437\n\t\t\tconst uint16_t initialOutputSeq =\n\t\t\t  Utils::Crypto::GetRandomUInt<uint16_t>(1000u, std::numeric_limits<uint16_t>::max() / 2);\n\n\t\t\tthis->mapRtpStreamRtpSeqManager[rtpStream] = RTC::SeqManager<uint16_t>(initialOutputSeq);\n\n\t\t\tthis->mapRtpStreamTargetLayerRetransmissionBuffer[rtpStream];\n\t\t}\n\t}\n\n\tvoid PipeConsumer::RequestKeyFrame()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tfor (auto& consumableRtpEncoding : this->consumableRtpEncodings)\n\t\t{\n\t\t\tauto mappedSsrc = consumableRtpEncoding.ssrc;\n\n\t\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t\t}\n\t}\n\n\tvoid PipeConsumer::OnRtpStreamScore(\n\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tvoid PipeConsumer::OnRtpStreamRetransmitRtpPacket(\n\t  RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnConsumerRetransmitRtpPacket(this, packet);\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventRtpAndKeyFrameTypes(packet, rtpStream->HasRtx());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/PipeTransport.cpp",
    "content": "#define MS_CLASS \"RTC::PipeTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/PipeTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\t// NOTE: PipeTransport just allows AEAD_AES_256_GCM SRTP crypto suite.\n\tRTC::SrtpSession::CryptoSuite PipeTransport::srtpCryptoSuite{\n\t\tRTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM\n\t};\n\t// MAster length of AEAD_AES_256_GCM.\n\tsize_t PipeTransport::srtpMasterLength{ 44 };\n\n\t/* Instance methods. */\n\n\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\n\tPipeTransport::PipeTransport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  const FBS::PipeTransport::PipeTransportOptions* options)\n\t  : RTC::Transport::Transport(shared, id, listener, options->base())\n\t{\n\t\tMS_TRACE();\n\n\t\tif (options->listenInfo()->protocol() != FBS::Transport::Protocol::UDP)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"unsupported listen protocol\");\n\t\t}\n\n\t\tthis->listenInfo.ip.assign(options->listenInfo()->ip()->str());\n\n\t\t// This may throw.\n\t\tUtils::IP::NormalizeIp(this->listenInfo.ip);\n\n\t\tif (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t{\n\t\t\tthis->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str());\n\t\t}\n\n\t\tif (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t{\n\t\t\tthis->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str());\n\t\t}\n\n\t\tthis->listenInfo.port               = options->listenInfo()->port();\n\t\tthis->listenInfo.portRange.min      = options->listenInfo()->portRange()->min();\n\t\tthis->listenInfo.portRange.max      = options->listenInfo()->portRange()->max();\n\t\tthis->listenInfo.sendBufferSize     = options->listenInfo()->sendBufferSize();\n\t\tthis->listenInfo.recvBufferSize     = options->listenInfo()->recvBufferSize();\n\t\tthis->listenInfo.flags.ipv6Only     = options->listenInfo()->flags()->ipv6Only();\n\t\tthis->listenInfo.flags.udpReusePort = options->listenInfo()->flags()->udpReusePort();\n\n\t\tthis->rtx = options->enableRtx();\n\n\t\tif (options->enableSrtp())\n\t\t{\n\t\t\tthis->srtpKey       = Utils::Crypto::GetRandomString(PipeTransport::srtpMasterLength);\n\t\t\tthis->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey);\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\tif (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0)\n\t\t\t{\n\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this,\n\t\t\t\t  this->listenInfo.ip,\n\t\t\t\t  this->listenInfo.portRange.min,\n\t\t\t\t  this->listenInfo.portRange.max,\n\t\t\t\t  this->listenInfo.flags,\n\t\t\t\t  portRangeHash);\n\t\t\t}\n\t\t\telse if (this->listenInfo.port != 0)\n\t\t\t{\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);\n\t\t\t}\n\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t// required.\n\t\t\telse\n\t\t\t{\n\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this,\n\t\t\t\t  this->listenInfo.ip,\n\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t  this->listenInfo.flags,\n\t\t\t\t  portRangeHash);\n\t\t\t}\n\n\t\t\tif (this->listenInfo.sendBufferSize != 0)\n\t\t\t{\n\t\t\t\t// NOTE: This may throw.\n\t\t\t\tthis->udpSocket->SetSendBufferSize(this->listenInfo.sendBufferSize);\n\t\t\t}\n\n\t\t\tif (this->listenInfo.recvBufferSize != 0)\n\t\t\t{\n\t\t\t\t// NOTE: This may throw.\n\t\t\t\tthis->udpSocket->SetRecvBufferSize(this->listenInfo.recvBufferSize);\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  info,\n\t\t\t  \"UDP socket buffer sizes [send:%\" PRIu32 \", recv:%\" PRIu32 \"]\",\n\t\t\t  udpSocket->GetSendBufferSize(),\n\t\t\t  udpSocket->GetRecvBufferSize());\n\n\t\t\t// NOTE: This may throw.\n\t\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t\t  this->id,\n\t\t\t  /*channelRequestHandler*/ this,\n\t\t\t  /*channelNotificationHandler*/ this);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\t// Must delete everything since the destructor won't be called.\n\n\t\t\tdelete this->udpSocket;\n\t\t\tthis->udpSocket = nullptr;\n\n\t\t\tthrow;\n\t\t}\n\t}\n\n\tPipeTransport::~PipeTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell the Transport parent class that we are about to destroy\n\t\t// the class instance.\n\t\tSetDestroying();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->udpSocket;\n\t\tthis->udpSocket = nullptr;\n\n\t\tdelete this->tuple;\n\t\tthis->tuple = nullptr;\n\n\t\tdelete this->srtpSendSession;\n\t\tthis->srtpSendSession = nullptr;\n\n\t\tdelete this->srtpRecvSession;\n\t\tthis->srtpRecvSession = nullptr;\n\t}\n\n\tflatbuffers::Offset<FBS::PipeTransport::DumpResponse> PipeTransport::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add tuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> tuple;\n\n\t\tif (this->tuple)\n\t\t{\n\t\t\ttuple = this->tuple->FillBuffer(builder);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::string localIp;\n\n\t\t\tif (this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tlocalIp = this->udpSocket->GetLocalIp();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlocalIp = this->listenInfo.announcedAddress;\n\t\t\t}\n\n\t\t\ttuple = FBS::Transport::CreateTupleDirect(\n\t\t\t  builder,\n\t\t\t  localIp.c_str(),\n\t\t\t  this->udpSocket->GetLocalPort(),\n\t\t\t  nullptr,\n\t\t\t  0,\n\t\t\t  FBS::Transport::Protocol::UDP);\n\t\t}\n\n\t\t// Add srtpParameters.\n\t\tflatbuffers::Offset<FBS::SrtpParameters::SrtpParameters> srtpParameters;\n\n\t\tif (HasSrtp())\n\t\t{\n\t\t\tsrtpParameters = FBS::SrtpParameters::CreateSrtpParametersDirect(\n\t\t\t  builder,\n\t\t\t  SrtpSession::CryptoSuiteToFbs(PipeTransport::srtpCryptoSuite),\n\t\t\t  this->srtpKeyBase64.c_str());\n\t\t}\n\n\t\t// Add base transport dump.\n\t\tauto base = Transport::FillBuffer(builder);\n\n\t\treturn FBS::PipeTransport::CreateDumpResponse(builder, base, tuple, this->rtx, srtpParameters);\n\t}\n\n\tflatbuffers::Offset<FBS::PipeTransport::GetStatsResponse> PipeTransport::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add tuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> tuple;\n\n\t\tif (this->tuple)\n\t\t{\n\t\t\ttuple = this->tuple->FillBuffer(builder);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::string localIp;\n\n\t\t\tif (this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tlocalIp = this->udpSocket->GetLocalIp();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlocalIp = this->listenInfo.announcedAddress;\n\t\t\t}\n\n\t\t\ttuple = FBS::Transport::CreateTupleDirect(\n\t\t\t  builder,\n\t\t\t  // localIp.\n\t\t\t  localIp.c_str(),\n\t\t\t  // localPort,\n\t\t\t  this->udpSocket->GetLocalPort(),\n\t\t\t  // remoteIp.\n\t\t\t  nullptr,\n\t\t\t  // remotePort.\n\t\t\t  0,\n\t\t\t  // protocol.\n\t\t\t  FBS::Transport::Protocol::UDP);\n\t\t}\n\n\t\t// Base Transport stats.\n\t\tauto base = Transport::FillBufferStats(builder);\n\n\t\treturn FBS::PipeTransport::CreateGetStatsResponse(builder, base, tuple);\n\t}\n\n\tvoid PipeTransport::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PipeTransport_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PipeTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PIPETRANSPORT_CONNECT:\n\t\t\t{\n\t\t\t\t// Ensure this method is not called twice.\n\t\t\t\tif (this->tuple)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"connect() already called\");\n\t\t\t\t}\n\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tstd::string ip;\n\t\t\t\t\tuint16_t port{ 0u };\n\t\t\t\t\tstd::string srtpKeyBase64;\n\n\t\t\t\t\tconst auto* body = request->data->body_as<FBS::PipeTransport::ConnectRequest>();\n\n\t\t\t\t\tauto srtpParametersPresent =\n\t\t\t\t\t  flatbuffers::IsFieldPresent(body, FBS::PipeTransport::ConnectRequest::VT_SRTPPARAMETERS);\n\n\t\t\t\t\tif (!HasSrtp() && srtpParametersPresent)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid srtpParameters (SRTP not enabled)\");\n\t\t\t\t\t}\n\t\t\t\t\telse if (HasSrtp())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!srtpParametersPresent)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing srtpParameters (SRTP enabled)\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst auto* srtpParameters = body->srtpParameters();\n\n\t\t\t\t\t\t// NOTE: We just use AEAD_AES_256_GCM as SRTP crypto suite in\n\t\t\t\t\t\t// PipeTransport.\n\t\t\t\t\t\tif (srtpParameters->cryptoSuite() != FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid/unsupported srtpParameters.cryptoSuite\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsrtpKeyBase64 = srtpParameters->keyBase64()->str();\n\n\t\t\t\t\t\tsize_t outLen;\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tauto* srtpKey = Utils::String::Base64Decode(srtpKeyBase64, outLen);\n\n\t\t\t\t\t\tif (outLen != PipeTransport::srtpMasterLength)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid decoded SRTP key length\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* srtpLocalKey  = new uint8_t[PipeTransport::srtpMasterLength];\n\t\t\t\t\t\tauto* srtpRemoteKey = new uint8_t[PipeTransport::srtpMasterLength];\n\n\t\t\t\t\t\tstd::memcpy(srtpLocalKey, this->srtpKey.c_str(), PipeTransport::srtpMasterLength);\n\t\t\t\t\t\tstd::memcpy(srtpRemoteKey, srtpKey, PipeTransport::srtpMasterLength);\n\n\t\t\t\t\t\ttry\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->srtpSendSession = new RTC::SrtpSession(\n\t\t\t\t\t\t\t  RTC::SrtpSession::Type::OUTBOUND,\n\t\t\t\t\t\t\t  PipeTransport::srtpCryptoSuite,\n\t\t\t\t\t\t\t  srtpLocalKey,\n\t\t\t\t\t\t\t  PipeTransport::srtpMasterLength);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\n\t\t\t\t\t\t\tMS_THROW_ERROR(\"error creating SRTP sending session: %s\", error.what());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttry\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->srtpRecvSession = new RTC::SrtpSession(\n\t\t\t\t\t\t\t  RTC::SrtpSession::Type::INBOUND,\n\t\t\t\t\t\t\t  PipeTransport::srtpCryptoSuite,\n\t\t\t\t\t\t\t  srtpRemoteKey,\n\t\t\t\t\t\t\t  PipeTransport::srtpMasterLength);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\n\t\t\t\t\t\t\tMS_THROW_ERROR(\"error creating SRTP receiving session: %s\", error.what());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!flatbuffers::IsFieldPresent(body, FBS::PipeTransport::ConnectRequest::VT_IP))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing ip\");\n\t\t\t\t\t}\n\n\t\t\t\t\tip = body->ip()->str();\n\n\t\t\t\t\t// This may throw.\n\t\t\t\t\tUtils::IP::NormalizeIp(ip);\n\n\t\t\t\t\tif (!body->port().has_value())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing port\");\n\t\t\t\t\t}\n\n\t\t\t\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\t\t\t\tport = body->port().value();\n\n\t\t\t\t\tint err;\n\n\t\t\t\t\tswitch (Utils::IP::GetFamily(ip))\n\t\t\t\t\t{\n\t\t\t\t\t\tcase AF_INET:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terr = uv_ip4_addr(\n\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t  static_cast<int>(port),\n\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in*>(&this->remoteAddrStorage));\n\n\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip4_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase AF_INET6:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terr = uv_ip6_addr(\n\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t  static_cast<int>(port),\n\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in6*>(&this->remoteAddrStorage));\n\n\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip6_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_ERROR(\"invalid IP '%s'\", ip.c_str());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create the tuple.\n\t\t\t\t\tthis->tuple = new RTC::TransportTuple(\n\t\t\t\t\t  this->udpSocket, reinterpret_cast<struct sockaddr*>(&this->remoteAddrStorage));\n\n\t\t\t\t\tif (!this->listenInfo.announcedAddress.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tdelete this->tuple;\n\t\t\t\t\tthis->tuple = nullptr;\n\n\t\t\t\t\tdelete this->srtpSendSession;\n\t\t\t\t\tthis->srtpSendSession = nullptr;\n\n\t\t\t\t\tdelete this->srtpRecvSession;\n\t\t\t\t\tthis->srtpRecvSession = nullptr;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\tauto tupleOffset = this->tuple->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\tauto responseOffset =\n\t\t\t\t  FBS::PipeTransport::CreateConnectResponse(request->GetBufferBuilder(), tupleOffset);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PipeTransport_ConnectResponse, responseOffset);\n\n\t\t\t\t// Assume we are connected (there is no much more we can do to know it)\n\t\t\t\t// and tell the parent class.\n\t\t\t\tRTC::Transport::Connected();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Transport::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid PipeTransport::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Pass it to the parent class.\n\t\tRTC::Transport::HandleNotification(notification);\n\t}\n\n\tinline bool PipeTransport::IsConnected() const\n\t{\n\t\treturn this->tuple ? true : false;\n\t}\n\n\tinline bool PipeTransport::HasSrtp() const\n\t{\n\t\treturn !this->srtpKey.empty();\n\t}\n\n\tvoid PipeTransport::SendRtpPacket(\n\t  RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetBuffer();\n\t\tauto len            = packet->GetLength();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtp(&data, &len))\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->tuple->Send(data, len, cb);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid PipeTransport::SendRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->tuple->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid PipeTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->tuple->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tvoid PipeTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, msg, len, ppid, cb);\n\t}\n\n\tvoid PipeTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, std::move(message), cb);\n\t}\n\n\tbool PipeTransport::SendData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->tuple->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\n\t\treturn true;\n\t}\n\n\tvoid PipeTransport::RecvStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpRecvSession)\n\t\t{\n\t\t\tthis->srtpRecvSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tvoid PipeTransport::SendStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpSendSession)\n\t\t{\n\t\t\tthis->srtpSendSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tinline void PipeTransport::OnPacketReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Increase receive transmission.\n\t\tRTC::Transport::DataReceived(len);\n\n\t\t// Check if it's RTCP.\n\t\tif (RTC::RTCP::Packet::IsRtcp(data, len))\n\t\t{\n\t\t\tOnRtcpDataReceived(tuple, data, len);\n\t\t}\n\t\t// Check if it's RTP.\n\t\telse if (RTC::RTP::Packet::IsRtp(data, len))\n\t\t{\n\t\t\tOnRtpDataReceived(tuple, data, len, bufferLen);\n\t\t}\n\t\t// Check if it's SCTP.\n\t\telse if (Settings::configuration.useBuiltInSctpStack && RTC::SCTP::Packet::IsSctp(data, len))\n\t\t{\n\t\t\tOnSctpDataReceived(tuple, data, len);\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && RTC::SctpAssociation::IsSctp(data, len))\n\t\t{\n\t\t\tOnSctpDataReceived(tuple, data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring received packet of unknown type\");\n\t\t}\n\t}\n\n\tinline void PipeTransport::OnRtpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTP packet.\n\t\tif (HasSrtp() && !this->srtpRecvSession->DecryptSrtp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\tconst auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\t\tif (!packet)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"DecryptSrtp() failed due to an invalid RTP packet\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  srtp,\n\t\t\t\t  \"DecryptSrtp() failed [ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetPayloadType(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\tdelete packet;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"received data is not a valid RTP packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Verify that the packet's tuple matches our tuple.\n\t\tif (!this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"ignoring RTP packet from unknown IP:port\");\n\n\t\t\t// Remove this SSRC.\n\t\t\tRecvStreamClosed(packet->GetSsrc());\n\n\t\t\tdelete packet;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtpPacket(packet);\n\t}\n\n\tinline void PipeTransport::OnRtcpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTCP packet.\n\t\tif (HasSrtp() && !this->srtpRecvSession->DecryptSrtcp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Verify that the packet's tuple matches our tuple.\n\t\tif (!this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring RTCP packet from unknown IP:port\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTCP::Packet::Parse(data, len);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"received data is not a valid RTCP compound or single packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtcpPacket(packet);\n\t}\n\n\tinline void PipeTransport::OnSctpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Verify that the packet's tuple matches our tuple.\n\t\tif (!this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(sctp, \"ignoring SCTP packet from unknown IP:port\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass it to the parent transport.\n\t\tRTC::Transport::ReceiveSctpData(data, len);\n\t}\n\n\tinline void PipeTransport::OnUdpSocketPacketReceived(\n\t  RTC::UdpSocket* socket,\n\t  const uint8_t* data,\n\t  size_t len,\n\t  size_t bufferLen,\n\t  const struct sockaddr* remoteAddr)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(socket, remoteAddr);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/PlainTransport.cpp",
    "content": "#define MS_CLASS \"RTC::PlainTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/PlainTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\t// AES-HMAC: http://tools.ietf.org/html/rfc3711\n\tstatic constexpr size_t SrtpMasterKeyLength{ 16 };\n\tstatic constexpr size_t SrtpMasterSaltLength{ 14 };\n\tstatic constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength };\n\t// AES-GCM: http://tools.ietf.org/html/rfc7714\n\tstatic constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 };\n\tstatic constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 };\n\tstatic constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength +\n\t\t                                                 SrtpAesGcm256MasterSaltLength };\n\tstatic constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 };\n\tstatic constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 };\n\tstatic constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength +\n\t\t                                                 SrtpAesGcm128MasterSaltLength };\n\n\t/* Class variables. */\n\n\t/* Instance methods. */\n\n\tPlainTransport::PlainTransport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  const FBS::PlainTransport::PlainTransportOptions* options)\n\t  : RTC::Transport::Transport(shared, id, listener, options->base())\n\t{\n\t\tMS_TRACE();\n\n\t\tif (options->listenInfo()->protocol() != FBS::Transport::Protocol::UDP)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"unsupported listen protocol\");\n\t\t}\n\n\t\tthis->listenInfo.ip.assign(options->listenInfo()->ip()->str());\n\n\t\t// This may throw.\n\t\tUtils::IP::NormalizeIp(this->listenInfo.ip);\n\n\t\tif (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t{\n\t\t\tthis->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str());\n\t\t}\n\n\t\tthis->listenInfo.port               = options->listenInfo()->port();\n\t\tthis->listenInfo.portRange.min      = options->listenInfo()->portRange()->min();\n\t\tthis->listenInfo.portRange.max      = options->listenInfo()->portRange()->max();\n\t\tthis->listenInfo.sendBufferSize     = options->listenInfo()->sendBufferSize();\n\t\tthis->listenInfo.recvBufferSize     = options->listenInfo()->recvBufferSize();\n\t\tthis->listenInfo.flags.ipv6Only     = options->listenInfo()->flags()->ipv6Only();\n\t\tthis->listenInfo.flags.udpReusePort = options->listenInfo()->flags()->udpReusePort();\n\n\t\tthis->rtcpMux = options->rtcpMux();\n\t\tthis->comedia = options->comedia();\n\n\t\tif (!this->rtcpMux)\n\t\t{\n\t\t\tif (flatbuffers::IsFieldPresent(options, FBS::PlainTransport::PlainTransportOptions::VT_RTCPLISTENINFO))\n\t\t\t{\n\t\t\t\tif (options->rtcpListenInfo()->protocol() != FBS::Transport::Protocol::UDP)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"unsupported RTCP listen protocol\");\n\t\t\t\t}\n\n\t\t\t\tthis->rtcpListenInfo.ip.assign(options->rtcpListenInfo()->ip()->str());\n\n\t\t\t\t// This may throw.\n\t\t\t\tUtils::IP::NormalizeIp(this->rtcpListenInfo.ip);\n\n\t\t\t\tif (flatbuffers::IsFieldPresent(options->rtcpListenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t\t\t{\n\t\t\t\t\tthis->rtcpListenInfo.announcedAddress.assign(\n\t\t\t\t\t  options->rtcpListenInfo()->announcedAddress()->str());\n\t\t\t\t}\n\n\t\t\t\tthis->rtcpListenInfo.port           = options->rtcpListenInfo()->port();\n\t\t\t\tthis->rtcpListenInfo.portRange.min  = options->rtcpListenInfo()->portRange()->min();\n\t\t\t\tthis->rtcpListenInfo.portRange.max  = options->rtcpListenInfo()->portRange()->max();\n\t\t\t\tthis->rtcpListenInfo.sendBufferSize = options->rtcpListenInfo()->sendBufferSize();\n\t\t\t\tthis->rtcpListenInfo.recvBufferSize = options->rtcpListenInfo()->recvBufferSize();\n\t\t\t\tthis->rtcpListenInfo.flags.ipv6Only = options->rtcpListenInfo()->flags()->ipv6Only();\n\t\t\t\tthis->rtcpListenInfo.flags.udpReusePort = options->rtcpListenInfo()->flags()->udpReusePort();\n\t\t\t}\n\t\t\t// If rtcpListenInfo is not given, just clone listenInfo.\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->rtcpListenInfo = this->listenInfo;\n\t\t\t}\n\t\t}\n\n\t\tif (options->enableSrtp())\n\t\t{\n\t\t\tauto srtpCryptoSuite = options->srtpCryptoSuite();\n\n\t\t\tif (!srtpCryptoSuite.has_value())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"missing srtpCryptoSuite\");\n\t\t\t}\n\n\t\t\t// NOTE: The SRTP crypto suite may change later on connect().\n\t\t\tthis->srtpCryptoSuite = SrtpSession::CryptoSuiteFromFbs(srtpCryptoSuite.value());\n\n\t\t\tswitch (this->srtpCryptoSuite)\n\t\t\t{\n\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:\n\t\t\t\t{\n\t\t\t\t\tthis->srtpMasterLength = SrtpAesGcm256MasterLength;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:\n\t\t\t\t{\n\t\t\t\t\tthis->srtpMasterLength = SrtpAesGcm128MasterLength;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t\t{\n\t\t\t\t\tthis->srtpMasterLength = SrtpMasterLength;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"unknown SRTP crypto suite\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->srtpKey       = Utils::Crypto::GetRandomString(this->srtpMasterLength);\n\t\t\tthis->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey);\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\tif (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0)\n\t\t\t{\n\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this,\n\t\t\t\t  this->listenInfo.ip,\n\t\t\t\t  this->listenInfo.portRange.min,\n\t\t\t\t  this->listenInfo.portRange.max,\n\t\t\t\t  this->listenInfo.flags,\n\t\t\t\t  portRangeHash);\n\t\t\t}\n\t\t\telse if (this->listenInfo.port != 0)\n\t\t\t{\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags);\n\t\t\t}\n\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t// required.\n\t\t\telse\n\t\t\t{\n\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\tthis->udpSocket = new RTC::UdpSocket(\n\t\t\t\t  this,\n\t\t\t\t  this->listenInfo.ip,\n\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t  this->listenInfo.flags,\n\t\t\t\t  portRangeHash);\n\t\t\t}\n\n\t\t\tif (this->listenInfo.sendBufferSize != 0)\n\t\t\t{\n\t\t\t\t// NOTE: This may throw.\n\t\t\t\tthis->udpSocket->SetSendBufferSize(this->listenInfo.sendBufferSize);\n\t\t\t}\n\n\t\t\tif (this->listenInfo.recvBufferSize != 0)\n\t\t\t{\n\t\t\t\t// NOTE: This may throw.\n\t\t\t\tthis->udpSocket->SetRecvBufferSize(this->listenInfo.recvBufferSize);\n\t\t\t}\n\n\t\t\tif (!this->rtcpMux)\n\t\t\t{\n\t\t\t\tif (this->rtcpListenInfo.portRange.min != 0 && this->rtcpListenInfo.portRange.max != 0)\n\t\t\t\t{\n\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\tthis->rtcpUdpSocket = new RTC::UdpSocket(\n\t\t\t\t\t  this,\n\t\t\t\t\t  this->rtcpListenInfo.ip,\n\t\t\t\t\t  this->rtcpListenInfo.portRange.min,\n\t\t\t\t\t  this->rtcpListenInfo.portRange.max,\n\t\t\t\t\t  this->rtcpListenInfo.flags,\n\t\t\t\t\t  portRangeHash);\n\t\t\t\t}\n\t\t\t\telse if (this->rtcpListenInfo.port != 0)\n\t\t\t\t{\n\t\t\t\t\tthis->rtcpUdpSocket = new RTC::UdpSocket(\n\t\t\t\t\t  this, this->rtcpListenInfo.ip, this->rtcpListenInfo.port, this->rtcpListenInfo.flags);\n\t\t\t\t}\n\t\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t\t// required.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\tthis->rtcpUdpSocket = new RTC::UdpSocket(\n\t\t\t\t\t  this,\n\t\t\t\t\t  this->rtcpListenInfo.ip,\n\t\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t\t  this->rtcpListenInfo.flags,\n\t\t\t\t\t  portRangeHash);\n\t\t\t\t}\n\n\t\t\t\tif (this->rtcpListenInfo.sendBufferSize != 0)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\tthis->rtcpUdpSocket->SetSendBufferSize(this->rtcpListenInfo.sendBufferSize);\n\t\t\t\t}\n\n\t\t\t\tif (this->rtcpListenInfo.recvBufferSize != 0)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\tthis->rtcpUdpSocket->SetRecvBufferSize(this->rtcpListenInfo.recvBufferSize);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// NOTE: This may throw.\n\t\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t\t  this->id,\n\t\t\t  /*channelRequestHandler*/ this,\n\t\t\t  /*channelNotificationHandler*/ this);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\tdelete this->udpSocket;\n\t\t\tthis->udpSocket = nullptr;\n\n\t\t\tdelete this->rtcpUdpSocket;\n\t\t\tthis->rtcpUdpSocket = nullptr;\n\n\t\t\tthrow;\n\t\t}\n\t}\n\n\tPlainTransport::~PlainTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell the Transport parent class that we are about to destroy\n\t\t// the class instance.\n\t\tSetDestroying();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->udpSocket;\n\t\tthis->udpSocket = nullptr;\n\n\t\tdelete this->rtcpUdpSocket;\n\t\tthis->rtcpUdpSocket = nullptr;\n\n\t\tdelete this->tuple;\n\t\tthis->tuple = nullptr;\n\n\t\tdelete this->rtcpTuple;\n\t\tthis->rtcpTuple = nullptr;\n\n\t\tdelete this->srtpSendSession;\n\t\tthis->srtpSendSession = nullptr;\n\n\t\tdelete this->srtpRecvSession;\n\t\tthis->srtpRecvSession = nullptr;\n\t}\n\n\tflatbuffers::Offset<FBS::PlainTransport::DumpResponse> PlainTransport::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add tuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> tuple;\n\n\t\tif (this->tuple)\n\t\t{\n\t\t\ttuple = this->tuple->FillBuffer(builder);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::string localIp;\n\n\t\t\tif (this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tlocalIp = this->udpSocket->GetLocalIp();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlocalIp = this->listenInfo.announcedAddress;\n\t\t\t}\n\n\t\t\ttuple = FBS::Transport::CreateTupleDirect(\n\t\t\t  builder,\n\t\t\t  localIp.c_str(),\n\t\t\t  this->udpSocket->GetLocalPort(),\n\t\t\t  nullptr,\n\t\t\t  0,\n\t\t\t  FBS::Transport::Protocol::UDP);\n\t\t}\n\n\t\t// Add rtcpTuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> rtcpTuple;\n\n\t\tif (!this->rtcpMux)\n\t\t{\n\t\t\tif (this->rtcpTuple)\n\t\t\t{\n\t\t\t\trtcpTuple = this->rtcpTuple->FillBuffer(builder);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstd::string localIp;\n\n\t\t\t\tif (this->rtcpListenInfo.announcedAddress.empty())\n\t\t\t\t{\n\t\t\t\t\tlocalIp = this->rtcpUdpSocket->GetLocalIp();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tlocalIp = this->rtcpListenInfo.announcedAddress;\n\t\t\t\t}\n\n\t\t\t\trtcpTuple = FBS::Transport::CreateTupleDirect(\n\t\t\t\t  builder,\n\t\t\t\t  localIp.c_str(),\n\t\t\t\t  this->rtcpUdpSocket->GetLocalPort(),\n\t\t\t\t  nullptr,\n\t\t\t\t  0,\n\t\t\t\t  FBS::Transport::Protocol::UDP);\n\t\t\t}\n\t\t}\n\n\t\t// Add srtpParameters.\n\t\tflatbuffers::Offset<FBS::SrtpParameters::SrtpParameters> srtpParameters;\n\n\t\tif (HasSrtp())\n\t\t{\n\t\t\tsrtpParameters = FBS::SrtpParameters::CreateSrtpParametersDirect(\n\t\t\t  builder, SrtpSession::CryptoSuiteToFbs(this->srtpCryptoSuite), this->srtpKeyBase64.c_str());\n\t\t}\n\n\t\t// Add base transport dump.\n\t\tauto base = Transport::FillBuffer(builder);\n\n\t\treturn FBS::PlainTransport::CreateDumpResponse(\n\t\t  builder, base, this->rtcpMux, this->comedia, tuple, rtcpTuple, srtpParameters);\n\t}\n\n\tflatbuffers::Offset<FBS::PlainTransport::GetStatsResponse> PlainTransport::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add tuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> tuple;\n\n\t\tif (this->tuple)\n\t\t{\n\t\t\ttuple = this->tuple->FillBuffer(builder);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::string localIp;\n\n\t\t\tif (this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tlocalIp = this->udpSocket->GetLocalIp();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlocalIp = this->listenInfo.announcedAddress;\n\t\t\t}\n\n\t\t\ttuple = FBS::Transport::CreateTupleDirect(\n\t\t\t  builder,\n\t\t\t  // localIp.\n\t\t\t  localIp.c_str(),\n\t\t\t  // localPort,\n\t\t\t  this->udpSocket->GetLocalPort(),\n\t\t\t  // remoteIp.\n\t\t\t  nullptr,\n\t\t\t  // remotePort.\n\t\t\t  0,\n\t\t\t  // protocol.\n\t\t\t  FBS::Transport::Protocol::UDP);\n\t\t}\n\n\t\t// Add rtcpTuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> rtcpTuple;\n\n\t\tif (!this->rtcpMux && this->rtcpTuple)\n\t\t{\n\t\t\trtcpTuple = this->rtcpTuple->FillBuffer(builder);\n\t\t}\n\n\t\t// Base Transport stats.\n\t\tauto base = Transport::FillBufferStats(builder);\n\n\t\treturn FBS::PlainTransport::CreateGetStatsResponse(\n\t\t  builder, base, this->rtcpMux, this->comedia, tuple, rtcpTuple);\n\t}\n\n\tvoid PlainTransport::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PlainTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PlainTransport_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PLAINTRANSPORT_CONNECT:\n\t\t\t{\n\t\t\t\t// Ensure this method is not called twice.\n\t\t\t\tif (this->connectCalled)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"connect() already called\");\n\t\t\t\t}\n\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tstd::string ip;\n\t\t\t\t\tuint16_t port{ 0u };\n\t\t\t\t\tuint16_t rtcpPort{ 0u };\n\t\t\t\t\tstd::string srtpKeyBase64;\n\n\t\t\t\t\tconst auto* body = request->data->body_as<FBS::PlainTransport::ConnectRequest>();\n\n\t\t\t\t\tauto srtpParametersPresent = flatbuffers::IsFieldPresent(\n\t\t\t\t\t  body, FBS::PlainTransport::ConnectRequest::VT_SRTPPARAMETERS);\n\n\t\t\t\t\tif (!HasSrtp() && srtpParametersPresent)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid srtpParameters (SRTP not enabled)\");\n\t\t\t\t\t}\n\t\t\t\t\telse if (HasSrtp())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!srtpParametersPresent)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing srtpParameters (SRTP enabled)\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst auto* const srtpParameters = body->srtpParameters();\n\n\t\t\t\t\t\t// Update out SRTP crypto suite with the one used by the remote.\n\t\t\t\t\t\tauto previousSrtpCryptoSuite = this->srtpCryptoSuite;\n\t\t\t\t\t\tthis->srtpCryptoSuite = SrtpSession::CryptoSuiteFromFbs(srtpParameters->cryptoSuite());\n\n\t\t\t\t\t\tswitch (this->srtpCryptoSuite)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->srtpMasterLength = SrtpAesGcm256MasterLength;\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->srtpMasterLength = SrtpAesGcm128MasterLength;\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\t\t\t\t\tcase RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->srtpMasterLength = SrtpMasterLength;\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_ABORT(\"unknown SRTP crypto suite\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// If the SRTP crypto suite changed we must regenerate our SRTP key.\n\t\t\t\t\t\tif (this->srtpCryptoSuite != previousSrtpCryptoSuite)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->srtpKey       = Utils::Crypto::GetRandomString(this->srtpMasterLength);\n\t\t\t\t\t\t\tthis->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsrtpKeyBase64 = srtpParameters->keyBase64()->str();\n\n\t\t\t\t\t\tsize_t outLen;\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tauto* srtpKey = Utils::String::Base64Decode(srtpKeyBase64, outLen);\n\n\t\t\t\t\t\tif (outLen != this->srtpMasterLength)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid decoded SRTP key length\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* srtpLocalKey  = new uint8_t[this->srtpMasterLength];\n\t\t\t\t\t\tauto* srtpRemoteKey = new uint8_t[this->srtpMasterLength];\n\n\t\t\t\t\t\tstd::memcpy(srtpLocalKey, this->srtpKey.c_str(), this->srtpMasterLength);\n\t\t\t\t\t\tstd::memcpy(srtpRemoteKey, srtpKey, this->srtpMasterLength);\n\n\t\t\t\t\t\ttry\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->srtpSendSession = new RTC::SrtpSession(\n\t\t\t\t\t\t\t  RTC::SrtpSession::Type::OUTBOUND,\n\t\t\t\t\t\t\t  this->srtpCryptoSuite,\n\t\t\t\t\t\t\t  srtpLocalKey,\n\t\t\t\t\t\t\t  this->srtpMasterLength);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\n\t\t\t\t\t\t\tMS_THROW_ERROR(\"error creating SRTP sending session: %s\", error.what());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\ttry\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->srtpRecvSession = new RTC::SrtpSession(\n\t\t\t\t\t\t\t  RTC::SrtpSession::Type::INBOUND,\n\t\t\t\t\t\t\t  this->srtpCryptoSuite,\n\t\t\t\t\t\t\t  srtpRemoteKey,\n\t\t\t\t\t\t\t  this->srtpMasterLength);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\n\t\t\t\t\t\t\tMS_THROW_ERROR(\"error creating SRTP receiving session: %s\", error.what());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdelete[] srtpLocalKey;\n\t\t\t\t\t\tdelete[] srtpRemoteKey;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!this->comedia)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!flatbuffers::IsFieldPresent(body, FBS::PlainTransport::ConnectRequest::VT_IP))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing ip\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tip = body->ip()->str();\n\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tUtils::IP::NormalizeIp(ip);\n\n\t\t\t\t\t\tif (!body->port().has_value())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing port\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\t\t\t\t\tport = body->port().value();\n\n\t\t\t\t\t\tif (body->rtcpPort().has_value())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (this->rtcpMux)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"cannot set rtcpPort with rtcpMux enabled\");\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\t\t\t\t\t\trtcpPort = body->rtcpPort().value();\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!this->rtcpMux)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing rtcpPort (required with rtcpMux disabled)\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tint err;\n\n\t\t\t\t\t\tswitch (Utils::IP::GetFamily(ip))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase AF_INET:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\terr = uv_ip4_addr(\n\t\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t\t  static_cast<int>(port),\n\t\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in*>(&this->remoteAddrStorage));\n\n\t\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip4_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcase AF_INET6:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\terr = uv_ip6_addr(\n\t\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t\t  static_cast<int>(port),\n\t\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in6*>(&this->remoteAddrStorage));\n\n\t\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip6_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"invalid IP '%s'\", ip.c_str());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create the tuple.\n\t\t\t\t\t\tthis->tuple = new RTC::TransportTuple(\n\t\t\t\t\t\t  this->udpSocket, reinterpret_cast<struct sockaddr*>(&this->remoteAddrStorage));\n\n\t\t\t\t\t\tif (!this->listenInfo.announcedAddress.empty())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!this->rtcpMux)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tswitch (Utils::IP::GetFamily(ip))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase AF_INET:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\terr = uv_ip4_addr(\n\t\t\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t\t\t  static_cast<int>(rtcpPort),\n\t\t\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in*>(&this->rtcpRemoteAddrStorage));\n\n\t\t\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip4_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tcase AF_INET6:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\terr = uv_ip6_addr(\n\t\t\t\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t\t\t\t  static_cast<int>(rtcpPort),\n\t\t\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr_in6*>(&this->rtcpRemoteAddrStorage));\n\n\t\t\t\t\t\t\t\t\tif (err != 0)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"uv_ip6_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tMS_THROW_ERROR(\"invalid IP '%s'\", ip.c_str());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Create the tuple.\n\t\t\t\t\t\t\tthis->rtcpTuple = new RTC::TransportTuple(\n\t\t\t\t\t\t\t  this->rtcpUdpSocket,\n\t\t\t\t\t\t\t  reinterpret_cast<struct sockaddr*>(&this->rtcpRemoteAddrStorage));\n\n\t\t\t\t\t\t\tif (!this->rtcpListenInfo.announcedAddress.empty())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->rtcpTuple->SetLocalAnnouncedAddress(this->rtcpListenInfo.announcedAddress);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tdelete this->tuple;\n\t\t\t\t\tthis->tuple = nullptr;\n\n\t\t\t\t\tdelete this->rtcpTuple;\n\t\t\t\t\tthis->rtcpTuple = nullptr;\n\n\t\t\t\t\tdelete this->srtpSendSession;\n\t\t\t\t\tthis->srtpSendSession = nullptr;\n\n\t\t\t\t\tdelete this->srtpRecvSession;\n\t\t\t\t\tthis->srtpRecvSession = nullptr;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\tthis->connectCalled = true;\n\n\t\t\t\t// Tell the caller about the selected local DTLS role.\n\t\t\t\tflatbuffers::Offset<FBS::Transport::Tuple> tupleOffset;\n\t\t\t\tflatbuffers::Offset<FBS::Transport::Tuple> rtcpTupleOffset;\n\t\t\t\tflatbuffers::Offset<FBS::SrtpParameters::SrtpParameters> srtpParametersOffset;\n\n\t\t\t\tif (this->tuple)\n\t\t\t\t{\n\t\t\t\t\ttupleOffset = this->tuple->FillBuffer(request->GetBufferBuilder());\n\t\t\t\t}\n\n\t\t\t\tif (!this->rtcpMux && this->rtcpTuple)\n\t\t\t\t{\n\t\t\t\t\trtcpTupleOffset = this->rtcpTuple->FillBuffer(request->GetBufferBuilder());\n\t\t\t\t}\n\n\t\t\t\tif (HasSrtp())\n\t\t\t\t{\n\t\t\t\t\tsrtpParametersOffset = FBS::SrtpParameters::CreateSrtpParametersDirect(\n\t\t\t\t\t  request->GetBufferBuilder(),\n\t\t\t\t\t  SrtpSession::CryptoSuiteToFbs(this->srtpCryptoSuite),\n\t\t\t\t\t  this->srtpKeyBase64.c_str());\n\t\t\t\t}\n\n\t\t\t\tauto responseOffset = FBS::PlainTransport::CreateConnectResponse(\n\t\t\t\t  request->GetBufferBuilder(), tupleOffset, rtcpTupleOffset, srtpParametersOffset);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PlainTransport_ConnectResponse, responseOffset);\n\n\t\t\t\t// Assume we are connected (there is no much more we can do to know it)\n\t\t\t\t// and tell the parent class.\n\t\t\t\tRTC::Transport::Connected();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Transport::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid PlainTransport::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Pass it to the parent class.\n\t\tRTC::Transport::HandleNotification(notification);\n\t}\n\n\tinline bool PlainTransport::IsConnected() const\n\t{\n\t\treturn this->tuple ? true : false;\n\t}\n\n\tinline bool PlainTransport::HasSrtp() const\n\t{\n\t\treturn !this->srtpKey.empty();\n\t}\n\n\tinline bool PlainTransport::IsSrtpReady() const\n\t{\n\t\treturn HasSrtp() && this->srtpSendSession && this->srtpRecvSession;\n\t}\n\n\tvoid PlainTransport::SendRtpPacket(\n\t  RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetBuffer();\n\t\tauto len            = packet->GetLength();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtp(&data, &len))\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->tuple->Send(data, len, cb);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid PlainTransport::SendRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->rtcpMux)\n\t\t{\n\t\t\tthis->tuple->Send(data, len);\n\t\t}\n\t\telse if (this->rtcpTuple)\n\t\t{\n\t\t\tthis->rtcpTuple->Send(data, len);\n\t\t}\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid PlainTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\tif (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->rtcpMux)\n\t\t{\n\t\t\tthis->tuple->Send(data, len);\n\t\t}\n\t\telse if (this->rtcpTuple)\n\t\t{\n\t\t\tthis->rtcpTuple->Send(data, len);\n\t\t}\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tvoid PlainTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, msg, len, ppid, cb);\n\t}\n\n\tvoid PlainTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, std::move(message), cb);\n\t}\n\n\tbool PlainTransport::SendData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"not connected, cannot send SCTP data\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->tuple->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\n\t\treturn true;\n\t}\n\n\tvoid PlainTransport::RecvStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpRecvSession)\n\t\t{\n\t\t\tthis->srtpRecvSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tvoid PlainTransport::SendStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpSendSession)\n\t\t{\n\t\t\tthis->srtpSendSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tinline void PlainTransport::OnPacketReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Increase receive transmission.\n\t\tRTC::Transport::DataReceived(len);\n\n\t\t// Check if it's RTCP.\n\t\tif (RTC::RTCP::Packet::IsRtcp(data, len))\n\t\t{\n\t\t\tOnRtcpDataReceived(tuple, data, len);\n\t\t}\n\t\t// Check if it's RTP.\n\t\telse if (RTC::RTP::Packet::IsRtp(data, len))\n\t\t{\n\t\t\tOnRtpDataReceived(tuple, data, len, bufferLen);\n\t\t}\n\t\t// Check if it's SCTP.\n\t\telse if (Settings::configuration.useBuiltInSctpStack && RTC::SCTP::Packet::IsSctp(data, len))\n\t\t{\n\t\t\tOnSctpDataReceived(tuple, data, len);\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && RTC::SctpAssociation::IsSctp(data, len))\n\t\t{\n\t\t\tOnSctpDataReceived(tuple, data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring received packet of unknown type\");\n\t\t}\n\t}\n\n\tinline void PlainTransport::OnRtpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (HasSrtp() && !IsSrtpReady())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTP packet.\n\t\tif (HasSrtp() && !this->srtpRecvSession->DecryptSrtp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\tconst auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\t\tif (!packet)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"DecryptSrtp() failed due to an invalid RTP packet\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  srtp,\n\t\t\t\t  \"DecryptSrtp() failed [ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetPayloadType(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\tdelete packet;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"received data is not a valid RTP packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If we don't have a RTP tuple yet, check whether comedia mode is set.\n\t\tif (!this->tuple)\n\t\t{\n\t\t\tif (!this->comedia)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtp, \"ignoring RTP packet while not connected\");\n\n\t\t\t\t// Remove this SSRC.\n\t\t\t\tRecvStreamClosed(packet->GetSsrc());\n\n\t\t\t\tdelete packet;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(rtp, \"setting RTP tuple (comedia mode enabled)\");\n\n\t\t\tauto wasConnected = IsConnected();\n\n\t\t\tthis->tuple = new RTC::TransportTuple(tuple);\n\n\t\t\tif (!this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tthis->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress);\n\t\t\t}\n\n\t\t\t// If not yet connected do it now.\n\t\t\tif (!wasConnected)\n\t\t\t{\n\t\t\t\t// Notify the Node PlainTransport.\n\t\t\t\tEmitTuple();\n\n\t\t\t\tRTC::Transport::Connected();\n\t\t\t}\n\t\t}\n\t\t// Otherwise, if RTP tuple is set, verify that it matches the origin\n\t\t// of the packet.\n\t\telse if (!this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"ignoring RTP packet from unknown IP:port\");\n\n\t\t\t// Remove this SSRC.\n\t\t\tRecvStreamClosed(packet->GetSsrc());\n\n\t\t\tdelete packet;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtpPacket(packet);\n\t}\n\n\tinline void PlainTransport::OnRtcpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (HasSrtp() && !IsSrtpReady())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTCP packet.\n\t\tif (HasSrtp() && !this->srtpRecvSession->DecryptSrtcp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// If we don't have a RTP tuple yet, check whether RTCP-mux and comedia\n\t\t// mode are set.\n\t\tif (this->rtcpMux && !this->tuple)\n\t\t{\n\t\t\tif (!this->comedia)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring RTCP packet while not connected\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(rtp, \"setting RTP tuple (comedia mode enabled)\");\n\n\t\t\tauto wasConnected = IsConnected();\n\n\t\t\tthis->tuple = new RTC::TransportTuple(tuple);\n\n\t\t\tif (!this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tthis->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress);\n\t\t\t}\n\n\t\t\t// If not yet connected do it now.\n\t\t\tif (!wasConnected)\n\t\t\t{\n\t\t\t\t// Notify the Node PlainTransport.\n\t\t\t\tEmitTuple();\n\n\t\t\t\tRTC::Transport::Connected();\n\t\t\t}\n\t\t}\n\t\t// Otherwise, if RTCP-mux is unset and RTCP tuple is unset, set it if we\n\t\t// are in comedia mode.\n\t\telse if (!this->rtcpMux && !this->rtcpTuple)\n\t\t{\n\t\t\tif (!this->comedia)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring RTCP packet while not connected\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(rtcp, \"setting RTCP tuple (comedia mode enabled)\");\n\n\t\t\tthis->rtcpTuple = new RTC::TransportTuple(tuple);\n\n\t\t\tif (!this->rtcpListenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tthis->rtcpTuple->SetLocalAnnouncedAddress(this->rtcpListenInfo.announcedAddress);\n\t\t\t}\n\n\t\t\t// Notify the Node PlainTransport.\n\t\t\tEmitRtcpTuple();\n\t\t}\n\t\t// If RTCP-mux verify that the packet's tuple matches our RTP tuple.\n\t\telse if (this->rtcpMux && !this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring RTCP packet from unknown IP:port\");\n\n\t\t\treturn;\n\t\t}\n\t\t// If no RTCP-mux verify that the packet's tuple matches our RTCP tuple.\n\t\telse if (!this->rtcpMux && !this->rtcpTuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring RTCP packet from unknown IP:port\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTCP::Packet::Parse(data, len);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"received data is not a valid RTCP compound or single packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtcpPacket(packet);\n\t}\n\n\tinline void PlainTransport::OnSctpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// If we don't have a RTP tuple yet, check whether comedia mode is set.\n\t\tif (!this->tuple)\n\t\t{\n\t\t\tif (!this->comedia)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(sctp, \"ignoring SCTP packet while not connected\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(sctp, \"setting RTP/SCTP tuple (comedia mode enabled)\");\n\n\t\t\tauto wasConnected = IsConnected();\n\n\t\t\tthis->tuple = new RTC::TransportTuple(tuple);\n\n\t\t\tif (!this->listenInfo.announcedAddress.empty())\n\t\t\t{\n\t\t\t\tthis->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress);\n\t\t\t}\n\n\t\t\t// If not yet connected do it now.\n\t\t\tif (!wasConnected)\n\t\t\t{\n\t\t\t\t// Notify the Node PlainTransport.\n\t\t\t\tEmitTuple();\n\n\t\t\t\tRTC::Transport::Connected();\n\t\t\t}\n\t\t}\n\t\t// Otherwise, if RTP tuple is set, verify that it matches the origin\n\t\t// of the packet.\n\t\tif (!this->tuple->Compare(tuple))\n\t\t{\n\t\t\tMS_DEBUG_TAG(sctp, \"ignoring SCTP packet from unknown IP:port\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass it to the parent transport.\n\t\tRTC::Transport::ReceiveSctpData(data, len);\n\t}\n\n\tinline void PlainTransport::EmitTuple() const\n\t{\n\t\tauto tuple = this->tuple->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\tauto notification = FBS::PlainTransport::CreateTupleNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), tuple);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::PLAINTRANSPORT_TUPLE,\n\t\t  FBS::Notification::Body::PlainTransport_TupleNotification,\n\t\t  notification);\n\t}\n\n\tinline void PlainTransport::EmitRtcpTuple() const\n\t{\n\t\tauto rtcpTuple =\n\t\t  this->rtcpTuple->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\tauto notification = FBS::PlainTransport::CreateRtcpTupleNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), rtcpTuple);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::PLAINTRANSPORT_RTCP_TUPLE,\n\t\t  FBS::Notification::Body::PlainTransport_RtcpTupleNotification,\n\t\t  notification);\n\t}\n\n\tinline void PlainTransport::OnUdpSocketPacketReceived(\n\t  RTC::UdpSocket* socket,\n\t  const uint8_t* data,\n\t  size_t len,\n\t  size_t bufferLen,\n\t  const struct sockaddr* remoteAddr)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(socket, remoteAddr);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/PortManager.cpp",
    "content": "#define MS_CLASS \"PortManager\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/PortManager.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <tuple> // std:make_tuple()\n\n/* Static methods for UV callbacks. */\n\n// NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by\n// ensuring that we call `delete xxx` with same type as `new xxx` before.\nstatic inline void onCloseUdp(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_udp_t*>(handle);\n}\n\nstatic inline void onCloseTcp(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_tcp_t*>(handle);\n}\n\ninline static void onFakeConnection(uv_stream_t* /*handle*/, int /*status*/)\n{\n\t// Do nothing.\n}\n\nnamespace RTC\n{\n\t/* Class variables. */\n\n\tthread_local absl::flat_hash_map<uint64_t, PortManager::PortRange> PortManager::mapPortRanges;\n\n\t/* Class methods. */\n\n\tuv_handle_t* PortManager::Bind(\n\t  Protocol protocol, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags)\n\t{\n\t\tMS_TRACE();\n\n\t\t// First normalize the IP. This may throw if invalid IP.\n\t\tUtils::IP::NormalizeIp(ip);\n\n\t\tint err;\n\t\tconst int family = Utils::IP::GetFamily(ip);\n\t\tstruct sockaddr_storage bindAddr{};\n\t\tuv_handle_t* uvHandle{ nullptr };\n\t\tstd::string protocolStr;\n\t\tconst uint8_t bitFlags = ConvertSocketFlags(flags, protocol, family);\n\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase Protocol::UDP:\n\t\t\t{\n\t\t\t\tprotocolStr.assign(\"udp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Protocol::TCP:\n\t\t\t{\n\t\t\t\tprotocolStr.assign(\"tcp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tswitch (family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\terr = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in*>(&bindAddr));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"uv_ip4_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\terr = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in6*>(&bindAddr));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"uv_ip6_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// This cannot happen.\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown IP family\");\n\t\t\t}\n\t\t}\n\n\t\t// Set the port into the sockaddr struct.\n\t\tswitch (family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\t(reinterpret_cast<struct sockaddr_in*>(&bindAddr))->sin_port = htons(port);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\t(reinterpret_cast<struct sockaddr_in6*>(&bindAddr))->sin6_port = htons(port);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// This cannot happen.\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown IP family\");\n\t\t\t}\n\t\t}\n\n\t\t// Try to bind on it.\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase Protocol::UDP:\n\t\t\t{\n\t\t\t\tuvHandle = reinterpret_cast<uv_handle_t*>(new uv_udp_t());\n\t\t\t\terr      = uv_udp_init_ex(\n\t\t\t\t  DepLibUV::GetLoop(), reinterpret_cast<uv_udp_t*>(uvHandle), UV_UDP_RECVMMSG);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Protocol::TCP:\n\t\t\t{\n\t\t\t\tuvHandle = reinterpret_cast<uv_handle_t*>(new uv_tcp_t());\n\t\t\t\terr      = uv_tcp_init(DepLibUV::GetLoop(), reinterpret_cast<uv_tcp_t*>(uvHandle));\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tswitch (protocol)\n\t\t\t{\n\t\t\t\tcase Protocol::UDP:\n\t\t\t\t{\n\t\t\t\t\tdelete reinterpret_cast<uv_udp_t*>(uvHandle);\n\n\t\t\t\t\tMS_THROW_ERROR(\"uv_udp_init_ex() failed: %s\", uv_strerror(err));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Protocol::TCP:\n\t\t\t\t{\n\t\t\t\t\tdelete reinterpret_cast<uv_tcp_t*>(uvHandle);\n\n\t\t\t\t\tMS_THROW_ERROR(\"uv_tcp_init() failed: %s\", uv_strerror(err));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase Protocol::UDP:\n\t\t\t{\n\t\t\t\terr = uv_udp_bind(\n\t\t\t\t  reinterpret_cast<uv_udp_t*>(uvHandle),\n\t\t\t\t  reinterpret_cast<const struct sockaddr*>(&bindAddr),\n\t\t\t\t  bitFlags);\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\t// If it failed, close the handle and check the reason.\n\t\t\t\t\tuv_close(uvHandle, static_cast<uv_close_cb>(onCloseUdp));\n\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"uv_udp_bind() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \"]: %s\",\n\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t  port,\n\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Protocol::TCP:\n\t\t\t{\n\t\t\t\terr = uv_tcp_bind(\n\t\t\t\t  reinterpret_cast<uv_tcp_t*>(uvHandle),\n\t\t\t\t  reinterpret_cast<const struct sockaddr*>(&bindAddr),\n\t\t\t\t  bitFlags);\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\t// If it failed, close the handle and check the reason.\n\t\t\t\t\tuv_close(uvHandle, static_cast<uv_close_cb>(onCloseTcp));\n\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"uv_tcp_bind() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \"]: %s\",\n\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t  port,\n\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\t// uv_tcp_bind() may succeed even if later uv_listen() fails, so\n\t\t\t\t// double check it.\n\t\t\t\terr = uv_listen(\n\t\t\t\t  reinterpret_cast<uv_stream_t*>(uvHandle),\n\t\t\t\t  256,\n\t\t\t\t  static_cast<uv_connection_cb>(onFakeConnection));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\t// If it failed, close the handle and check the reason.\n\t\t\t\t\tuv_close(uvHandle, static_cast<uv_close_cb>(onCloseTcp));\n\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"uv_listen() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \"]: %s\",\n\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t  port,\n\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"bind succeeded [protocol:%s, ip:'%s', port:%\" PRIu16 \"]\", protocolStr.c_str(), ip.c_str(), port);\n\n\t\treturn uvHandle;\n\t}\n\n\tuv_handle_t* PortManager::Bind(\n\t  Protocol protocol,\n\t  std::string& ip,\n\t  uint16_t minPort,\n\t  uint16_t maxPort,\n\t  RTC::Transport::SocketFlags& flags,\n\t  uint64_t& hash)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (maxPort < minPort)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"maxPort cannot be less than minPort\");\n\t\t}\n\n\t\t// First normalize the IP. This may throw if invalid IP.\n\t\tUtils::IP::NormalizeIp(ip);\n\n\t\tint err;\n\t\tconst int family = Utils::IP::GetFamily(ip);\n\t\tstruct sockaddr_storage bindAddr{};\n\t\tstd::string protocolStr;\n\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase Protocol::UDP:\n\t\t\t{\n\t\t\t\tprotocolStr.assign(\"udp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Protocol::TCP:\n\t\t\t{\n\t\t\t\tprotocolStr.assign(\"tcp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tswitch (family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\terr = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in*>(&bindAddr));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"uv_ip4_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\terr = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in6*>(&bindAddr));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"uv_ip6_addr() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// This cannot happen.\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown IP family\");\n\t\t\t}\n\t\t}\n\n\t\thash = GeneratePortRangeHash(protocol, std::addressof(bindAddr), minPort, maxPort);\n\n\t\tauto& portRange          = PortManager::GetOrCreatePortRange(hash, minPort, maxPort);\n\t\tconst size_t numPorts    = portRange.ports.size();\n\t\tconst size_t numAttempts = numPorts;\n\t\tsize_t attempt{ 0u };\n\t\tsize_t portIdx;\n\t\tuint16_t port;\n\t\tuv_handle_t* uvHandle{ nullptr };\n\t\tconst uint8_t bitFlags = ConvertSocketFlags(flags, protocol, family);\n\n\t\t// Choose a random port index to start from.\n\t\tportIdx = Utils::Crypto::GetRandomUInt<size_t>(\n\t\t  static_cast<uint32_t>(0), static_cast<uint32_t>(numPorts - 1));\n\n\t\t// Iterate all ports until getting one available. Fail if none found and also\n\t\t// if bind() fails N times in theoretically available ports.\n\t\twhile (true)\n\t\t{\n\t\t\t// Increase attempt number.\n\t\t\t++attempt;\n\n\t\t\t// If we have tried all the ports in the range throw.\n\t\t\tif (attempt > numAttempts)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t  \"no more available ports [protocol:%s, ip:'%s', numAttempt:%zu]\",\n\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t  ip.c_str(),\n\t\t\t\t  numAttempts);\n\t\t\t}\n\n\t\t\t// Increase current port index.\n\t\t\tportIdx = (portIdx + 1) % numPorts;\n\n\t\t\t// So the corresponding port is the vector position plus the RTC minimum port.\n\t\t\tport = static_cast<uint16_t>(portIdx + minPort);\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"testing port [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]\",\n\t\t\t  protocolStr.c_str(),\n\t\t\t  ip.c_str(),\n\t\t\t  port,\n\t\t\t  attempt,\n\t\t\t  numAttempts);\n\n\t\t\t// Check whether this port is not available.\n\t\t\tif (portRange.ports[portIdx])\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"port in use, trying again [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]\",\n\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t  ip.c_str(),\n\t\t\t\t  port,\n\t\t\t\t  attempt,\n\t\t\t\t  numAttempts);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Here we already have a theoretically available port. Now let's check\n\t\t\t// whether no other process is binding into it.\n\n\t\t\t// Set the chosen port into the sockaddr struct.\n\t\t\tswitch (family)\n\t\t\t{\n\t\t\t\tcase AF_INET:\n\t\t\t\t{\n\t\t\t\t\t(reinterpret_cast<struct sockaddr_in*>(&bindAddr))->sin_port = htons(port);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase AF_INET6:\n\t\t\t\t{\n\t\t\t\t\t(reinterpret_cast<struct sockaddr_in6*>(&bindAddr))->sin6_port = htons(port);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// This cannot happen.\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"unknown IP family\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Try to bind on it.\n\t\t\tswitch (protocol)\n\t\t\t{\n\t\t\t\tcase Protocol::UDP:\n\t\t\t\t{\n\t\t\t\t\tuvHandle = reinterpret_cast<uv_handle_t*>(new uv_udp_t());\n\t\t\t\t\terr      = uv_udp_init_ex(\n\t\t\t\t\t  DepLibUV::GetLoop(), reinterpret_cast<uv_udp_t*>(uvHandle), UV_UDP_RECVMMSG);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Protocol::TCP:\n\t\t\t\t{\n\t\t\t\t\tuvHandle = reinterpret_cast<uv_handle_t*>(new uv_tcp_t());\n\t\t\t\t\terr      = uv_tcp_init(DepLibUV::GetLoop(), reinterpret_cast<uv_tcp_t*>(uvHandle));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (err != 0)\n\t\t\t{\n\t\t\t\tswitch (protocol)\n\t\t\t\t{\n\t\t\t\t\tcase Protocol::UDP:\n\t\t\t\t\t{\n\t\t\t\t\t\tdelete reinterpret_cast<uv_udp_t*>(uvHandle);\n\n\t\t\t\t\t\tMS_THROW_ERROR(\"uv_udp_init_ex() failed: %s\", uv_strerror(err));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Protocol::TCP:\n\t\t\t\t\t{\n\t\t\t\t\t\tdelete reinterpret_cast<uv_tcp_t*>(uvHandle);\n\n\t\t\t\t\t\tMS_THROW_ERROR(\"uv_tcp_init() failed: %s\", uv_strerror(err));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch (protocol)\n\t\t\t{\n\t\t\t\tcase Protocol::UDP:\n\t\t\t\t{\n\t\t\t\t\terr = uv_udp_bind(\n\t\t\t\t\t  reinterpret_cast<uv_udp_t*>(uvHandle),\n\t\t\t\t\t  reinterpret_cast<const struct sockaddr*>(&bindAddr),\n\t\t\t\t\t  bitFlags);\n\n\t\t\t\t\tif (err != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\n\t\t\t\t\t\t  \"uv_udp_bind() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]: %s\",\n\t\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t  port,\n\t\t\t\t\t\t  attempt,\n\t\t\t\t\t\t  numAttempts,\n\t\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Protocol::TCP:\n\t\t\t\t{\n\t\t\t\t\terr = uv_tcp_bind(\n\t\t\t\t\t  reinterpret_cast<uv_tcp_t*>(uvHandle),\n\t\t\t\t\t  reinterpret_cast<const struct sockaddr*>(&bindAddr),\n\t\t\t\t\t  bitFlags);\n\n\t\t\t\t\tif (err != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\n\t\t\t\t\t\t  \"uv_tcp_bind() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]: %s\",\n\t\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t  port,\n\t\t\t\t\t\t  attempt,\n\t\t\t\t\t\t  numAttempts,\n\t\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t\t}\n\n\t\t\t\t\t// uv_tcp_bind() may succeed even if later uv_listen() fails, so\n\t\t\t\t\t// double check it.\n\t\t\t\t\tif (err == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\terr = uv_listen(\n\t\t\t\t\t\t  reinterpret_cast<uv_stream_t*>(uvHandle),\n\t\t\t\t\t\t  256,\n\t\t\t\t\t\t  static_cast<uv_connection_cb>(onFakeConnection));\n\n\t\t\t\t\t\tMS_WARN_DEV(\n\t\t\t\t\t\t  \"uv_listen() failed [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]: %s\",\n\t\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t\t  port,\n\t\t\t\t\t\t  attempt,\n\t\t\t\t\t\t  numAttempts,\n\t\t\t\t\t\t  uv_strerror(err));\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If it succeeded, exit the loop here.\n\t\t\tif (err == 0)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If it failed, close the handle and check the reason.\n\t\t\tswitch (protocol)\n\t\t\t{\n\t\t\t\tcase Protocol::UDP:\n\t\t\t\t{\n\t\t\t\t\tuv_close(uvHandle, static_cast<uv_close_cb>(onCloseUdp));\n\n\t\t\t\t\tbreak;\n\t\t\t\t};\n\n\t\t\t\tcase Protocol::TCP:\n\t\t\t\t{\n\t\t\t\t\tuv_close(uvHandle, static_cast<uv_close_cb>(onCloseTcp));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch (err)\n\t\t\t{\n\t\t\t\t// If bind() fails due to \"too many open files\" just throw.\n\t\t\t\tcase UV_EMFILE:\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"port bind failed due to too many open files [protocol:%s, ip:'%s', port:%\" PRIu16\n\t\t\t\t\t  \", attempt:%zu/%zu]\",\n\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t  port,\n\t\t\t\t\t  attempt,\n\t\t\t\t\t  numAttempts);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// If cannot bind in the given IP, throw.\n\t\t\t\tcase UV_EADDRNOTAVAIL:\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"port bind failed due to address not available [protocol:%s, ip:'%s', port:%\" PRIu16\n\t\t\t\t\t  \", attempt:%zu/%zu]\",\n\t\t\t\t\t  protocolStr.c_str(),\n\t\t\t\t\t  ip.c_str(),\n\t\t\t\t\t  port,\n\t\t\t\t\t  attempt,\n\t\t\t\t\t  numAttempts);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\t// Otherwise continue in the loop to try again with next port.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If here, we got an available port. Mark it as unavailable.\n\t\tportRange.ports[portIdx] = true;\n\n\t\t// Increase number of used ports in the range.\n\t\tportRange.numUsedPorts++;\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"bind succeeded [protocol:%s, ip:'%s', port:%\" PRIu16 \", attempt:%zu/%zu]\",\n\t\t  protocolStr.c_str(),\n\t\t  ip.c_str(),\n\t\t  port,\n\t\t  attempt,\n\t\t  numAttempts);\n\n\t\treturn uvHandle;\n\t}\n\n\tvoid PortManager::Unbind(uint64_t hash, uint16_t port)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = PortManager::mapPortRanges.find(hash);\n\n\t\t// This should not happen.\n\t\tif (it == PortManager::mapPortRanges.end())\n\t\t{\n\t\t\tMS_ERROR(\"hash %\" PRIu64 \" doesn't exist in the map\", hash);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto& portRange    = it->second;\n\t\tconst auto portIdx = static_cast<size_t>(port - portRange.minPort);\n\n\t\t// This should not happen.\n\t\tMS_ASSERT(portRange.ports.at(portIdx) == true, \"port %\" PRIu16 \" is not used\", port);\n\t\tMS_ASSERT(portRange.numUsedPorts > 0u, \"number of used ports is 0\");\n\n\t\t// Mark the port as available.\n\t\tportRange.ports[portIdx] = false;\n\n\t\t// Decrease number of used ports in the range.\n\t\tportRange.numUsedPorts--;\n\n\t\t// Remove vector if there are no used ports.\n\t\tif (portRange.numUsedPorts == 0u)\n\t\t{\n\t\t\tPortManager::mapPortRanges.erase(it);\n\t\t}\n\t}\n\n\tvoid PortManager::Dump(int indentation) const\n\t{\n\t\tMS_DUMP_CLEAN(indentation, \"<PortManager>\");\n\n\t\tfor (auto& kv : PortManager::mapPortRanges)\n\t\t{\n\t\t\tauto hash      = kv.first;\n\t\t\tauto portRange = kv.second;\n\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<PortRange>\");\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  hash: %\" PRIu64, hash);\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  minPort: %\" PRIu16, portRange.minPort);\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  maxPort: %zu\", portRange.minPort + portRange.ports.size() - 1);\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  numUsedPorts: %\" PRIu16, portRange.numUsedPorts);\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</PortRange>\");\n\t\t}\n\n\t\tMS_DUMP_CLEAN(indentation, \"</PortManager>\");\n\t}\n\n\t/*\n\t * Hash for IPv4.\n\t *\n\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |           MIN PORT            |           MAX PORT            |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |              IP               |           IP >> 2         |F|P|\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t *\n\t * Hash for IPv6.\n\t *\n\t *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |           MIN PORT            |           MAX PORT            |\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t * |IP[0] ^  IP[1] ^ IP[2] ^ IP[3] |           same >> 2       |F|P|\n\t * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\t */\n\tuint64_t PortManager::GeneratePortRangeHash(\n\t  Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint64_t hash{ 0u };\n\n\t\tswitch (bindAddr->ss_family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\tauto* bindAddrIn = reinterpret_cast<struct sockaddr_in*>(bindAddr);\n\n\t\t\t\t// We want it in network order.\n\t\t\t\tconst uint64_t address = bindAddrIn->sin_addr.s_addr;\n\n\t\t\t\thash |= static_cast<uint64_t>(minPort) << 48;\n\t\t\t\thash |= static_cast<uint64_t>(maxPort) << 32;\n\t\t\t\thash |= (address >> 2) << 2;\n\t\t\t\thash |= 0x0000; // AF_INET.\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\tauto* bindAddrIn6 = reinterpret_cast<struct sockaddr_in6*>(bindAddr);\n\t\t\t\tauto* a           = reinterpret_cast<uint32_t*>(std::addressof(bindAddrIn6->sin6_addr));\n\n\t\t\t\tconst auto address = a[0] ^ a[1] ^ a[2] ^ a[3];\n\n\t\t\t\thash |= static_cast<uint64_t>(minPort) << 48;\n\t\t\t\thash |= static_cast<uint64_t>(maxPort) << 32;\n\t\t\t\thash |= static_cast<uint64_t>(address) << 16;\n\t\t\t\thash |= (static_cast<uint64_t>(address) >> 2) << 2;\n\t\t\t\thash |= 0x0002; // AF_INET6.\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// This cannot happen.\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown IP family\");\n\t\t\t}\n\t\t}\n\n\t\t// Override least significant bit with protocol information:\n\t\t// - If UDP, start with 0.\n\t\t// - If TCP, start with 1.\n\t\tif (protocol == Protocol::UDP)\n\t\t{\n\t\t\thash |= 0x0000;\n\t\t}\n\t\telse\n\t\t{\n\t\t\thash |= 0x0001;\n\t\t}\n\n\t\treturn hash;\n\t}\n\n\tPortManager::PortRange& PortManager::GetOrCreatePortRange(\n\t  uint64_t hash, uint16_t minPort, uint16_t maxPort)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = PortManager::mapPortRanges.find(hash);\n\n\t\t// If the hash is already handled, return its port range.\n\t\tif (it != PortManager::mapPortRanges.end())\n\t\t{\n\t\t\tauto& portRange = it->second;\n\n\t\t\treturn portRange;\n\t\t}\n\n\t\tconst uint16_t numPorts = maxPort - minPort + 1;\n\n\t\t// Emplace a new vector filled with numPorts false values, meaning that\n\t\t// all ports are available.\n\t\tauto pair = PortManager::mapPortRanges.emplace(\n\t\t  std::piecewise_construct, std::make_tuple(hash), std::make_tuple(numPorts, minPort));\n\n\t\t// pair.first is an iterator to the inserted value.\n\t\tauto& portRange = pair.first->second;\n\n\t\treturn portRange;\n\t}\n\n\tuint8_t PortManager::ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint8_t bitFlags{ 0b00000000 };\n\n\t\t// Ignore ipv6Only in IPv4, otherwise libuv will throw.\n\t\tif (flags.ipv6Only && family == AF_INET6)\n\t\t{\n\t\t\tswitch (protocol)\n\t\t\t{\n\t\t\t\tcase Protocol::UDP:\n\t\t\t\t{\n\t\t\t\t\tbitFlags |= UV_UDP_IPV6ONLY;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Protocol::TCP:\n\t\t\t\t{\n\t\t\t\t\tbitFlags |= UV_TCP_IPV6ONLY;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Ignore udpReusePort in TCP, otherwise libuv will throw.\n\t\tif (flags.udpReusePort && protocol == Protocol::UDP)\n\t\t{\n\t\t\tbitFlags |= UV_UDP_REUSEADDR;\n\t\t}\n\n\t\treturn bitFlags;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/Producer.cpp",
    "content": "#define MS_CLASS \"RTC::Producer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Producer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <absl/container/inlined_vector.h>\n#include <cstring> // std::memcpy()\n\nnamespace RTC\n{\n\t/* Static */\n\n\tstatic constexpr size_t ProducerSendBufferSize{ 65536 };\n\tstatic thread_local uint8_t ProducerSendBuffer[ProducerSendBufferSize];\n\tstatic constexpr unsigned int SendNackDelay{ 10u }; // In ms.\n\n\t/* Instance methods. */\n\n\tProducer::Producer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Producer::Listener* listener,\n\t  const FBS::Transport::ProduceRequest* data)\n\t  : id(id), shared(shared), listener(listener), kind(RTC::Media::Kind(data->kind()))\n\t{\n\t\tMS_TRACE();\n\n\t\t// This may throw.\n\t\tthis->rtpParameters = RTC::RtpParameters(data->rtpParameters());\n\n\t\t// Evaluate type.\n\t\tauto type = RTC::RtpParameters::GetType(this->rtpParameters);\n\n\t\tif (!type.has_value())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid RTP parameters\");\n\t\t}\n\n\t\tthis->type = type.value();\n\n\t\t// Reserve a slot in rtpStreamByEncodingIdx and rtpStreamsScores vectors\n\t\t// for each RTP stream.\n\t\tthis->rtpStreamByEncodingIdx.resize(this->rtpParameters.encodings.size(), nullptr);\n\t\tthis->rtpStreamScores.resize(this->rtpParameters.encodings.size(), 0u);\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tif (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType))\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"%s codec not supported for %s\",\n\t\t\t  mediaCodec->mimeType.ToString().c_str(),\n\t\t\t  RTC::RtpParameters::GetTypeString(this->type).c_str());\n\t\t}\n\n\t\tfor (const auto& codec : *data->rtpMapping()->codecs())\n\t\t{\n\t\t\tthis->rtpMapping.codecs[codec->payloadType()] = codec->mappedPayloadType();\n\t\t}\n\n\t\tconst auto* encodings = data->rtpMapping()->encodings();\n\n\t\tthis->rtpMapping.encodings.reserve(encodings->size());\n\n\t\tfor (const auto& encoding : *encodings)\n\t\t{\n\t\t\tthis->rtpMapping.encodings.emplace_back();\n\n\t\t\tauto& encodingMapping = this->rtpMapping.encodings.back();\n\n\t\t\tif (auto ssrc = encoding->ssrc(); ssrc.has_value())\n\t\t\t{\n\t\t\t\tencodingMapping.ssrc = ssrc.value();\n\t\t\t}\n\n\t\t\t// rid is optional.\n\t\t\t// However ssrc or rid must be present (if more than 1 encoding).\n\t\t\tif (\n\t\t\t  encodings->size() > 1 && !encoding->ssrc().has_value() &&\n\t\t\t  !flatbuffers::IsFieldPresent(encoding, FBS::RtpParameters::EncodingMapping::VT_RID))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"wrong entry in rtpMapping.encodings (missing ssrc or rid)\");\n\t\t\t}\n\n\t\t\t// If there is no mid and a single encoding, ssrc or rid must be present.\n\t\t\tif (\n\t\t\t  this->rtpParameters.mid.empty() && encodings->size() == 1 && !encoding->ssrc().has_value() &&\n\t\t\t  !flatbuffers::IsFieldPresent(encoding, FBS::RtpParameters::EncodingMapping::VT_RID))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"wrong entry in rtpMapping.encodings (missing ssrc or rid, or rtpParameters.mid)\");\n\t\t\t}\n\n\t\t\t// mappedSsrc is mandatory.\n\t\t\tif (!encoding->mappedSsrc())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"wrong entry in rtpMapping.encodings (missing mappedSsrc)\");\n\t\t\t}\n\n\t\t\tencodingMapping.mappedSsrc = encoding->mappedSsrc();\n\t\t}\n\n\t\tthis->paused = data->paused();\n\n\t\tthis->enableMediasoupPacketIdHeaderExtension = data->enableMediasoupPacketIdHeaderExtension();\n\n\t\t// The number of encodings in rtpParameters must match the number of encodings\n\t\t// in rtpMapping.\n\t\tif (this->rtpParameters.encodings.size() != this->rtpMapping.encodings.size())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"rtpParameters.encodings size does not match rtpMapping.encodings size\");\n\t\t}\n\n\t\t// Fill RTP header extension ids.\n\t\t// This may throw.\n\t\tfor (auto& exten : this->rtpParameters.headerExtensions)\n\t\t{\n\t\t\tif (exten.id == 0u)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"RTP extension id cannot be 0\");\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.mid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.mid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.rid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.rid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.rrid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.rrid = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.absSendTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.absSendTime = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.transportWideCc01 == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.transportWideCc01 = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.ssrcAudioLevel == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.ssrcAudioLevel = exten.id;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t  this->rtpHeaderExtensionIds.dependencyDescriptor == 0u &&\n\t\t\t  exten.type == RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.dependencyDescriptor = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.videoOrientation = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.timeOffset == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.timeOffset = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.absCaptureTime = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.playoutDelay == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.playoutDelay = exten.id;\n\t\t\t}\n\n\t\t\tif (this->rtpHeaderExtensionIds.mediasoupPacketId == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID)\n\t\t\t{\n\t\t\t\tthis->rtpHeaderExtensionIds.mediasoupPacketId = exten.id;\n\t\t\t}\n\t\t}\n\n\t\t// Set the RTCP report generation interval.\n\t\tif (this->kind == RTC::Media::Kind::AUDIO)\n\t\t{\n\t\t\tthis->maxRtcpInterval = RTC::RTCP::MaxAudioIntervalMs;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs;\n\t\t}\n\n\t\t// Create a KeyFrameRequestManager.\n\t\tif (this->kind == RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\tauto keyFrameRequestDelay = data->keyFrameRequestDelay();\n\n\t\t\tthis->keyFrameRequestManager =\n\t\t\t  new RTC::KeyFrameRequestManager(this, this->shared, keyFrameRequestDelay);\n\t\t}\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ this);\n\t}\n\n\tProducer::~Producer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\t// Delete all streams.\n\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t{\n\t\t\tauto* rtpStream = kv.second;\n\n\t\t\tdelete rtpStream;\n\t\t}\n\n\t\tthis->mapSsrcRtpStream.clear();\n\t\tthis->rtpStreamByEncodingIdx.clear();\n\t\tthis->rtpStreamScores.clear();\n\t\tthis->mapRtxSsrcRtpStream.clear();\n\t\tthis->mapRtpStreamMappedSsrc.clear();\n\t\tthis->mapMappedSsrcSsrc.clear();\n\n\t\t// Delete the KeyFrameRequestManager.\n\t\tdelete this->keyFrameRequestManager;\n\t}\n\n\tflatbuffers::Offset<FBS::Producer::DumpResponse> Producer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add rtpParameters.\n\t\tauto rtpParameters = this->rtpParameters.FillBuffer(builder);\n\n\t\t// Add rtpMapping.codecs.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::CodecMapping>> codecs;\n\n\t\tfor (const auto& kv : this->rtpMapping.codecs)\n\t\t{\n\t\t\tcodecs.emplace_back(FBS::RtpParameters::CreateCodecMapping(builder, kv.first, kv.second));\n\t\t}\n\n\t\t// Add rtpMapping.encodings.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::EncodingMapping>> encodings;\n\t\tencodings.reserve(this->rtpMapping.encodings.size());\n\n\t\tfor (const auto& encodingMapping : this->rtpMapping.encodings)\n\t\t{\n\t\t\tencodings.emplace_back(\n\t\t\t  FBS::RtpParameters::CreateEncodingMappingDirect(\n\t\t\t    builder,\n\t\t\t    encodingMapping.rid.c_str(),\n\t\t\t    encodingMapping.ssrc != 0u ? flatbuffers::Optional<uint32_t>(encodingMapping.ssrc)\n\t\t\t                               : flatbuffers::nullopt,\n\t\t\t    nullptr, /* capability mode. NOTE: Present in NODE*/\n\t\t\t    encodingMapping.mappedSsrc));\n\t\t}\n\n\t\t// Build rtpMapping.\n\t\tauto rtpMapping = FBS::RtpParameters::CreateRtpMappingDirect(builder, &codecs, &encodings);\n\n\t\t// Add rtpStreams.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Dump>> rtpStreams;\n\n\t\tfor (const auto* rtpStream : this->rtpStreamByEncodingIdx)\n\t\t{\n\t\t\tif (!rtpStream)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trtpStreams.emplace_back(rtpStream->FillBuffer(builder));\n\t\t}\n\n\t\t// Add traceEventTypes.\n\t\tstd::vector<FBS::Producer::TraceEventType> traceEventTypes;\n\n\t\tif (this->traceEventTypes.rtp)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Producer::TraceEventType::RTP);\n\t\t}\n\t\tif (this->traceEventTypes.keyframe)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Producer::TraceEventType::KEYFRAME);\n\t\t}\n\t\tif (this->traceEventTypes.nack)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Producer::TraceEventType::NACK);\n\t\t}\n\t\tif (this->traceEventTypes.pli)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Producer::TraceEventType::PLI);\n\t\t}\n\t\tif (this->traceEventTypes.fir)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Producer::TraceEventType::FIR);\n\t\t}\n\n\t\treturn FBS::Producer::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  this->kind == RTC::Media::Kind::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO\n\t\t                                        : FBS::RtpParameters::MediaKind::VIDEO,\n\t\t  RTC::RtpParameters::TypeToFbs(this->type),\n\t\t  rtpParameters,\n\t\t  rtpMapping,\n\t\t  &rtpStreams,\n\t\t  &traceEventTypes,\n\t\t  this->paused);\n\t}\n\n\tflatbuffers::Offset<FBS::Producer::GetStatsResponse> Producer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Stats>> rtpStreams;\n\n\t\tfor (auto* rtpStream : this->rtpStreamByEncodingIdx)\n\t\t{\n\t\t\tif (!rtpStream)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\trtpStreams.emplace_back(rtpStream->FillBufferStats(builder));\n\t\t}\n\n\t\treturn FBS::Producer::CreateGetStatsResponseDirect(builder, &rtpStreams);\n\t}\n\n\tvoid Producer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::PRODUCER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Producer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PRODUCER_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Producer_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PRODUCER_PAUSE:\n\t\t\t{\n\t\t\t\tif (this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Pause all streams.\n\t\t\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t\t\t{\n\t\t\t\t\tauto* rtpStream = kv.second;\n\n\t\t\t\t\trtpStream->Pause();\n\t\t\t\t}\n\n\t\t\t\tthis->paused = true;\n\n\t\t\t\tMS_DEBUG_DEV(\"Producer paused [producerId:%s]\", this->id.c_str());\n\n\t\t\t\tthis->listener->OnProducerPaused(this);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PRODUCER_RESUME:\n\t\t\t{\n\t\t\t\tif (!this->paused)\n\t\t\t\t{\n\t\t\t\t\trequest->Accept();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Resume all streams.\n\t\t\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t\t\t{\n\t\t\t\t\tauto* rtpStream = kv.second;\n\n\t\t\t\t\trtpStream->Resume();\n\t\t\t\t}\n\n\t\t\t\tthis->paused = false;\n\n\t\t\t\tMS_DEBUG_DEV(\"Producer resumed [producerId:%s]\", this->id.c_str());\n\n\t\t\t\tthis->listener->OnProducerResumed(this);\n\n\t\t\t\tif (this->keyFrameRequestManager)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtcp, rtx, \"requesting forced key frame(s) after resumed\");\n\n\t\t\t\t\t// Request a key frame for all streams.\n\t\t\t\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto ssrc = kv.first;\n\n\t\t\t\t\t\tthis->keyFrameRequestManager->ForceKeyFrameNeeded(ssrc);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::PRODUCER_ENABLE_TRACE_EVENT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Producer::EnableTraceEventRequest>();\n\n\t\t\t\t// Reset traceEventTypes.\n\t\t\t\tstruct TraceEventTypes newTraceEventTypes;\n\n\t\t\t\tfor (const auto& type : *body->events())\n\t\t\t\t{\n\t\t\t\t\tswitch (type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::KEYFRAME:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.keyframe = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::FIR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.fir = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::NACK:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.nack = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::PLI:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.pli = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::RTP:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.rtp = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcase FBS::Producer::TraceEventType::SR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.sr = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->traceEventTypes = newTraceEventTypes;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Producer::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (notification->event)\n\t\t{\n\t\t\tcase Channel::ChannelNotification::Event::PRODUCER_SEND:\n\t\t\t{\n\t\t\t\tconst auto* body = notification->data->body_as<FBS::Producer::SendNotification>();\n\t\t\t\tauto len         = body->data()->size();\n\n\t\t\t\t// Increase receive transmission.\n\t\t\t\tthis->listener->OnProducerReceiveData(this, len);\n\n\t\t\t\tif (len > ProducerSendBufferSize)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"given RTP packet exceeds maximum size [len:%i]\", len);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Copy the received packet into this buffer so it can be expanded later.\n\t\t\t\tstd::memcpy(ProducerSendBuffer, body->data()->data(), static_cast<size_t>(len));\n\n\t\t\t\tauto* packet = RTC::RTP::Packet::Parse(ProducerSendBuffer, len, ProducerSendBufferSize);\n\n\t\t\t\tif (!packet)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"received data is not a valid RTP packet\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Pass the packet to the parent transport.\n\t\t\t\tthis->listener->OnProducerReceiveRtpPacket(this, packet);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ERROR(\"unknown event '%s'\", notification->eventCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tProducer::ReceiveRtpPacketResult Producer::ReceiveRtpPacket(RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.producerId = this->id;\n#endif\n\n\t\t// Reset current packet.\n\t\tthis->currentRtpPacket = nullptr;\n\n\t\t// Count number of RTP streams.\n\t\tauto numRtpStreamsBefore = this->mapSsrcRtpStream.size();\n\n\t\tauto* rtpStream = GetRtpStream(packet);\n\n\t\tif (!rtpStream)\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"no stream found for received packet [ssrc:%\" PRIu32 \"]\", packet->GetSsrc());\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_STREAM_NOT_FOUND);\n#endif\n\n\t\t\treturn ReceiveRtpPacketResult::DISCARDED;\n\t\t}\n\n\t\tReceiveRtpPacketResult result;\n\t\tbool isRtx{ false };\n\n\t\t// Media packet.\n\t\tif (packet->GetSsrc() == rtpStream->GetSsrc())\n\t\t{\n\t\t\tresult = ReceiveRtpPacketResult::MEDIA;\n\n\t\t\t// Process the packet.\n\t\t\tif (!rtpStream->ReceivePacket(packet))\n\t\t\t{\n\t\t\t\t// May have to announce a new RTP stream to the listener.\n\t\t\t\tif (this->mapSsrcRtpStream.size() > numRtpStreamsBefore)\n\t\t\t\t{\n\t\t\t\t\tNotifyNewRtpStream(rtpStream);\n\t\t\t\t}\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_STREAM_DISCARDED);\n#endif\n\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\t\t// RTX packet.\n\t\telse if (packet->GetSsrc() == rtpStream->GetRtxSsrc())\n\t\t{\n\t\t\tresult = ReceiveRtpPacketResult::RETRANSMISSION;\n\t\t\tisRtx  = true;\n\n\t\t\t// Process the packet.\n\t\t\tif (!rtpStream->ReceiveRtxPacket(packet))\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(\n\t\t\t\t  RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_RTX_STREAM_DISCARDED);\n#endif\n\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\t\t// Should not happen.\n\t\telse\n\t\t{\n\t\t\tMS_ABORT(\"found stream does not match received packet\");\n\t\t}\n\n\t\tif (packet->IsKeyFrame())\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp,\n\t\t\t  \"key frame received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t// Tell the keyFrameRequestManager.\n\t\t\tif (this->keyFrameRequestManager)\n\t\t\t{\n\t\t\t\tthis->keyFrameRequestManager->KeyFrameReceived(packet->GetSsrc());\n\t\t\t}\n\t\t}\n\n\t\t// May have to announce a new RTP stream to the listener.\n\t\tif (this->mapSsrcRtpStream.size() > numRtpStreamsBefore)\n\t\t{\n\t\t\t// Request a key frame for this stream since we may have lost the first packets\n\t\t\t// (do not do it if this is a key frame).\n\t\t\tif (this->keyFrameRequestManager && !this->paused && !packet->IsKeyFrame())\n\t\t\t{\n\t\t\t\tthis->keyFrameRequestManager->ForceKeyFrameNeeded(packet->GetSsrc());\n\t\t\t}\n\n\t\t\t// Update current packet.\n\t\t\tthis->currentRtpPacket = packet;\n\n\t\t\tNotifyNewRtpStream(rtpStream);\n\n\t\t\t// Reset current packet.\n\t\t\tthis->currentRtpPacket = nullptr;\n\t\t}\n\n\t\t// If paused stop here.\n\t\tif (this->paused)\n\t\t{\n\t\t\treturn result;\n\t\t}\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventRtpAndKeyFrameTypes(packet, isRtx);\n\n\t\t// Mangle the packet before providing the listener with it.\n\t\tif (!MangleRtpPacket(packet, rtpStream))\n\t\t{\n\t\t\treturn ReceiveRtpPacketResult::DISCARDED;\n\t\t}\n\n\t\t// Post-process the packet.\n\t\tPostProcessRtpPacket(packet);\n\n\t\tthis->listener->OnProducerRtpPacketReceived(this, packet);\n\n\t\treturn result;\n\t}\n\n\tvoid Producer::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapSsrcRtpStream.find(report->GetSsrc());\n\n\t\tif (it != this->mapSsrcRtpStream.end())\n\t\t{\n\t\t\tauto* rtpStream  = it->second;\n\t\t\tconst bool first = rtpStream->GetSenderReportNtpMs() == 0;\n\n\t\t\trtpStream->ReceiveRtcpSenderReport(report);\n\n\t\t\tthis->listener->OnProducerRtcpSenderReport(this, rtpStream, first);\n\n\t\t\tEmitTraceEventSrType(report);\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If not found, check with RTX.\n\t\tauto it2 = this->mapRtxSsrcRtpStream.find(report->GetSsrc());\n\n\t\tif (it2 != this->mapRtxSsrcRtpStream.end())\n\t\t{\n\t\t\tauto* rtpStream = it2->second;\n\n\t\t\trtpStream->ReceiveRtxRtcpSenderReport(report);\n\n\t\t\treturn;\n\t\t}\n\n\t\tMS_DEBUG_TAG(rtcp, \"RtpStream not found [ssrc:%\" PRIu32 \"]\", report->GetSsrc());\n\t}\n\n\tvoid Producer::ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapSsrcRtpStream.find(ssrcInfo->GetSsrc());\n\n\t\tif (it == this->mapSsrcRtpStream.end())\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"RtpStream not found [ssrc:%\" PRIu32 \"]\", ssrcInfo->GetSsrc());\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* rtpStream = it->second;\n\n\t\trtpStream->ReceiveRtcpXrDelaySinceLastRr(ssrcInfo);\n\t}\n\n\tbool Producer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (static_cast<float>((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tstd::vector<RTCP::ReceiverReport*> receiverReports;\n\t\tRTCP::ReceiverReferenceTime* receiverReferenceTimeReport{ nullptr };\n\n\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t{\n\t\t\tauto* rtpStream = kv.second;\n\t\t\tauto* report    = rtpStream->GetRtcpReceiverReport();\n\n\t\t\treceiverReports.push_back(report);\n\n\t\t\tauto* rtxReport = rtpStream->GetRtxRtcpReceiverReport();\n\n\t\t\tif (rtxReport)\n\t\t\t{\n\t\t\t\treceiverReports.push_back(rtxReport);\n\t\t\t}\n\t\t}\n\n\t\t// Add a receiver reference time report if no present in the packet.\n\t\tif (!packet->HasReceiverReferenceTime())\n\t\t{\n\t\t\tauto ntp                    = Utils::Time::TimeMs2Ntp(nowMs);\n\t\t\treceiverReferenceTimeReport = new RTC::RTCP::ReceiverReferenceTime();\n\n\t\t\treceiverReferenceTimeReport->SetNtpSec(ntp.seconds);\n\t\t\treceiverReferenceTimeReport->SetNtpFrac(ntp.fractions);\n\t\t}\n\n\t\t// RTCP Compound packet buffer cannot hold the data.\n\t\tif (!packet->Add(receiverReports, receiverReferenceTimeReport))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->lastRtcpSentTime = nowMs;\n\n\t\treturn true;\n\t}\n\n\tvoid Producer::RequestKeyFrame(uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->keyFrameRequestManager || this->paused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto it = this->mapMappedSsrcSsrc.find(mappedSsrc);\n\n\t\tif (it == this->mapMappedSsrcSsrc.end())\n\t\t{\n\t\t\tMS_WARN_2TAGS(rtcp, rtx, \"given mappedSsrc not found, ignoring\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint32_t ssrc = it->second;\n\n\t\t// If the current RTP packet is a key frame for the given mapped SSRC do\n\t\t// nothing since we are gonna provide Consumers with the requested key frame\n\t\t// right now.\n\t\t//\n\t\t// NOTE: We know that this may only happen before calling MangleRtpPacket()\n\t\t// so the SSRC of the packet is still the original one and not the mapped one.\n\t\tif (\n\t\t  this->currentRtpPacket && this->currentRtpPacket->GetSsrc() == ssrc &&\n\t\t  this->currentRtpPacket->IsKeyFrame())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->keyFrameRequestManager->KeyFrameNeeded(ssrc);\n\t}\n\n\tRTC::RTP::RtpStreamRecv* Producer::GetRtpStream(const RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint32_t ssrc       = packet->GetSsrc();\n\t\tconst uint8_t payloadType = packet->GetPayloadType();\n\n\t\t// If stream found in media ssrcs map, return it.\n\t\t{\n\t\t\tauto it = this->mapSsrcRtpStream.find(ssrc);\n\n\t\t\tif (it != this->mapSsrcRtpStream.end())\n\t\t\t{\n\t\t\t\tauto* rtpStream = it->second;\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t}\n\n\t\t// If stream found in RTX ssrcs map, return it.\n\t\t{\n\t\t\tauto it = this->mapRtxSsrcRtpStream.find(ssrc);\n\n\t\t\tif (it != this->mapRtxSsrcRtpStream.end())\n\t\t\t{\n\t\t\t\tauto* rtpStream = it->second;\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t}\n\n\t\t// Otherwise check our encodings and, if appropriate, create a new stream.\n\n\t\t// First, look for an encoding with matching media or RTX ssrc value.\n\t\tfor (size_t i{ 0 }; i < this->rtpParameters.encodings.size(); ++i)\n\t\t{\n\t\t\tauto& encoding           = this->rtpParameters.encodings[i];\n\t\t\tconst auto* mediaCodec   = this->rtpParameters.GetCodecForEncoding(encoding);\n\t\t\tconst auto* rtxCodec     = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\t\t\tconst bool isMediaPacket = (mediaCodec->payloadType == payloadType);\n\t\t\tconst bool isRtxPacket   = (rtxCodec && rtxCodec->payloadType == payloadType);\n\n\t\t\tif (isMediaPacket && encoding.ssrc == ssrc)\n\t\t\t{\n\t\t\t\tauto* rtpStream = CreateRtpStream(packet, *mediaCodec, i);\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t\telse if (isRtxPacket && encoding.hasRtx && encoding.rtx.ssrc == ssrc)\n\t\t\t{\n\t\t\t\tauto it = this->mapSsrcRtpStream.find(encoding.ssrc);\n\n\t\t\t\t// Ignore if no stream has been created yet for the corresponding encoding.\n\t\t\t\tif (it == this->mapSsrcRtpStream.end())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtx, \"ignoring RTX packet for not yet created RtpStream (ssrc lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tauto* rtpStream = it->second;\n\n\t\t\t\t// Ensure no RTX ssrc was previously detected.\n\t\t\t\tif (rtpStream->HasRtx())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtx, \"ignoring RTX packet with new ssrc (ssrc lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\t// Update the stream RTX data.\n\t\t\t\trtpStream->SetRtx(payloadType, ssrc);\n\n\t\t\t\t// Insert the new RTX ssrc into the map.\n\t\t\t\tthis->mapRtxSsrcRtpStream[ssrc] = rtpStream;\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t}\n\n\t\t// If not found, look for an encoding matching the packet RID value.\n\t\tstd::string rid;\n\n\t\tif (packet->ReadRid(rid))\n\t\t{\n\t\t\tfor (size_t i{ 0 }; i < this->rtpParameters.encodings.size(); ++i)\n\t\t\t{\n\t\t\t\tauto& encoding = this->rtpParameters.encodings[i];\n\n\t\t\t\tif (encoding.rid != rid)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst auto* mediaCodec   = this->rtpParameters.GetCodecForEncoding(encoding);\n\t\t\t\tconst auto* rtxCodec     = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\t\t\t\tconst bool isMediaPacket = (mediaCodec->payloadType == payloadType);\n\t\t\t\tconst bool isRtxPacket   = (rtxCodec && rtxCodec->payloadType == payloadType);\n\n\t\t\t\tif (isMediaPacket)\n\t\t\t\t{\n\t\t\t\t\t// Ensure no other stream already exists with same RID.\n\t\t\t\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* rtpStream = kv.second;\n\n\t\t\t\t\t\tif (rtpStream->GetRid() == rid)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  rtp, \"ignoring packet with unknown ssrc but already handled RID (RID lookup)\");\n\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tauto* rtpStream = CreateRtpStream(packet, *mediaCodec, i);\n\n\t\t\t\t\treturn rtpStream;\n\t\t\t\t}\n\t\t\t\telse if (isRtxPacket)\n\t\t\t\t{\n\t\t\t\t\t// Ensure a stream already exists with same RID.\n\t\t\t\t\tfor (auto& kv : this->mapSsrcRtpStream)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* rtpStream = kv.second;\n\n\t\t\t\t\t\tif (rtpStream->GetRid() == rid)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Ensure no RTX ssrc was previously detected.\n\t\t\t\t\t\t\tif (rtpStream->HasRtx())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtx, \"ignoring RTX packet with new SSRC (RID lookup)\");\n\n\t\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Update the stream RTX data.\n\t\t\t\t\t\t\trtpStream->SetRtx(payloadType, ssrc);\n\n\t\t\t\t\t\t\t// Insert the new RTX ssrc into the map.\n\t\t\t\t\t\t\tthis->mapRtxSsrcRtpStream[ssrc] = rtpStream;\n\n\t\t\t\t\t\t\treturn rtpStream;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtx, \"ignoring RTX packet for not yet created RtpStream (RID lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMS_WARN_TAG(rtp, \"ignoring packet with unknown RID (RID lookup)\");\n\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t// If not found, and there is a single encoding without ssrc and RID, this\n\t\t// may be the media or RTX stream.\n\t\tif (\n\t\t  this->rtpParameters.encodings.size() == 1 && !this->rtpParameters.encodings[0].ssrc &&\n\t\t  this->rtpParameters.encodings[0].rid.empty())\n\t\t{\n\t\t\tauto& encoding           = this->rtpParameters.encodings[0];\n\t\t\tconst auto* mediaCodec   = this->rtpParameters.GetCodecForEncoding(encoding);\n\t\t\tconst auto* rtxCodec     = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\t\t\tconst bool isMediaPacket = (mediaCodec->payloadType == payloadType);\n\t\t\tconst bool isRtxPacket   = (rtxCodec && rtxCodec->payloadType == payloadType);\n\n\t\t\tif (isMediaPacket)\n\t\t\t{\n\t\t\t\t// Ensure there is no other RTP stream already.\n\t\t\t\tif (!this->mapSsrcRtpStream.empty())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"ignoring packet with unknown ssrc not matching the already existing stream (single RtpStream lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tauto* rtpStream = CreateRtpStream(packet, *mediaCodec, 0);\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t\telse if (isRtxPacket)\n\t\t\t{\n\t\t\t\t// There must be already a media RTP stream.\n\t\t\t\tauto it = this->mapSsrcRtpStream.begin();\n\n\t\t\t\tif (it == this->mapSsrcRtpStream.end())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(\n\t\t\t\t\t  rtp, rtx, \"ignoring RTX packet for not yet created RtpStream (single stream lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tauto* rtpStream = it->second;\n\n\t\t\t\t// Ensure no RTX SSRC was previously detected.\n\t\t\t\tif (rtpStream->HasRtx())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_2TAGS(rtp, rtx, \"ignoring RTX packet with new SSRC (single stream lookup)\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\t// Update the stream RTX data.\n\t\t\t\trtpStream->SetRtx(payloadType, ssrc);\n\n\t\t\t\t// Insert the new RTX SSRC into the map.\n\t\t\t\tthis->mapRtxSsrcRtpStream[ssrc] = rtpStream;\n\n\t\t\t\treturn rtpStream;\n\t\t\t}\n\t\t}\n\n\t\treturn nullptr;\n\t}\n\n\tRTC::RTP::RtpStreamRecv* Producer::CreateRtpStream(\n\t  const RTC::RTP::Packet* packet, const RTC::RtpCodecParameters& mediaCodec, size_t encodingIdx)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint32_t ssrc = packet->GetSsrc();\n\n\t\tMS_ASSERT(\n\t\t  this->mapSsrcRtpStream.find(ssrc) == this->mapSsrcRtpStream.end(),\n\t\t  \"RtpStream with given SSRC already exists\");\n\t\tMS_ASSERT(\n\t\t  !this->rtpStreamByEncodingIdx[encodingIdx],\n\t\t  \"RtpStream for given encoding index already exists\");\n\n\t\tauto& encoding        = this->rtpParameters.encodings[encodingIdx];\n\t\tauto& encodingMapping = this->rtpMapping.encodings[encodingIdx];\n\n\t\tMS_DEBUG_TAG(\n\t\t  rtp,\n\t\t  \"[encodingIdx:%zu, ssrc:%\" PRIu32 \", rid:%s, payloadType:%\" PRIu8 \"]\",\n\t\t  encodingIdx,\n\t\t  ssrc,\n\t\t  encoding.rid.c_str(),\n\t\t  mediaCodec.payloadType);\n\n\t\t// Set stream params.\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.encodingIdx    = encodingIdx;\n\t\tparams.ssrc           = ssrc;\n\t\tparams.payloadType    = mediaCodec.payloadType;\n\t\tparams.mimeType       = mediaCodec.mimeType;\n\t\tparams.clockRate      = mediaCodec.clockRate;\n\t\tparams.rid            = encoding.rid;\n\t\tparams.cname          = this->rtpParameters.rtcp.cname;\n\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\tparams.temporalLayers = encoding.temporalLayers;\n\n\t\t// Check in band FEC in codec parameters.\n\t\tif (mediaCodec.parameters.HasInteger(\"useinbandfec\") && mediaCodec.parameters.GetInteger(\"useinbandfec\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtcp, \"in band FEC enabled\");\n\n\t\t\tparams.useInBandFec = true;\n\t\t}\n\n\t\t// Check DTX in codec parameters.\n\t\tif (mediaCodec.parameters.HasInteger(\"usedtx\") && mediaCodec.parameters.GetInteger(\"usedtx\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\t// Check DTX in the encoding.\n\t\tif (encoding.dtx)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\tfor (const auto& fb : mediaCodec.rtcpFeedback)\n\t\t{\n\t\t\tif (!params.useNack && fb.type == \"nack\" && fb.parameter.empty())\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"NACK supported\");\n\n\t\t\t\tparams.useNack = true;\n\t\t\t}\n\t\t\telse if (!params.usePli && fb.type == \"nack\" && fb.parameter == \"pli\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"PLI supported\");\n\n\t\t\t\tparams.usePli = true;\n\t\t\t}\n\t\t\telse if (!params.useFir && fb.type == \"ccm\" && fb.parameter == \"fir\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"FIR supported\");\n\n\t\t\t\tparams.useFir = true;\n\t\t\t}\n\t\t}\n\n\t\t// Only perform RTP inactivity check on simulcast and only if there are\n\t\t// more than 1 stream.\n\t\tauto useRtpInactivityCheck =\n\t\t  this->type == RtpParameters::Type::SIMULCAST && this->rtpMapping.encodings.size() > 1;\n\n\t\t// Create a RtpStreamRecv for receiving a media stream.\n\t\tauto* rtpStream =\n\t\t  new RTC::RTP::RtpStreamRecv(this, this->shared, params, SendNackDelay, useRtpInactivityCheck);\n\n\t\t// Insert into the maps.\n\t\tthis->mapSsrcRtpStream[ssrc]              = rtpStream;\n\t\tthis->rtpStreamByEncodingIdx[encodingIdx] = rtpStream;\n\t\tthis->rtpStreamScores[encodingIdx]        = rtpStream->GetScore();\n\n\t\t// Set the mapped SSRC.\n\t\tthis->mapRtpStreamMappedSsrc[rtpStream]             = encodingMapping.mappedSsrc;\n\t\tthis->mapMappedSsrcSsrc[encodingMapping.mappedSsrc] = ssrc;\n\n\t\t// If the Producer is paused tell it to the new RtpStreamRecv.\n\t\tif (this->paused)\n\t\t{\n\t\t\trtpStream->Pause();\n\t\t}\n\n\t\t// Emit the first score event right now.\n\t\tEmitScore();\n\n\t\treturn rtpStream;\n\t}\n\n\tvoid Producer::NotifyNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream);\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnProducerNewRtpStream(this, rtpStream, mappedSsrc);\n\t}\n\n\tinline bool Producer::MangleRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::RtpStreamRecv* rtpStream) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Mangle the payload type.\n\t\t{\n\t\t\tconst uint8_t payloadType = packet->GetPayloadType();\n\t\t\tauto it                   = this->rtpMapping.codecs.find(payloadType);\n\n\t\t\tif (it == this->rtpMapping.codecs.end())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"unknown payload type [payloadType:%\" PRIu8 \"]\", payloadType);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst uint8_t mappedPayloadType = it->second;\n\n\t\t\tpacket->SetPayloadType(mappedPayloadType);\n\t\t}\n\n\t\t// Mangle the SSRC.\n\t\t{\n\t\t\tconst uint32_t mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream);\n\n\t\t\tpacket->SetSsrc(mappedSsrc);\n\t\t}\n\n\t\t// Mangle RTP header extensions.\n\t\t{\n\t\t\tstatic thread_local uint8_t buffer[4096];\n\t\t\tstatic thread_local std::vector<RTC::RTP::Packet::Extension> extensions;\n\n\t\t\t// This happens just once.\n\t\t\tif (extensions.capacity() != 24)\n\t\t\t{\n\t\t\t\textensions.reserve(24);\n\t\t\t}\n\n\t\t\textensions.clear();\n\n\t\t\tuint8_t* extenValue;\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* bufferPtr{ buffer };\n\n\t\t\t// Add urn:ietf:params:rtp-hdrext:sdes:mid.\n\t\t\t{\n\t\t\t\textenLen = RTC::Consts::MidRtpExtensionMaxLength;\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::MID,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MID),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\tbufferPtr += extenLen;\n\t\t\t}\n\n\t\t\t// Proxy http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time.\n\t\t\textenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.absCaptureTime, extenLen);\n\n\t\t\tif (extenValue)\n\t\t\t{\n\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\tbufferPtr += extenLen;\n\t\t\t}\n\n\t\t\t// Proxy http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\n\t\t\textenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.playoutDelay, extenLen);\n\n\t\t\tif (extenValue)\n\t\t\t{\n\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\tbufferPtr += extenLen;\n\t\t\t}\n\n\t\t\tif (this->kind == RTC::Media::Kind::AUDIO)\n\t\t\t{\n\t\t\t\t// Proxy urn:ietf:params:rtp-hdrext:ssrc-audio-level.\n\t\t\t\textenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.ssrcAudioLevel, extenLen);\n\n\t\t\t\tif (extenValue)\n\t\t\t\t{\n\t\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (this->kind == RTC::Media::Kind::VIDEO)\n\t\t\t{\n\t\t\t\t// Add http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time.\n\t\t\t\t// NOTE: This is for REMB.\n\t\t\t\t{\n\t\t\t\t\textenLen = 3u;\n\n\t\t\t\t\t// NOTE: Add value 0. The sending Transport will update it.\n\t\t\t\t\tconst uint32_t absSendTime{ 0u };\n\n\t\t\t\t\tUtils::Byte::Set3Bytes(bufferPtr, 0, absSendTime);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\n\t\t\t\t// Add http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01.\n\t\t\t\t// NOTE: We don't include it in outbound audio packets for now.\n\t\t\t\t{\n\t\t\t\t\textenLen = 2u;\n\n\t\t\t\t\t// NOTE: Add value 0. The sending Transport will update it.\n\t\t\t\t\tconst uint16_t wideSeqNumber{ 0u };\n\n\t\t\t\t\tUtils::Byte::Set2Bytes(bufferPtr, 0, wideSeqNumber);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\n\t\t\t\t// Proxy https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension.\n\t\t\t\textenValue =\n\t\t\t\t  packet->GetExtensionValue(this->rtpHeaderExtensionIds.dependencyDescriptor, extenLen);\n\n\t\t\t\tif (extenValue)\n\t\t\t\t{\n\t\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\n\t\t\t\t// Proxy urn:3gpp:video-orientation.\n\t\t\t\textenValue =\n\t\t\t\t  packet->GetExtensionValue(this->rtpHeaderExtensionIds.videoOrientation, extenLen);\n\n\t\t\t\tif (extenValue)\n\t\t\t\t{\n\t\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\n\t\t\t\t// Proxy urn:ietf:params:rtp-hdrext:toffset.\n\t\t\t\textenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.timeOffset, extenLen);\n\n\t\t\t\tif (extenValue)\n\t\t\t\t{\n\t\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\tbufferPtr += extenLen;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add urn:mediasoup:params:rtp-hdrext:packet-id.\n\t\t\t//\n\t\t\t// Here if may happen that the packet ALREADY contains the header (if it comes\n\t\t\t// from another mediasoup Router in which it was added). If so, honor it.\n\t\t\t// Otherwise, if the flag `enableMediasoupPacketIdHeaderExtension` is set,\n\t\t\t// add it.\n\t\t\t{\n\t\t\t\textenValue =\n\t\t\t\t  packet->GetExtensionValue(this->rtpHeaderExtensionIds.mediasoupPacketId, extenLen);\n\n\t\t\t\tif (extenValue)\n\t\t\t\t{\n\t\t\t\t\tstd::memcpy(bufferPtr, extenValue, extenLen);\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\t// Not needed since this is the latest added extension.\n\t\t\t\t\t// bufferPtr += extenLen;\n\t\t\t\t}\n\t\t\t\telse if (this->enableMediasoupPacketIdHeaderExtension)\n\t\t\t\t{\n\t\t\t\t\textenLen = 4;\n\n\t\t\t\t\tUtils::Byte::Set4Bytes(bufferPtr, 0, RTC::RTP::Packet::GetNextMediasoupPacketId());\n\n\t\t\t\t\textensions.emplace_back(\n\t\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID,\n\t\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID),\n\t\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t\t// Not needed since this is the latest added extension.\n\t\t\t\t\t// bufferPtr += extenLen;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tinline void Producer::PostProcessRtpPacket(RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind == RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\tbool camera{ false };\n\t\t\tbool flip{ false };\n\t\t\tuint16_t rotation{ 0 };\n\n\t\t\tif (packet->ReadVideoOrientation(camera, flip, rotation))\n\t\t\t{\n\t\t\t\t// If video orientation was not yet detected or any value has changed,\n\t\t\t\t// emit event.\n\t\t\t\tif (\n\t\t\t\t  !this->videoOrientationDetected || camera != this->videoOrientation.camera ||\n\t\t\t\t  flip != this->videoOrientation.flip || rotation != this->videoOrientation.rotation)\n\t\t\t\t{\n\t\t\t\t\tthis->videoOrientationDetected  = true;\n\t\t\t\t\tthis->videoOrientation.camera   = camera;\n\t\t\t\t\tthis->videoOrientation.flip     = flip;\n\t\t\t\t\tthis->videoOrientation.rotation = rotation;\n\n\t\t\t\t\tauto notification = FBS::Producer::CreateVideoOrientationChangeNotification(\n\t\t\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t\t\t  this->videoOrientation.camera,\n\t\t\t\t\t  this->videoOrientation.flip,\n\t\t\t\t\t  this->videoOrientation.rotation);\n\n\t\t\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t\t\t  this->id,\n\t\t\t\t\t  FBS::Notification::Event::PRODUCER_VIDEO_ORIENTATION_CHANGE,\n\t\t\t\t\t  FBS::Notification::Body::Producer_VideoOrientationChangeNotification,\n\t\t\t\t\t  notification);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tinline void Producer::EmitScore() const\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::Producer::Score>> scores;\n\n\t\tfor (const auto* rtpStream : this->rtpStreamByEncodingIdx)\n\t\t{\n\t\t\tif (!rtpStream)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tscores.emplace_back(\n\t\t\t  FBS::Producer::CreateScoreDirect(\n\t\t\t    this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t    rtpStream->GetEncodingIdx(),\n\t\t\t    rtpStream->GetSsrc(),\n\t\t\t    !rtpStream->GetRid().empty() ? rtpStream->GetRid().c_str() : nullptr,\n\t\t\t    rtpStream->GetScore()));\n\t\t}\n\n\t\tauto notification = FBS::Producer::CreateScoreNotificationDirect(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), &scores);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::PRODUCER_SCORE,\n\t\t  FBS::Notification::Body::Producer_ScoreNotification,\n\t\t  notification);\n\t}\n\n\tinline void Producer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->traceEventTypes.keyframe && packet->IsKeyFrame())\n\t\t{\n\t\t\tauto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\t\tauto traceInfo = FBS::Producer::CreateKeyFrameTraceInfo(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx);\n\n\t\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  FBS::Producer::TraceEventType::KEYFRAME,\n\t\t\t  this->shared->GetTimeMs(),\n\t\t\t  FBS::Common::TraceDirection::DIRECTION_IN,\n\t\t\t  FBS::Producer::TraceInfo::KeyFrameTraceInfo,\n\t\t\t  traceInfo.Union());\n\n\t\t\tEmitTraceEvent(notification);\n\t\t}\n\t\telse if (this->traceEventTypes.rtp)\n\t\t{\n\t\t\tauto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\t\t\tauto traceInfo = FBS::Producer::CreateRtpTraceInfo(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx);\n\n\t\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  FBS::Producer::TraceEventType::RTP,\n\t\t\t  this->shared->GetTimeMs(),\n\t\t\t  FBS::Common::TraceDirection::DIRECTION_IN,\n\t\t\t  FBS::Producer::TraceInfo::RtpTraceInfo,\n\t\t\t  traceInfo.Union());\n\n\t\t\tEmitTraceEvent(notification);\n\t\t}\n\t}\n\n\tinline void Producer::EmitTraceEventPliType(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.pli)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Producer::CreatePliTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc);\n\n\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Producer::TraceEventType::PLI,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_OUT,\n\t\t  FBS::Producer::TraceInfo::PliTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tinline void Producer::EmitTraceEventFirType(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.fir)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Producer::CreateFirTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc);\n\n\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Producer::TraceEventType::FIR,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_OUT,\n\t\t  FBS::Producer::TraceInfo::FirTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tinline void Producer::EmitTraceEventNackType() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.nack)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Producer::TraceEventType::NACK,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_OUT);\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tinline void Producer::EmitTraceEventSrType(RTC::RTCP::SenderReport* report) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.sr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Producer::CreateSrTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  report->GetSsrc(),\n\t\t  report->GetNtpSec(),\n\t\t  report->GetNtpFrac(),\n\t\t  report->GetRtpTs(),\n\t\t  report->GetPacketCount(),\n\t\t  report->GetOctetCount());\n\n\t\tauto notification = FBS::Producer::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Producer::TraceEventType::SR,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_IN,\n\t\t  FBS::Producer::TraceInfo::SrTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tEmitTraceEvent(notification);\n\t}\n\n\tinline void Producer::EmitTraceEvent(\n\t  flatbuffers::Offset<FBS::Producer::TraceNotification>& notification) const\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::PRODUCER_TRACE,\n\t\t  FBS::Notification::Body::Producer_TraceNotification,\n\t\t  notification);\n\t}\n\n\tinline void Producer::OnRtpStreamScore(\n\t  RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Update the vector of scores.\n\t\tthis->rtpStreamScores[rtpStream->GetEncodingIdx()] = score;\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnProducerRtpStreamScore(\n\t\t  this, static_cast<RTC::RTP::RtpStreamRecv*>(rtpStream), score, previousScore);\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\t}\n\n\tinline void Producer::OnRtpStreamSendRtcpPacket(\n\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* packet)\n\t{\n\t\tswitch (packet->GetType())\n\t\t{\n\t\t\tcase RTC::RTCP::Type::PSFB:\n\t\t\t{\n\t\t\t\tauto* feedback = static_cast<RTC::RTCP::FeedbackPsPacket*>(packet);\n\n\t\t\t\tswitch (feedback->GetMessageType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t\t\t{\n\t\t\t\t\t\t// May emit 'trace' event.\n\t\t\t\t\t\tEmitTraceEventPliType(feedback->GetMediaSsrc());\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t\t\t{\n\t\t\t\t\t\t// May emit 'trace' event.\n\t\t\t\t\t\tEmitTraceEventFirType(feedback->GetMediaSsrc());\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::RTPFB:\n\t\t\t{\n\t\t\t\tauto* feedback = static_cast<RTC::RTCP::FeedbackRtpPacket*>(packet);\n\n\t\t\t\tswitch (feedback->GetMessageType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RTCP::FeedbackRtp::MessageType::NACK:\n\t\t\t\t\t{\n\t\t\t\t\t\t// May emit 'trace' event.\n\t\t\t\t\t\tEmitTraceEventNackType();\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnProducerSendRtcpPacket(this, packet);\n\t}\n\n\tinline void Producer::OnRtpStreamNeedWorstRemoteFractionLost(\n\t  RTC::RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tauto mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream);\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnProducerNeedWorstRemoteFractionLost(this, mappedSsrc, worstRemoteFractionLost);\n\t}\n\n\tinline void Producer::OnKeyFrameNeeded(\n\t  RTC::KeyFrameRequestManager* /*keyFrameRequestManager*/, uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapSsrcRtpStream.find(ssrc);\n\n\t\tif (it == this->mapSsrcRtpStream.end())\n\t\t{\n\t\t\tMS_WARN_2TAGS(rtcp, rtx, \"no associated RtpStream found [ssrc:%\" PRIu32 \"]\", ssrc);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* rtpStream = it->second;\n\n\t\trtpStream->RequestKeyFrame();\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/Bye.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::Bye\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/Bye.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tByePacket* ByePacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\t\t\tstd::unique_ptr<ByePacket> packet(new ByePacket(header));\n\t\t\tsize_t offset = Packet::CommonHeaderSize;\n\t\t\tuint8_t count = header->count;\n\n\t\t\twhile (((count--) != 0u) && (len > offset))\n\t\t\t{\n\t\t\t\tif (len - offset < 4u)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for SSRC in RTCP Bye message\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tpacket->AddSsrc(Utils::Byte::Get4Bytes(data, offset));\n\t\t\t\toffset += 4u;\n\t\t\t}\n\n\t\t\tif (len > offset)\n\t\t\t{\n\t\t\t\tauto length = size_t{ Utils::Byte::Get1Byte(data, offset) };\n\n\t\t\t\toffset += 1u;\n\n\t\t\t\tif (length <= len - offset)\n\t\t\t\t{\n\t\t\t\t\tpacket->SetReason(std::string(reinterpret_cast<const char*>(data) + offset, length));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t ByePacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset = Packet::Serialize(buffer);\n\n\t\t\t// SSRCs.\n\t\t\tfor (auto ssrc : this->ssrcs)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set4Bytes(buffer, offset, ssrc);\n\t\t\t\toffset += 4u;\n\t\t\t}\n\n\t\t\tif (!this->reason.empty())\n\t\t\t{\n\t\t\t\t// Length field.\n\t\t\t\tUtils::Byte::Set1Byte(buffer, offset, this->reason.length());\n\t\t\t\toffset += 1u;\n\n\t\t\t\t// Reason field.\n\t\t\t\tstd::memcpy(buffer + offset, this->reason.c_str(), this->reason.length());\n\t\t\t\toffset += this->reason.length();\n\t\t\t}\n\n\t\t\t// 32 bits padding.\n\t\t\tconst size_t padding = (-offset) & 3;\n\n\t\t\tfor (size_t i{ 0 }; i < padding; ++i)\n\t\t\t{\n\t\t\t\tbuffer[offset + i] = 0;\n\t\t\t}\n\n\t\t\treturn offset + padding;\n\t\t}\n\n\t\tvoid ByePacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ByePacket>\");\n\t\t\tfor (auto ssrc : this->ssrcs)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, ssrc);\n\t\t\t}\n\t\t\tif (!this->reason.empty())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  reason: %s\", this->reason.c_str());\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</ByePacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/CompoundPacket.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::CompoundPacket\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/CompoundPacket.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/Consts.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\n\t\tsize_t CompoundPacket::GetSize()\n\t\t{\n\t\t\tsize_t size{ 0 };\n\n\t\t\tif (this->senderReportPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\tsize += this->senderReportPacket.GetSize();\n\t\t\t}\n\n\t\t\tif (this->receiverReportPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\tsize += this->receiverReportPacket.GetSize();\n\t\t\t}\n\n\t\t\tif (this->sdesPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\tsize += this->sdesPacket.GetSize();\n\t\t\t}\n\n\t\t\tif (this->xrPacket.Begin() != this->xrPacket.End())\n\t\t\t{\n\t\t\t\tsize += this->xrPacket.GetSize();\n\t\t\t}\n\n\t\t\treturn size;\n\t\t}\n\n\t\tvoid CompoundPacket::Serialize(uint8_t* data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->header = data;\n\n\t\t\t// Fill it.\n\t\t\tsize_t offset{ 0 };\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->senderReportPacket.GetCount() > 0u || this->receiverReportPacket.GetCount() > 0u,\n\t\t\t  \"no Sender or Receiver report present\");\n\n\t\t\tif (this->senderReportPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\toffset += this->senderReportPacket.Serialize(this->header);\n\t\t\t}\n\n\t\t\tif (this->receiverReportPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\toffset += this->receiverReportPacket.Serialize(this->header + offset);\n\t\t\t}\n\n\t\t\tif (this->sdesPacket.GetCount() > 0u)\n\t\t\t{\n\t\t\t\toffset += this->sdesPacket.Serialize(this->header + offset);\n\t\t\t}\n\n\t\t\tif (this->xrPacket.Begin() != this->xrPacket.End())\n\t\t\t{\n\t\t\t\tthis->xrPacket.Serialize(this->header + offset);\n\t\t\t}\n\t\t}\n\n\t\tbool CompoundPacket::Add(\n\t\t  SenderReport* senderReport,\n\t\t  SdesChunk* sdesChunk,\n\t\t  DelaySinceLastRr::SsrcInfo* delaySinceLastRrSsrcInfo)\n\t\t{\n\t\t\t// Add the items into the packet.\n\n\t\t\tif (senderReport)\n\t\t\t{\n\t\t\t\tthis->senderReportPacket.AddReport(senderReport);\n\t\t\t}\n\n\t\t\tif (sdesChunk)\n\t\t\t{\n\t\t\t\tthis->sdesPacket.AddChunk(sdesChunk);\n\t\t\t}\n\n\t\t\tif (delaySinceLastRrSsrcInfo)\n\t\t\t{\n\t\t\t\t// Add a DLRR block into the XR packet if no present.\n\t\t\t\tif (!this->delaySinceLastRr)\n\t\t\t\t{\n\t\t\t\t\tthis->delaySinceLastRr = new RTC::RTCP::DelaySinceLastRr();\n\t\t\t\t\tthis->xrPacket.AddReport(this->delaySinceLastRr);\n\t\t\t\t}\n\n\t\t\t\tthis->delaySinceLastRr->AddSsrcInfo(delaySinceLastRrSsrcInfo);\n\t\t\t}\n\n\t\t\t// New items can hold in the packet, report it.\n\t\t\tif (GetSize() <= RTC::Consts::RtcpPacketMaxSize)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// New items can not hold in the packet, remove them,\n\t\t\t// delete and report it.\n\n\t\t\tif (senderReport)\n\t\t\t{\n\t\t\t\tthis->senderReportPacket.RemoveReport(senderReport);\n\t\t\t\tdelete senderReport;\n\t\t\t}\n\n\t\t\tif (sdesChunk)\n\t\t\t{\n\t\t\t\tthis->sdesPacket.RemoveChunk(sdesChunk);\n\t\t\t\tdelete sdesChunk;\n\t\t\t}\n\n\t\t\tif (delaySinceLastRrSsrcInfo)\n\t\t\t{\n\t\t\t\t// NOTE: This method deletes the removed instances in place.\n\t\t\t\tthis->delaySinceLastRr->RemoveLastSsrcInfos(1);\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tbool CompoundPacket::Add(\n\t\t  std::vector<SenderReport*>& senderReports,\n\t\t  std::vector<SdesChunk*>& sdesChunks,\n\t\t  std::vector<DelaySinceLastRr::SsrcInfo*>& delaySinceLastRrSsrcInfos)\n\t\t{\n\t\t\t// Add the items into the packet.\n\n\t\t\tfor (auto* report : senderReports)\n\t\t\t{\n\t\t\t\tthis->senderReportPacket.AddReport(report);\n\t\t\t}\n\n\t\t\tfor (auto* chunk : sdesChunks)\n\t\t\t{\n\t\t\t\tthis->sdesPacket.AddChunk(chunk);\n\t\t\t}\n\n\t\t\t// Add a DLRR block into the XR packet if no present.\n\t\t\tif (!delaySinceLastRrSsrcInfos.empty() && !this->delaySinceLastRr)\n\t\t\t{\n\t\t\t\tthis->delaySinceLastRr = new RTC::RTCP::DelaySinceLastRr();\n\t\t\t\tthis->xrPacket.AddReport(this->delaySinceLastRr);\n\t\t\t}\n\n\t\t\tfor (auto* ssrcInfo : delaySinceLastRrSsrcInfos)\n\t\t\t{\n\t\t\t\tthis->delaySinceLastRr->AddSsrcInfo(ssrcInfo);\n\t\t\t}\n\n\t\t\t// New items can hold in the packet, report it.\n\t\t\tif (GetSize() <= RTC::Consts::RtcpPacketMaxSize)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// New items can not hold in the packet, remove them,\n\t\t\t// delete and report it.\n\n\t\t\tfor (auto* report : senderReports)\n\t\t\t{\n\t\t\t\tthis->senderReportPacket.RemoveReport(report);\n\t\t\t\tdelete report;\n\t\t\t}\n\n\t\t\tfor (auto* chunk : sdesChunks)\n\t\t\t{\n\t\t\t\tthis->sdesPacket.RemoveChunk(chunk);\n\t\t\t\tdelete chunk;\n\t\t\t}\n\n\t\t\tif (!delaySinceLastRrSsrcInfos.empty())\n\t\t\t{\n\t\t\t\t// NOTE: This method deletes the instances in place.\n\t\t\t\tthis->delaySinceLastRr->RemoveLastSsrcInfos(delaySinceLastRrSsrcInfos.size());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tbool CompoundPacket::Add(\n\t\t  std::vector<ReceiverReport*>& receiverReports,\n\t\t  ReceiverReferenceTime* receiverReferenceTimeReport)\n\t\t{\n\t\t\t// Add the items into the packet.\n\n\t\t\tfor (auto* report : receiverReports)\n\t\t\t{\n\t\t\t\tthis->receiverReportPacket.AddReport(report);\n\t\t\t}\n\n\t\t\tif (receiverReferenceTimeReport)\n\t\t\t{\n\t\t\t\tthis->xrPacket.AddReport(receiverReferenceTimeReport);\n\t\t\t}\n\n\t\t\t// New items can hold in the packet, report it.\n\t\t\tif (GetSize() <= RTC::Consts::RtcpPacketMaxSize)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// New items can not hold in the packet, remove them,\n\t\t\t// delete and report it.\n\n\t\t\tfor (auto* report : receiverReports)\n\t\t\t{\n\t\t\t\tthis->receiverReportPacket.RemoveReport(report);\n\t\t\t\tdelete report;\n\t\t\t}\n\n\t\t\tif (receiverReferenceTimeReport)\n\t\t\t{\n\t\t\t\tthis->xrPacket.RemoveReport(receiverReferenceTimeReport);\n\t\t\t\tdelete receiverReferenceTimeReport;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tvoid CompoundPacket::Dump(int indentation)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<CompoundPacket>\");\n\n\t\t\tif (HasSenderReport())\n\t\t\t{\n\t\t\t\tthis->senderReportPacket.Dump(indentation + 1);\n\n\t\t\t\tif (this->receiverReportPacket.GetCount() != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->receiverReportPacket.Dump(indentation + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->receiverReportPacket.Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tif (this->sdesPacket.GetCount() != 0u)\n\t\t\t{\n\t\t\t\tthis->sdesPacket.Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tif (this->xrPacket.Begin() != this->xrPacket.End())\n\t\t\t{\n\t\t\t\tthis->xrPacket.Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"</CompoundPacket>\");\n\t\t}\n\n\t\tvoid CompoundPacket::AddSenderReport(SenderReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->senderReportPacket.AddReport(report);\n\t\t}\n\n\t\tvoid CompoundPacket::AddReceiverReport(ReceiverReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->receiverReportPacket.AddReport(report);\n\t\t}\n\n\t\tvoid CompoundPacket::AddSdesChunk(SdesChunk* chunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sdesPacket.AddChunk(chunk);\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/Feedback.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::Feedback\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/Feedback.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n#include \"RTC/RTCP/FeedbackPsLei.hpp\"\n#include \"RTC/RTCP/FeedbackPsPli.hpp\"\n#include \"RTC/RTCP/FeedbackPsRpsi.hpp\"\n#include \"RTC/RTCP/FeedbackPsSli.hpp\"\n#include \"RTC/RTCP/FeedbackPsTst.hpp\"\n#include \"RTC/RTCP/FeedbackPsVbcm.hpp\"\n#include \"RTC/RTCP/FeedbackRtpEcn.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/FeedbackRtpSrReq.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTllei.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTmmb.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\ttemplate<typename T>\n\t\tconst std::string& FeedbackPacket<T>::MessageTypeToString(typename T::MessageType type)\n\t\t{\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = FeedbackPacket<T>::MessageType2String.find(type);\n\n\t\t\tif (it == FeedbackPacket<T>::MessageType2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\ttemplate<typename T>\n\t\tFeedbackPacket<T>::FeedbackPacket(CommonHeader* commonHeader)\n\t\t  : Packet(commonHeader), messageType(typename T::MessageType(commonHeader->count))\n\t\t{\n\t\t\tthis->header = reinterpret_cast<Header*>(\n\t\t\t  reinterpret_cast<uint8_t*>(commonHeader) + Packet::CommonHeaderSize);\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tFeedbackPacket<T>::FeedbackPacket(\n\t\t  typename T::MessageType messageType, uint32_t senderSsrc, uint32_t mediaSsrc)\n\t\t  : Packet(rtcpType), messageType(messageType)\n\t\t{\n\t\t\tthis->raw                = new uint8_t[HeaderSize];\n\t\t\tthis->header             = reinterpret_cast<Header*>(this->raw);\n\t\t\tthis->header->senderSsrc = htonl(senderSsrc);\n\t\t\tthis->header->mediaSsrc  = htonl(mediaSsrc);\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tFeedbackPacket<T>::~FeedbackPacket()\n\t\t{\n\t\t\tdelete[] this->raw;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\ttemplate<typename T>\n\t\tsize_t FeedbackPacket<T>::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t offset = Packet::Serialize(buffer);\n\n\t\t\t// Copy the header.\n\t\t\tstd::memcpy(buffer + offset, this->header, HeaderSize);\n\n\t\t\treturn offset + HeaderSize;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tvoid FeedbackPacket<T>::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sender ssrc: %\" PRIu32, GetSenderSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  media ssrc: %\" PRIu32, GetMediaSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  size: %zu\", this->GetSize());\n\t\t}\n\n\t\t/* Specialization for Ps class. */\n\n\t\ttemplate<>\n\t\tRTCP::Type FeedbackPacket<FeedbackPs>::rtcpType = RTCP::Type::PSFB;\n\n\t\t// clang-format off\n\t\ttemplate<>\n\t\tconst absl::flat_hash_map<FeedbackPs::MessageType, std::string> FeedbackPacket<FeedbackPs>::MessageType2String =\n\t\t{\n\t\t\t{ FeedbackPs::MessageType::PLI,   \"PLI\"   },\n\t\t\t{ FeedbackPs::MessageType::SLI,   \"SLI\"   },\n\t\t\t{ FeedbackPs::MessageType::RPSI,  \"RPSI\"  },\n\t\t\t{ FeedbackPs::MessageType::FIR,   \"FIR\"   },\n\t\t\t{ FeedbackPs::MessageType::TSTR,  \"TSTR\"  },\n\t\t\t{ FeedbackPs::MessageType::TSTN,  \"TSTN\"  },\n\t\t\t{ FeedbackPs::MessageType::VBCM,  \"VBCM\"  },\n\t\t\t{ FeedbackPs::MessageType::PSLEI, \"PSLEI\" },\n\t\t\t{ FeedbackPs::MessageType::ROI,   \"ROI\"   },\n\t\t\t{ FeedbackPs::MessageType::AFB,   \"AFB\"   },\n\t\t\t{ FeedbackPs::MessageType::EXT,   \"EXT\"   }\n\t\t};\n\t\t// clang-format on\n\n\t\ttemplate<>\n\t\tFeedbackPacket<FeedbackPs>* FeedbackPacket<FeedbackPs>::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\t\t\tFeedbackPsPacket* packet{ nullptr }; // NOLINT(misc-const-correctness)\n\n\t\t\tswitch (FeedbackPs::MessageType(commonHeader->count))\n\t\t\t{\n\t\t\t\tcase FeedbackPs::MessageType::PLI:\n\t\t\t\t\tpacket = FeedbackPsPliPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::SLI:\n\t\t\t\t\tpacket = FeedbackPsSliPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::RPSI:\n\t\t\t\t\tpacket = FeedbackPsRpsiPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::FIR:\n\t\t\t\t\tpacket = FeedbackPsFirPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::TSTR:\n\t\t\t\t\tpacket = FeedbackPsTstrPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::TSTN:\n\t\t\t\t\tpacket = FeedbackPsTstnPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::VBCM:\n\t\t\t\t\tpacket = FeedbackPsVbcmPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::PSLEI:\n\t\t\t\t\tpacket = FeedbackPsLeiPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::ROI:\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::AFB:\n\t\t\t\t\tpacket = FeedbackPsAfbPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackPs::MessageType::EXT:\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtcp, \"unknown RTCP PS Feedback message type [packetType:%\" PRIu8 \"]\", commonHeader->count);\n\t\t\t}\n\n\t\t\treturn packet;\n\t\t}\n\n\t\t/* Specialization for Rtcp class. */\n\n\t\ttemplate<>\n\t\tType FeedbackPacket<FeedbackRtp>::rtcpType = RTCP::Type::RTPFB;\n\n\t\t// clang-format off\n\t\ttemplate<>\n\t\tconst absl::flat_hash_map<FeedbackRtp::MessageType, std::string> FeedbackPacket<FeedbackRtp>::MessageType2String =\n\t\t{\n\t\t\t{ FeedbackRtp::MessageType::NACK,   \"NACK\"   },\n\t\t\t{ FeedbackRtp::MessageType::TMMBR,  \"TMMBR\"  },\n\t\t\t{ FeedbackRtp::MessageType::TMMBN,  \"TMMBN\"  },\n\t\t\t{ FeedbackRtp::MessageType::SR_REQ, \"SR_REQ\" },\n\t\t\t{ FeedbackRtp::MessageType::RAMS,   \"RAMS\"   },\n\t\t\t{ FeedbackRtp::MessageType::TLLEI,  \"TLLEI\"  },\n\t\t\t{ FeedbackRtp::MessageType::ECN,    \"ECN\"    },\n\t\t\t{ FeedbackRtp::MessageType::PS,     \"PS\"     },\n\t\t\t{ FeedbackRtp::MessageType::EXT,    \"EXT\"    },\n\t\t\t{ FeedbackRtp::MessageType::TCC,    \"TCC\"    }\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\ttemplate<>\n\t\tFeedbackPacket<FeedbackRtp>* FeedbackPacket<FeedbackRtp>::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* commonHeader = reinterpret_cast<CommonHeader*>(const_cast<uint8_t*>(data));\n\t\t\tFeedbackRtpPacket* packet{ nullptr };\n\n\t\t\tswitch (FeedbackRtp::MessageType(commonHeader->count))\n\t\t\t{\n\t\t\t\tcase FeedbackRtp::MessageType::NACK:\n\t\t\t\t\tpacket = FeedbackRtpNackPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::TMMBR:\n\t\t\t\t\tpacket = FeedbackRtpTmmbrPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::TMMBN:\n\t\t\t\t\tpacket = FeedbackRtpTmmbnPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::SR_REQ:\n\t\t\t\t\tpacket = FeedbackRtpSrReqPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::RAMS:\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::TLLEI:\n\t\t\t\t\tpacket = FeedbackRtpTlleiPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::ECN:\n\t\t\t\t\tpacket = FeedbackRtpEcnPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::PS:\n\t\t\t\t\tbreak;\n\t\t\t\tcase FeedbackRtp::MessageType::EXT:\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase FeedbackRtp::MessageType::TCC:\n\t\t\t\t\tpacket = FeedbackRtpTransportPacket::Parse(data, len);\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtcp,\n\t\t\t\t\t  \"unknown RTCP RTP Feedback message type [packetType:%\" PRIu8 \"]\",\n\t\t\t\t\t  commonHeader->count);\n\t\t\t}\n\n\t\t\treturn packet;\n\t\t}\n\n\t\t// Explicit instantiation to have all FeedbackPacket definitions in this file.\n\t\ttemplate class FeedbackPacket<FeedbackPs>;\n\t\ttemplate class FeedbackPacket<FeedbackRtp>;\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPs.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPs\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackItem.hpp\"\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n#include \"RTC/RTCP/FeedbackPsLei.hpp\"\n#include \"RTC/RTCP/FeedbackPsRpsi.hpp\"\n#include \"RTC/RTCP/FeedbackPsSli.hpp\"\n#include \"RTC/RTCP/FeedbackPsTst.hpp\"\n#include \"RTC/RTCP/FeedbackPsVbcm.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\ttemplate<typename Item>\n\t\tFeedbackPsItemsPacket<Item>* FeedbackPsItemsPacket<Item>::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackPsItemsPacket<Item>> packet(\n\t\t\t  new FeedbackPsItemsPacket<Item>(commonHeader));\n\n\t\t\tsize_t offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize;\n\n\t\t\twhile (len > offset)\n\t\t\t{\n\t\t\t\tauto* item = FeedbackItem::Parse<Item>(data + offset, len - offset);\n\n\t\t\t\tif (item)\n\t\t\t\t{\n\t\t\t\t\tif (!item->IsCorrect())\n\t\t\t\t\t{\n\t\t\t\t\t\tdelete item;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tpacket->AddItem(item);\n\t\t\t\t\toffset += item->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\ttemplate<typename Item>\n\t\tsize_t FeedbackPsItemsPacket<Item>::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset = FeedbackPacket::Serialize(buffer);\n\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\toffset += item->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\ttemplate<typename Item>\n\t\tvoid FeedbackPsItemsPacket<Item>::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"<%s>\", FeedbackPsPacket::MessageTypeToString(Item::MessageType).c_str());\n\t\t\tFeedbackPsPacket::Dump(indentation + 1);\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\titem->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"</%s>\", FeedbackPsPacket::MessageTypeToString(Item::MessageType).c_str());\n\t\t}\n\n\t\t// explicit instantiation to have all FeedbackRtpPacket definitions in this file.\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsFirItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsSliItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsRpsiItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsTstrItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsTstnItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsVbcmItem>;\n\t\ttemplate class FeedbackPsItemsPacket<FeedbackPsLeiItem>;\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsAfb.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsAfb\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tFeedbackPsAfbPacket* FeedbackPsAfbPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackPsAfbPacket> packet;\n\n\t\t\tconstexpr size_t Offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize;\n\n\t\t\tif (\n\t\t\t  len >= Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 4 &&\n\t\t\t  Utils::Byte::Get4Bytes(data, Offset) == FeedbackPsRembPacket::UniqueIdentifier)\n\t\t\t{\n\t\t\t\tpacket.reset(FeedbackPsRembPacket::Parse(data, len));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket.reset(new FeedbackPsAfbPacket(commonHeader));\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\tsize_t FeedbackPsAfbPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t offset = FeedbackPsPacket::Serialize(buffer);\n\n\t\t\t// Copy the content.\n\t\t\tstd::memcpy(buffer + offset, this->data, this->size);\n\n\t\t\treturn offset + this->size;\n\t\t}\n\n\t\tvoid FeedbackPsAfbPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsAfbPacket>\");\n\t\t\tFeedbackPsPacket::Dump(indentation + 1);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsAfbPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsFir.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsFir\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memset()\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\t/* Instance methods. */\n\n\t\tFeedbackPsFirItem::FeedbackPsFirItem(uint32_t ssrc, uint8_t sequenceNumber)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->raw    = new uint8_t[HeaderSize];\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\t// Set reserved bits to zero.\n\t\t\tstd::memset(this->header, 0, HeaderSize);\n\n\t\t\tthis->header->ssrc           = htonl(ssrc);\n\t\t\tthis->header->sequenceNumber = sequenceNumber;\n\t\t}\n\n\t\tsize_t FeedbackPsFirItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackPsFirItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsFirItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sequence number: %\" PRIu8, this->GetSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsFirItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsLei.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsPsLei\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsLei.hpp\"\n#include \"Logger.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\t\tFeedbackPsLeiItem::FeedbackPsLeiItem(uint32_t ssrc)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->raw          = new uint8_t[HeaderSize];\n\t\t\tthis->header       = reinterpret_cast<Header*>(this->raw);\n\t\t\tthis->header->ssrc = htonl(ssrc);\n\t\t}\n\n\t\tsize_t FeedbackPsLeiItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackPsLeiItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsLeiItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsLeiItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsPli.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsPli\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsPli.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tFeedbackPsPliPacket* FeedbackPsPliPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackPsPliPacket> packet(new FeedbackPsPliPacket(commonHeader));\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\tvoid FeedbackPsPliPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsPliPacket>\");\n\t\t\tFeedbackPsPacket::Dump(indentation + 1);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsPliPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsRemb.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsRemb\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tFeedbackPsRembPacket* FeedbackPsRembPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Check that there is space for the REMB unique identifier and basic fields.\n\t\t\t// NOTE: Feedback.cpp already checked that there is space for CommonHeader and\n\t\t\t// Feedback Header.\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 8u)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackPsRembPacket> packet(new FeedbackPsRembPacket(commonHeader, len));\n\n\t\t\tif (!packet->IsCorrect())\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\tFeedbackPsRembPacket::FeedbackPsRembPacket(CommonHeader* commonHeader, size_t availableLen)\n\t\t  : FeedbackPsAfbPacket(commonHeader, FeedbackPsAfbPacket::Application::REMB)\n\t\t{\n\t\t\tconst size_t len = static_cast<size_t>(ntohs(commonHeader->length) + 1) * 4;\n\n\t\t\tif (len > availableLen)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"packet announced length exceeds the available buffer length, discarded\");\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Make data point to the 4 bytes that must containt the \"REMB\" identifier.\n\t\t\tauto* data            = reinterpret_cast<uint8_t*>(commonHeader) + Packet::CommonHeaderSize +\n\t\t\t                        FeedbackPacket::HeaderSize;\n\t\t\tconst size_t numSsrcs = data[4];\n\n\t\t\t// Ensure there is space for the the announced number of SSRC feedbacks.\n\t\t\tif (len != Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 8u + (numSsrcs * 4u))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtcp, \"invalid payload size (%zu bytes) for the given number of ssrcs (%zu)\", len, numSsrcs);\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Verify the \"REMB\" unique identifier.\n\t\t\tif (Utils::Byte::Get4Bytes(data, 0) != FeedbackPsRembPacket::UniqueIdentifier)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid unique indentifier in REMB packet\");\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst uint8_t exponent = data[5] >> 2;\n\t\t\tconst uint64_t mantissa =\n\t\t\t  (static_cast<uint32_t>(data[5] & 0x03) << 16) | Utils::Byte::Get2Bytes(data, 6);\n\n\t\t\tthis->bitrate = (mantissa << exponent);\n\n\t\t\tif ((this->bitrate >> exponent) != mantissa)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid REMB bitrate value: %\" PRIu64 \" *2^%u\", mantissa, exponent);\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Make index point to the first SSRC feedback item.\n\t\t\tsize_t index{ 8 };\n\n\t\t\tthis->ssrcs.reserve(numSsrcs);\n\n\t\t\tfor (size_t n{ 0 }; n < numSsrcs; ++n)\n\t\t\t{\n\t\t\t\tthis->ssrcs.push_back(Utils::Byte::Get4Bytes(data, index));\n\t\t\t\tindex += 4u;\n\t\t\t}\n\t\t}\n\n\t\tsize_t FeedbackPsRembPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOLINTNEXTLINE(bugprone-parent-virtual-call)\n\t\t\tsize_t offset     = FeedbackPsPacket::Serialize(buffer);\n\t\t\tuint64_t mantissa = this->bitrate;\n\t\t\tuint8_t exponent{ 0u };\n\n\t\t\twhile (mantissa > 0x3FFFF /* max mantissa (18 bits) */)\n\t\t\t{\n\t\t\t\tmantissa >>= 1;\n\t\t\t\t++exponent;\n\t\t\t}\n\n\t\t\tUtils::Byte::Set4Bytes(buffer, offset, FeedbackPsRembPacket::UniqueIdentifier);\n\t\t\toffset += FeedbackPsRembPacket::UniqueIdentifierSize;\n\n\t\t\tbuffer[offset] = this->ssrcs.size();\n\t\t\toffset += 1;\n\n\t\t\tbuffer[offset] = (exponent << 2) | (mantissa >> 16);\n\t\t\toffset += 1;\n\n\t\t\tUtils::Byte::Set2Bytes(buffer, offset, mantissa & 0xFFFF);\n\t\t\toffset += 2;\n\n\t\t\tfor (auto ssrc : this->ssrcs)\n\t\t\t{\n\t\t\t\tUtils::Byte::Set4Bytes(buffer, offset, ssrc);\n\t\t\t\toffset += 4u;\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid FeedbackPsRembPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsRembPacket>\");\n\t\t\t// NOLINTNEXTLINE(bugprone-parent-virtual-call)\n\t\t\tFeedbackPsPacket::Dump();\n\t\t\tMS_DUMP_CLEAN(indentation, \"  bitrate (bps): %\" PRIu64, this->bitrate);\n\t\t\tfor (auto ssrc : this->ssrcs)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, ssrc);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsRembPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsRpsi.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsRpsi\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsRpsi.hpp\"\n#include \"Logger.hpp\"\n#include <cstring>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\n\t\tFeedbackPsRpsiItem::FeedbackPsRpsiItem(Header* header)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->header = header;\n\n\t\t\t// Calculate bitString length.\n\t\t\tif (this->header->paddingBits % 8 != 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid Rpsi packet with fractional padding bytes value\");\n\n\t\t\t\tisCorrect = false;\n\t\t\t}\n\n\t\t\tconst size_t paddingBytes = this->header->paddingBits / 8;\n\n\t\t\tif (paddingBytes > FeedbackPsRpsiItem::MaxBitStringSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid Rpsi packet with too many padding bytes\");\n\n\t\t\t\tisCorrect = false;\n\t\t\t}\n\n\t\t\tthis->length = FeedbackPsRpsiItem::MaxBitStringSize - paddingBytes;\n\t\t}\n\n\t\tFeedbackPsRpsiItem::FeedbackPsRpsiItem(uint8_t payloadType, uint8_t* bitString, size_t length)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(payloadType <= 0x7f, \"rpsi payload type exceeds the maximum value\");\n\t\t\tMS_ASSERT(\n\t\t\t  length <= FeedbackPsRpsiItem::MaxBitStringSize,\n\t\t\t  \"rpsi bit string length exceeds the maximum value\");\n\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\t// TODO: We should use Utils::Byte::PadTo4Bytes().\n\t\t\t// 32 bits padding.\n\t\t\tconst uint8_t padding = (-length) & 3;\n\n\t\t\tthis->header->paddingBits = padding * 8;\n\t\t\tthis->header->zero        = 0;\n\t\t\tstd::memcpy(this->header->bitString, bitString, length);\n\n\t\t\tthis->raw = new uint8_t[FeedbackPsRpsiItem::HeaderSize + padding];\n\n\t\t\t// Fill padding.\n\t\t\tfor (uint8_t i{ 0 }; i < padding; ++i)\n\t\t\t{\n\t\t\t\tthis->raw[FeedbackPsRpsiItem::HeaderSize + i - 1] = 0;\n\t\t\t}\n\t\t}\n\n\t\tsize_t FeedbackPsRpsiItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memcpy(buffer, this->header, FeedbackPsRpsiItem::HeaderSize);\n\n\t\t\treturn FeedbackPsRpsiItem::HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackPsRpsiItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsRpsiItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  padding bits %\" PRIu8, this->header->paddingBits);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload type: %\" PRIu8, this->GetPayloadType());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %zu\", this->GetLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsRpsiItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsSli.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsSli\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsSli.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\n\t\tFeedbackPsSliItem::FeedbackPsSliItem(Header* header)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->header = header;\n\n\t\t\tauto compact = ntohl(header->compact);\n\n\t\t\tthis->first     = compact >> 19;           /* first 13 bits */\n\t\t\tthis->number    = (compact >> 6) & 0x1fff; /* next  13 bits */\n\t\t\tthis->pictureId = compact & 0x3f;          /* last   6 bits */\n\t\t}\n\n\t\tsize_t FeedbackPsSliItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tconst uint32_t compact = (this->first << 19) | (this->number << 6) | this->pictureId;\n\t\t\tauto* header           = reinterpret_cast<Header*>(buffer);\n\n\t\t\theader->compact = htonl(compact);\n\t\t\tstd::memcpy(buffer, header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackPsSliItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsSliItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  first: %\" PRIu16, this->first);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number: %\" PRIu16, this->number);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  picture id: %\" PRIu8, this->pictureId);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsSliItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsTst.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsTst\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsTst.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\ttemplate<typename T>\n\t\tFeedbackPsTstItem<T>::FeedbackPsTstItem(uint32_t ssrc, uint8_t sequenceNumber, uint8_t index)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->raw    = new uint8_t[HeaderSize];\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\t// Set reserved bits to zero.\n\t\t\tstd::memset(this->header, 0, HeaderSize);\n\n\t\t\tthis->header->ssrc           = htonl(ssrc);\n\t\t\tthis->header->sequenceNumber = sequenceNumber;\n\t\t\tthis->header->index          = index;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tsize_t FeedbackPsTstItem<T>::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tvoid FeedbackPsTstItem<T>::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsTstItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sequence number: %\" PRIu32, this->GetSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  index: %\" PRIu32, this->GetIndex());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsTstItem>\");\n\t\t}\n\n\t\t/* Specialization for Tstr class. */\n\n\t\ttemplate<>\n\t\tconst FeedbackPs::MessageType FeedbackPsTstItem<Tstr>::MessageType =\n\t\t  FeedbackPs::MessageType::TSTR;\n\n\t\t/* Specialization for Tstn class. */\n\n\t\ttemplate<>\n\t\tconst FeedbackPs::MessageType FeedbackPsTstItem<Tstn>::MessageType =\n\t\t  FeedbackPs::MessageType::TSTN;\n\n\t\t// Explicit instantiation to have all definitions in this file.\n\t\ttemplate class FeedbackPsTstItem<Tstr>;\n\t\ttemplate class FeedbackPsTstItem<Tstn>;\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackPsVbcm.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackPsVbcm\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackPsVbcm.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\t\tFeedbackPsVbcmItem::FeedbackPsVbcmItem(\n\t\t  uint32_t ssrc, uint8_t sequenceNumber, uint8_t payloadType, uint16_t length, uint8_t* value)\n\t\t{\n\t\t\tthis->raw    = new uint8_t[8 + length];\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\tthis->header->ssrc           = htonl(ssrc);\n\t\t\tthis->header->sequenceNumber = sequenceNumber;\n\t\t\tthis->header->zero           = 0;\n\t\t\tthis->header->payloadType    = payloadType;\n\t\t\tthis->header->length         = htons(length);\n\t\t\tstd::memcpy(this->header->value, value, sizeof(length));\n\t\t}\n\n\t\tsize_t FeedbackPsVbcmItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, 8);\n\n\t\t\t// Copy the content.\n\t\t\tstd::memcpy(buffer + 8, this->header->value, GetLength());\n\n\t\t\tconst size_t offset = 8 + GetLength();\n\t\t\t// 32 bits padding.\n\t\t\tconst size_t padding = (-offset) & 3;\n\n\t\t\tfor (size_t i{ 0 }; i < padding; ++i)\n\t\t\t{\n\t\t\t\tbuffer[offset + i] = 0;\n\t\t\t}\n\n\t\t\treturn offset + padding;\n\t\t}\n\n\t\tvoid FeedbackPsVbcmItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackPsVbcmItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sequence number: %\" PRIu8, this->GetSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload type: %\" PRIu8, this->GetPayloadType());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %\" PRIu16, this->GetLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackPsVbcmItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtp.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtp\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackRtpEcn.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTllei.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTmmb.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\ttemplate<typename Item>\n\t\tFeedbackRtpItemsPacket<Item>* FeedbackRtpItemsPacket<Item>::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackRtpItemsPacket<Item>> packet(\n\t\t\t  new FeedbackRtpItemsPacket<Item>(commonHeader));\n\n\t\t\tsize_t offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize;\n\n\t\t\twhile (len > offset)\n\t\t\t{\n\t\t\t\tauto* item = FeedbackItem::Parse<Item>(data + offset, len - offset);\n\n\t\t\t\tif (item)\n\t\t\t\t{\n\t\t\t\t\tpacket->AddItem(item);\n\t\t\t\t\toffset += item->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\ttemplate<typename Item>\n\t\tsize_t FeedbackRtpItemsPacket<Item>::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset = FeedbackPacket::Serialize(buffer);\n\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\toffset += item->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\ttemplate<typename Item>\n\t\tvoid FeedbackRtpItemsPacket<Item>::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"<%s>\", FeedbackRtpPacket::MessageTypeToString(Item::MessageType).c_str());\n\t\t\tFeedbackRtpPacket::Dump(indentation + 1);\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\titem->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"</%s>\", FeedbackRtpPacket::MessageTypeToString(Item::MessageType).c_str());\n\t\t}\n\n\t\t// Explicit instantiation to have all FeedbackRtpPacket definitions in this file.\n\t\ttemplate class FeedbackRtpItemsPacket<FeedbackRtpNackItem>;\n\t\ttemplate class FeedbackRtpItemsPacket<FeedbackRtpTmmbrItem>;\n\t\ttemplate class FeedbackRtpItemsPacket<FeedbackRtpTmmbnItem>;\n\t\ttemplate class FeedbackRtpItemsPacket<FeedbackRtpTlleiItem>;\n\t\ttemplate class FeedbackRtpItemsPacket<FeedbackRtpEcnItem>;\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpEcn.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpEcn\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpEcn.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\tsize_t FeedbackRtpEcnItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackRtpEcnItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpEcnItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sequence number: %\" PRIu32, this->GetSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ect0 counter: %\" PRIu32, this->GetEct0Counter());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ect1 counter: %\" PRIu32, this->GetEct1Counter());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ecn ce counter: %\" PRIu16, this->GetEcnCeCounter());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  not ect counter: %\" PRIu16, this->GetNotEctCounter());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  lost packets: %\" PRIu16, this->GetLostPackets());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  duplicated packets: %\" PRIu16, this->GetDuplicatedPackets());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpEcnItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpNack.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpNack\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"Logger.hpp\"\n#include <bitset>  // std::bitset()\n#include <cstring> // std::memcpy\n#include <string>\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\t\tFeedbackRtpNackItem::FeedbackRtpNackItem(uint16_t packetId, uint16_t lostPacketBitmask)\n\t\t{\n\t\t\tthis->raw    = new uint8_t[HeaderSize];\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\tthis->header->packetId          = htons(packetId);\n\t\t\tthis->header->lostPacketBitmask = htons(lostPacketBitmask);\n\t\t}\n\n\t\tsize_t FeedbackRtpNackItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackRtpNackItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst std::bitset<16> nackBitset(GetLostPacketBitmask());\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpNackItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  pid: %\" PRIu16, this->GetPacketId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  bpl: %s\", nackBitset.to_string().c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpNackItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpSrReq.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpSrReq\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpSrReq.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tFeedbackRtpSrReqPacket* FeedbackRtpSrReqPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* commonHeader = reinterpret_cast<CommonHeader*>(const_cast<uint8_t*>(data));\n\n\t\t\treturn new FeedbackRtpSrReqPacket(commonHeader);\n\t\t}\n\n\t\tvoid FeedbackRtpSrReqPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpSrReqPacket>\");\n\t\t\tFeedbackRtpPacket::Dump(indentation + 1);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpSrReqPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpTllei.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpTllei\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpTllei.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\t\tFeedbackRtpTlleiItem::FeedbackRtpTlleiItem(uint16_t packetId, uint16_t lostPacketBitmask)\n\t\t{\n\t\t\tthis->raw    = new uint8_t[HeaderSize];\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw);\n\n\t\t\tthis->header->packetId          = htons(packetId);\n\t\t\tthis->header->lostPacketBitmask = htons(lostPacketBitmask);\n\t\t}\n\n\t\tsize_t FeedbackRtpTlleiItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\tvoid FeedbackRtpTlleiItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpTlleiItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  pid: %\" PRIu16, this->GetPacketId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  bpl: %\" PRIu16, this->GetLostPacketBitmask());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpTlleiItem>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpTmmb.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpTmmb\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpTmmb.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Instance methods. */\n\t\ttemplate<typename T>\n\t\tFeedbackRtpTmmbItem<T>::FeedbackRtpTmmbItem(const Header* header)\n\t\t  : FeedbackRtpTmmbItem<T>(reinterpret_cast<const uint8_t*>(header))\n\t\t{\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tFeedbackRtpTmmbItem<T>::FeedbackRtpTmmbItem(const uint8_t* data)\n\t\t{\n\t\t\tthis->ssrc = Utils::Byte::Get4Bytes(data, 0);\n\n\t\t\t// Read the 4 bytes block.\n\t\t\tconst uint32_t compact = Utils::Byte::Get4Bytes(data, 4);\n\t\t\t// Read each component.\n\t\t\tconst uint8_t exponent  = compact >> 26;            // 6 bits.\n\t\t\tconst uint64_t mantissa = (compact >> 9) & 0x1ffff; // 17 bits.\n\n\t\t\tthis->overhead = compact & 0x1ff; // 9 bits.\n\t\t\t// Get the bitrate out of exponent and mantissa.\n\t\t\tthis->bitrate = (mantissa << exponent);\n\n\t\t\tif ((this->bitrate >> exponent) != mantissa)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid TMMB bitrate value : %\" PRIu64 \" x 2^%\" PRIu8, mantissa, exponent);\n\n\t\t\t\tthis->isCorrect = false;\n\t\t\t}\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tsize_t FeedbackRtpTmmbItem<T>::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tstatic constexpr uint32_t MaxMantissa{ 0x1ffff }; // 17 bits.\n\n\t\t\tuint64_t mantissa = this->bitrate;\n\t\t\tuint32_t exponent{ 0 };\n\n\t\t\twhile (mantissa > MaxMantissa)\n\t\t\t{\n\t\t\t\tmantissa >>= 1;\n\t\t\t\t++exponent;\n\t\t\t}\n\n\t\t\tUtils::Byte::Set4Bytes(buffer, 0, this->ssrc);\n\n\t\t\tconst uint32_t compact = (exponent << 26) | (mantissa << 9) | this->overhead;\n\n\t\t\tUtils::Byte::Set4Bytes(buffer, 4, compact);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\ttemplate<typename T>\n\t\tvoid FeedbackRtpTmmbItem<T>::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpTmmbItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  bitrate: %\" PRIu64, this->GetBitrate());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  overhead: %\" PRIu16, this->GetOverhead());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpTmmbItem>\");\n\t\t}\n\n\t\t/* Specialization for Tmmbr class. */\n\n\t\ttemplate<>\n\t\tconst FeedbackRtp::MessageType FeedbackRtpTmmbItem<FeedbackRtpTmmbr>::MessageType =\n\t\t  FeedbackRtp::MessageType::TMMBR;\n\n\t\t/* Specialization for Tmmbn class. */\n\n\t\ttemplate<>\n\t\tconst FeedbackRtp::MessageType FeedbackRtpTmmbItem<FeedbackRtpTmmbn>::MessageType =\n\t\t  FeedbackRtp::MessageType::TMMBN;\n\n\t\t// Explicit instantiation to have all FeedbackRtpTmmbItem definitions in this file.\n\t\ttemplate class FeedbackRtpTmmbItem<FeedbackRtpTmmbr>;\n\t\ttemplate class FeedbackRtpTmmbItem<FeedbackRtpTmmbn>;\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/FeedbackRtpTransport.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::FeedbackRtpTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <sstream> // std::ostringstream\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Static members. */\n\n\t\tsize_t FeedbackRtpTransportPacket::fixedHeaderSize{ 8u };\n\t\tuint16_t FeedbackRtpTransportPacket::maxMissingPackets{ (1 << 13) - 1 };\n\t\tuint16_t FeedbackRtpTransportPacket::maxPacketStatusCount{ (1 << 16) - 1 };\n\t\tint16_t FeedbackRtpTransportPacket::maxPacketDelta{ 0x7FFF };\n\n\t\t// clang-format off\n\t\tconst absl::flat_hash_map<FeedbackRtpTransportPacket::Status, std::string> FeedbackRtpTransportPacket::Status2String =\n\t\t{\n\t\t\t{ FeedbackRtpTransportPacket::Status::NotReceived, \"NR\" },\n\t\t\t{ FeedbackRtpTransportPacket::Status::SmallDelta,  \"SD\" },\n\t\t\t{ FeedbackRtpTransportPacket::Status::LargeDelta,  \"LD\" }\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tFeedbackRtpTransportPacket* FeedbackRtpTransportPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + FeedbackRtpTransportPacket::fixedHeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for Feedback packet, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(llvm-qualified-auto)\n\t\t\tauto* commonHeader = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<FeedbackRtpTransportPacket> packet(\n\t\t\t  new FeedbackRtpTransportPacket(commonHeader, len));\n\n\t\t\tif (!packet->IsCorrect())\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tFeedbackRtpTransportPacket::FeedbackRtpTransportPacket(CommonHeader* commonHeader, size_t availableLen)\n\t\t  : FeedbackRtpPacket(commonHeader)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t len = static_cast<size_t>(ntohs(commonHeader->length) + 1) * 4;\n\n\t\t\tif (len > availableLen)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"packet announced length exceeds the available buffer length, discarded\");\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Make data point to the packet specific info.\n\t\t\tauto* data = reinterpret_cast<uint8_t*>(commonHeader) + Packet::CommonHeaderSize +\n\t\t\t             FeedbackPacket::HeaderSize;\n\n\t\t\tthis->baseSequenceNumber  = Utils::Byte::Get2Bytes(data, 0);\n\t\t\tthis->packetStatusCount   = Utils::Byte::Get2Bytes(data, 2);\n\t\t\tthis->referenceTime       = Utils::Byte::Get3BytesSigned(data, 4);\n\t\t\tthis->feedbackPacketCount = Utils::Byte::Get1Byte(data, 7);\n\t\t\tthis->size                = len;\n\n\t\t\t// Make contentData point to the beginning of the chunks.\n\t\t\tuint8_t* contentData = data + FeedbackRtpTransportPacket::fixedHeaderSize;\n\t\t\t// Make contentLen be the available length for chunks.\n\t\t\tconst size_t contentLen = len - Packet::CommonHeaderSize - FeedbackPacket::HeaderSize -\n\t\t\t                          FeedbackRtpTransportPacket::fixedHeaderSize;\n\t\t\tsize_t offset{ 0u };\n\t\t\tuint16_t count{ 0u };\n\t\t\tuint16_t receivedPacketStatusCount{ 0u };\n\n\t\t\twhile (count < this->packetStatusCount && contentLen > offset)\n\t\t\t{\n\t\t\t\tif (contentLen - offset < 2u)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for chunk\");\n\n\t\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* chunk =\n\t\t\t\t  Chunk::Parse(contentData + offset, contentLen - offset, this->packetStatusCount - count);\n\n\t\t\t\tif (!chunk)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"invalid chunk\");\n\n\t\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis->chunks.push_back(chunk);\n\t\t\t\tthis->deltasAndChunksSize += 2u;\n\n\t\t\t\toffset += 2u;\n\t\t\t\tcount += chunk->GetCount();\n\t\t\t\treceivedPacketStatusCount += chunk->GetReceivedStatusCount();\n\t\t\t}\n\n\t\t\tif (count != this->packetStatusCount)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"provided packet status count does not match with content\");\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto chunksIt = this->chunks.begin();\n\n\t\t\twhile (chunksIt != this->chunks.end() && contentLen > offset)\n\t\t\t{\n\t\t\t\tsize_t deltasOffset{ 0u };\n\t\t\t\tauto* chunk = *chunksIt;\n\n\t\t\t\tif (!chunk->AddDeltas(contentData + offset, contentLen - offset, this->deltas, deltasOffset))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for deltas\");\n\n\t\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\toffset += deltasOffset;\n\t\t\t\tthis->deltasAndChunksSize += deltasOffset;\n\n\t\t\t\t++chunksIt;\n\t\t\t}\n\n\t\t\tif (this->deltas.size() != receivedPacketStatusCount)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"received deltas does not match received status count\");\n\n\t\t\t\tthis->isCorrect = false;\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::~FeedbackRtpTransportPacket()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tdelete chunk;\n\t\t\t}\n\t\t\tthis->chunks.clear();\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<FeedbackRtpTransportPacket>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  base sequence: %\" PRIu16, this->baseSequenceNumber);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  packet status count: %\" PRIu16, this->packetStatusCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  reference time: %\" PRIi32, this->referenceTime);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  feedback packet count: %\" PRIu8, this->feedbackPacketCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  size: %zu\", GetSize());\n\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tchunk->Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<Deltas>\");\n\t\t\tfor (auto delta : this->deltas)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  %\" PRIi16 \" ms\", static_cast<int16_t>(delta / 4));\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</Deltas>\");\n\n\t\t\tauto packetResults = GetPacketResults();\n\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<PacketResults>\");\n\t\t\tfor (auto& packetResult : packetResults)\n\t\t\t{\n\t\t\t\tif (packetResult.received)\n\t\t\t\t{\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t  \"  seq:%\" PRIu16 \", received:yes, receivedAtMs:%\" PRIi64,\n\t\t\t\t\t  packetResult.sequenceNumber,\n\t\t\t\t\t  packetResult.receivedAtMs);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1, \"  seq:%\" PRIu16 \", received:no\", packetResult.sequenceNumber);\n\t\t\t\t}\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</PacketResults>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</FeedbackRtpTransportPacket>\");\n\t\t}\n\n\t\tsize_t FeedbackRtpTransportPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add chunks for status packets that may not be represented yet.\n\t\t\tAddPendingChunks();\n\n\t\t\tsize_t offset = FeedbackPacket::Serialize(buffer);\n\n\t\t\t// Base sequence number.\n\t\t\tUtils::Byte::Set2Bytes(buffer, offset, this->baseSequenceNumber);\n\t\t\toffset += 2;\n\n\t\t\t// Packet status count.\n\t\t\tUtils::Byte::Set2Bytes(buffer, offset, this->packetStatusCount);\n\t\t\toffset += 2;\n\n\t\t\t// Reference time.\n\t\t\tUtils::Byte::Set3BytesSigned(buffer, offset, this->referenceTime);\n\t\t\toffset += 3;\n\n\t\t\t// Feedback packet count.\n\t\t\tUtils::Byte::Set1Byte(buffer, offset, this->feedbackPacketCount);\n\t\t\toffset += 1;\n\n\t\t\t// Serialize chunks.\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\toffset += chunk->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\t// Serialize deltas.\n\t\t\tfor (auto delta : this->deltas)\n\t\t\t{\n\t\t\t\tif (delta >= 0 && delta <= 255)\n\t\t\t\t{\n\t\t\t\t\tUtils::Byte::Set1Byte(buffer, offset, delta);\n\t\t\t\t\toffset += 1u;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tUtils::Byte::Set2Bytes(buffer, offset, delta);\n\t\t\t\t\toffset += 2u;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 32 bits padding.\n\t\t\tconst size_t padding = (-offset) & 3;\n\n\t\t\tfor (size_t i{ 0u }; i < padding; ++i)\n\t\t\t{\n\t\t\t\tbuffer[offset + i] = 0u;\n\t\t\t}\n\n\t\t\toffset += padding;\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::SetBase(uint16_t sequenceNumber, uint64_t timestamp)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(!this->baseSet, \"base already set\");\n\n\t\t\tthis->baseSet              = true;\n\t\t\tthis->baseSequenceNumber   = sequenceNumber;\n\t\t\tthis->referenceTime        = static_cast<int32_t>((timestamp & 0x1FFFFFC0) / 64);\n\t\t\tthis->latestSequenceNumber = sequenceNumber - 1;\n\t\t\tthis->latestTimestamp      = (timestamp >> 6) * 64; // IMPORTANT: Loose precision.\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::AddPacketResult FeedbackRtpTransportPacket::AddPacket(\n\t\t  uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->baseSet, \"base not set\");\n\t\t\tMS_ASSERT(!IsFull(), \"packet is full\");\n\n\t\t\t// If the wide sequence number of the new packet is lower than the latest\n\t\t\t// seen, ignore it.\n\t\t\t// NOTE: Not very spec compliant but libwebrtc does it.\n\t\t\t// Also ignore if the sequence number matches the latest seen.\n\t\t\tif (!RTC::SeqManager<uint16_t>::IsSeqHigherThan(sequenceNumber, this->latestSequenceNumber))\n\t\t\t{\n\t\t\t\treturn AddPacketResult::SUCCESS;\n\t\t\t}\n\n\t\t\t// Check if there are too many missing packets.\n\t\t\t{\n\t\t\t\t// NOTE: We CANNOT use auto here, we must use uint16_t. Otherwise this is a bug.\n\t\t\t\t// https://github.com/versatica/mediasoup/issues/1385#issuecomment-2084982087\n\t\t\t\tconst uint16_t missingPackets = sequenceNumber - (this->latestSequenceNumber + 1);\n\n\t\t\t\tif (missingPackets > FeedbackRtpTransportPacket::maxMissingPackets)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"RTP missing packet number exceeded\");\n\n\t\t\t\t\treturn AddPacketResult::FATAL;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Deltas are represented as multiples of 250 us.\n\t\t\t// NOTE: Read it as int 64 to detect long elapsed times.\n\t\t\tconst int64_t delta64 = (timestamp - this->latestTimestamp) * 4;\n\n\t\t\tif (\n\t\t\t  delta64 > FeedbackRtpTransportPacket::maxPacketDelta ||\n\t\t\t  delta64 < -1 * static_cast<int64_t>(FeedbackRtpTransportPacket::maxPacketDelta))\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"RTP packet delta exceeded [latestTimestamp:%\" PRIu64 \", timestamp:%\" PRIu64 \"]\",\n\t\t\t\t  this->latestTimestamp,\n\t\t\t\t  timestamp);\n\n\t\t\t\treturn AddPacketResult::FATAL;\n\t\t\t}\n\n\t\t\t// Delta in 16 bits signed.\n\t\t\tauto delta = static_cast<int16_t>(delta64);\n\n\t\t\t// Check whether another chunks and corresponding delta infos could be\n\t\t\t// added.\n\t\t\t{\n\t\t\t\t// Fixed packet size.\n\t\t\t\tsize_t size = FeedbackRtpPacket::GetSize();\n\n\t\t\t\tsize += FeedbackRtpTransportPacket::fixedHeaderSize;\n\t\t\t\tsize += this->deltasAndChunksSize;\n\n\t\t\t\t// Maximum size needed for another chunk and its delta infos.\n\t\t\t\tsize += 2u;\n\t\t\t\tsize += 2u;\n\n\t\t\t\t// 32 bits padding.\n\t\t\t\tsize += (-size) & 3;\n\n\t\t\t\tif (size > maxRtcpPacketLen)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"maximum packet size exceeded\");\n\n\t\t\t\t\treturn AddPacketResult::MAX_SIZE_EXCEEDED;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fill a chunk.\n\t\t\tFillChunk(this->latestSequenceNumber, sequenceNumber, delta);\n\n\t\t\t// Update latest seen sequence number and timestamp.\n\t\t\tthis->latestSequenceNumber = sequenceNumber;\n\t\t\tthis->latestTimestamp      = timestamp;\n\n\t\t\treturn AddPacketResult::SUCCESS;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::Finish()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAddPendingChunks();\n\t\t}\n\n\t\tstd::vector<struct FeedbackRtpTransportPacket::PacketResult> FeedbackRtpTransportPacket::GetPacketResults() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<struct PacketResult> packetResults;\n\n\t\t\tuint16_t currentSequenceNumber = this->baseSequenceNumber - 1;\n\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tchunk->FillResults(packetResults, currentSequenceNumber);\n\t\t\t}\n\n\t\t\tsize_t deltaIdx{ 0u };\n\t\t\t// NOLINTNEXTLINE(bugprone-misplaced-widening-cast)\n\t\t\tauto currentReceivedAtMs = static_cast<int64_t>(this->referenceTime * 64);\n\n\t\t\tfor (size_t idx{ 0u }; idx < packetResults.size(); ++idx)\n\t\t\t{\n\t\t\t\tauto& packetResult = packetResults[idx];\n\n\t\t\t\tif (!packetResult.received)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tcurrentReceivedAtMs += this->deltas.at(deltaIdx) / 4;\n\t\t\t\tpacketResult.delta        = this->deltas.at(deltaIdx);\n\t\t\t\tpacketResult.receivedAtMs = currentReceivedAtMs;\n\t\t\t\tdeltaIdx++;\n\t\t\t}\n\n\t\t\treturn packetResults;\n\t\t}\n\n\t\tuint8_t FeedbackRtpTransportPacket::GetPacketFractionLost() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t expected = this->packetStatusCount;\n\t\t\tuint16_t lost{ 0u };\n\n\t\t\tif (expected == 0u)\n\t\t\t{\n\t\t\t\treturn 0u;\n\t\t\t}\n\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tlost += chunk->GetCount() - chunk->GetReceivedStatusCount();\n\t\t\t}\n\n\t\t\t// NOTE: If lost equals expected, the math below would produce 256, which\n\t\t\t// becomes 0 in uint8_t.\n\t\t\tif (lost == expected)\n\t\t\t{\n\t\t\t\treturn 255u;\n\t\t\t}\n\n\t\t\treturn (lost << 8) / expected;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::FillChunk(\n\t\t  uint16_t previousSequenceNumber, uint16_t sequenceNumber, int16_t delta)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto missingPackets = static_cast<uint16_t>(sequenceNumber - (previousSequenceNumber + 1));\n\n\t\t\tif (missingPackets > 0)\n\t\t\t{\n\t\t\t\t// Create a long run chunk before processing this packet, if needed.\n\t\t\t\tif (this->context.statuses.size() >= 7 && this->context.allSameStatus)\n\t\t\t\t{\n\t\t\t\t\tCreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());\n\n\t\t\t\t\tthis->context.statuses.clear();\n\t\t\t\t\tthis->context.currentStatus = Status::None;\n\t\t\t\t}\n\n\t\t\t\tthis->context.currentStatus = Status::NotReceived;\n\t\t\t\tsize_t representedPackets{ 0u };\n\n\t\t\t\t// Fill statuses vector.\n\t\t\t\tfor (uint16_t i{ 0u }; i < missingPackets && this->context.statuses.size() < 7; ++i)\n\t\t\t\t{\n\t\t\t\t\tthis->context.statuses.emplace_back(Status::NotReceived);\n\t\t\t\t\trepresentedPackets++;\n\t\t\t\t}\n\n\t\t\t\t// Create a two bit vector if needed.\n\t\t\t\tif (this->context.statuses.size() == 7)\n\t\t\t\t{\n\t\t\t\t\t// Fill a vector chunk.\n\t\t\t\t\tCreateTwoBitVectorChunk(this->context.statuses);\n\n\t\t\t\t\tthis->context.statuses.clear();\n\t\t\t\t\tthis->context.currentStatus = Status::None;\n\t\t\t\t}\n\n\t\t\t\tmissingPackets -= representedPackets;\n\n\t\t\t\t// Not all missing packets have been represented.\n\t\t\t\tif (missingPackets != 0)\n\t\t\t\t{\n\t\t\t\t\t// Fill a run length chunk with the remaining missing packets.\n\t\t\t\t\tCreateRunLengthChunk(Status::NotReceived, missingPackets);\n\n\t\t\t\t\tthis->context.statuses.clear();\n\t\t\t\t\tthis->context.currentStatus = Status::None;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tStatus status;\n\n\t\t\tif (delta >= 0 && delta <= 255)\n\t\t\t{\n\t\t\t\tstatus = Status::SmallDelta;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstatus = Status::LargeDelta;\n\t\t\t}\n\n\t\t\t// Create a long run chunk before processing this packet, if needed.\n\t\t\tif (\n\t\t\t  this->context.statuses.size() >= 7 && this->context.allSameStatus &&\n\t\t\t  status != this->context.currentStatus)\n\t\t\t{\n\t\t\t\tCreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());\n\n\t\t\t\tthis->context.statuses.clear();\n\t\t\t}\n\n\t\t\tthis->context.statuses.emplace_back(status);\n\t\t\tthis->deltas.push_back(delta);\n\t\t\tthis->deltasAndChunksSize += (status == Status::SmallDelta) ? 1u : 2u;\n\n\t\t\t// Update context info.\n\n\t\t\tif (\n\t\t\t  this->context.currentStatus == Status::None ||\n\t\t\t  (this->context.allSameStatus && this->context.currentStatus == status))\n\t\t\t{\n\t\t\t\tthis->context.allSameStatus = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->context.allSameStatus = false;\n\t\t\t}\n\n\t\t\tthis->context.currentStatus = status;\n\n\t\t\t// Not enough packet infos for creating a chunk.\n\t\t\tif (this->context.statuses.size() < 7)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// 7 packet infos with heterogeneous status, create the chunk.\n\t\t\telse if (this->context.statuses.size() == 7 && !this->context.allSameStatus)\n\t\t\t{\n\t\t\t\t// Reset current status.\n\t\t\t\tthis->context.currentStatus = Status::None;\n\n\t\t\t\t// Fill a vector chunk and return.\n\t\t\t\tCreateTwoBitVectorChunk(this->context.statuses);\n\n\t\t\t\tthis->context.statuses.clear();\n\t\t\t}\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::CreateRunLengthChunk(Status status, uint16_t count)\n\t\t{\n\t\t\tauto* chunk = new RunLengthChunk(status, count);\n\n\t\t\tthis->chunks.push_back(chunk);\n\t\t\tthis->packetStatusCount += count;\n\t\t\tthis->deltasAndChunksSize += 2u;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::CreateOneBitVectorChunk(std::vector<Status>& statuses)\n\t\t{\n\t\t\tauto* chunk = new OneBitVectorChunk(statuses);\n\n\t\t\tthis->chunks.push_back(chunk);\n\t\t\tthis->packetStatusCount += static_cast<uint16_t>(statuses.size());\n\t\t\tthis->deltasAndChunksSize += 2u;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::CreateTwoBitVectorChunk(std::vector<Status>& statuses)\n\t\t{\n\t\t\tauto* chunk = new TwoBitVectorChunk(statuses);\n\n\t\t\tthis->chunks.push_back(chunk);\n\t\t\tthis->packetStatusCount += static_cast<uint16_t>(statuses.size());\n\t\t\tthis->deltasAndChunksSize += 2u;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::AddPendingChunks()\n\t\t{\n\t\t\t// No pending status packets.\n\t\t\tif (this->context.statuses.empty())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this->context.allSameStatus)\n\t\t\t{\n\t\t\t\tCreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_ASSERT(this->context.statuses.size() < 7, \"already 7 status packets present\");\n\n\t\t\t\tCreateTwoBitVectorChunk(this->context.statuses);\n\t\t\t}\n\n\t\t\tthis->context.statuses.clear();\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::Chunk* FeedbackRtpTransportPacket::Chunk::Parse(\n\t\t  const uint8_t* data, size_t len, uint16_t count)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (len < 2u)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for FeedbackRtpTransportPacket chunk, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto bytes              = Utils::Byte::Get2Bytes(data, 0);\n\t\t\tconst uint8_t chunkType = (bytes >> 15) & 0x01;\n\n\t\t\t// Run length chunk.\n\t\t\tif (chunkType == 0)\n\t\t\t{\n\t\t\t\tauto* chunk = new RunLengthChunk(bytes);\n\n\t\t\t\t// Verify that the status is a valid one.\n\t\t\t\tswitch (chunk->GetStatus())\n\t\t\t\t{\n\t\t\t\t\tcase Status::NotReceived:\n\t\t\t\t\tcase Status::SmallDelta:\n\t\t\t\t\tcase Status::LargeDelta:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn chunk;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"invalid status for a run length chunk\");\n\n\t\t\t\t\t\tdelete chunk;\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Vector chunk.\n\t\t\telse\n\t\t\t{\n\t\t\t\tconst uint8_t symbolSize = data[0] & 0x40;\n\n\t\t\t\tif (symbolSize == 0)\n\t\t\t\t{\n\t\t\t\t\treturn new OneBitVectorChunk(bytes, count);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn new TwoBitVectorChunk(bytes, count);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nullptr;\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::RunLengthChunk::RunLengthChunk(uint16_t buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->status = static_cast<Status>((buffer >> 13) & 0x03);\n\t\t\tthis->count  = buffer & 0x1FFF;\n\t\t}\n\n\t\tbool FeedbackRtpTransportPacket::RunLengthChunk::AddDeltas(\n\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// No delta to be added.\n\t\t\tif (this->status == Status::NotReceived)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse if (this->status == Status::SmallDelta)\n\t\t\t{\n\t\t\t\tif (len < this->count * 1u)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for small deltas\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tfor (size_t i{ 0 }; i < this->count; ++i)\n\t\t\t\t{\n\t\t\t\t\tauto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));\n\n\t\t\t\t\tdeltas.push_back(delta);\n\t\t\t\t\toffset += 1u;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (this->status == Status::LargeDelta)\n\t\t\t{\n\t\t\t\tif (len < this->count * 2u)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for large deltas\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tfor (size_t i{ 0 }; i < this->count; ++i)\n\t\t\t\t{\n\t\t\t\t\tauto delta = static_cast<int16_t>(Utils::Byte::Get2Bytes(data, offset));\n\n\t\t\t\t\tdeltas.push_back(delta);\n\t\t\t\t\toffset += 2u;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::RunLengthChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<RunLengthChunk>\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  status: %s\",\n\t\t\t  FeedbackRtpTransportPacket::Status2String.at(this->status).c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  count: %\" PRIu16, this->count);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</RunLengthChunk>\");\n\t\t}\n\n\t\tuint16_t FeedbackRtpTransportPacket::RunLengthChunk::GetReceivedStatusCount() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->status == Status::SmallDelta || this->status == Status::LargeDelta)\n\t\t\t{\n\t\t\t\treturn this->count;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn 0u;\n\t\t\t}\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::RunLengthChunk::FillResults(\n\t\t  std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,\n\t\t  uint16_t& currentSequenceNumber) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst bool received =\n\t\t\t  (this->status == Status::SmallDelta || this->status == Status::LargeDelta);\n\n\t\t\tfor (uint16_t count{ 1u }; count <= this->count; ++count)\n\t\t\t{\n\t\t\t\tpacketResults.emplace_back(++currentSequenceNumber, received);\n\t\t\t}\n\t\t}\n\n\t\tsize_t FeedbackRtpTransportPacket::RunLengthChunk::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint16_t bytes{ 0x0000 };\n\n\t\t\tbytes |= this->status << 13;\n\t\t\tbytes |= this->count & 0x1FFF;\n\n\t\t\tUtils::Byte::Set2Bytes(buffer, 0, bytes);\n\n\t\t\treturn 2u;\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::OneBitVectorChunk::OneBitVectorChunk(uint16_t buffer, uint16_t count)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(buffer & 0x8000, \"invalid one bit vector chunk\");\n\n\t\t\tfor (uint8_t i{ 0u }; i < 14 && count > 0; ++i, --count)\n\t\t\t{\n\t\t\t\tauto status = static_cast<Status>((buffer >> (14 - 1 - i)) & 0x01);\n\n\t\t\t\tthis->statuses.emplace_back(status);\n\t\t\t}\n\t\t}\n\n\t\tbool FeedbackRtpTransportPacket::OneBitVectorChunk::AddDeltas(\n\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tif (status == Status::NotReceived)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telse if (status == Status::SmallDelta)\n\t\t\t\t{\n\t\t\t\t\tif (len < 1u)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for small delta\");\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));\n\n\t\t\t\t\tdeltas.push_back(delta);\n\t\t\t\t\toffset += 1u;\n\t\t\t\t\tlen -= 1u;\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"invalid status for one bit vector chunk\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::OneBitVectorChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::ostringstream out;\n\n\t\t\t// Dump status slots.\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tout << \"|\" << FeedbackRtpTransportPacket::Status2String.at(status);\n\t\t\t}\n\n\t\t\t// Dump empty slots.\n\t\t\tfor (size_t i{ this->statuses.size() }; i < 14; ++i)\n\t\t\t{\n\t\t\t\tout << \"|--\";\n\t\t\t}\n\n\t\t\tout << \"|\";\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<OneBitVectorChunk>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  %s\", out.str().c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</OneBitVectorChunk>\");\n\t\t}\n\n\t\tuint16_t FeedbackRtpTransportPacket::OneBitVectorChunk::GetReceivedStatusCount() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint16_t count{ 0u };\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tif (status == Status::SmallDelta || status == Status::LargeDelta)\n\t\t\t\t{\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn count;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::OneBitVectorChunk::FillResults(\n\t\t  std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,\n\t\t  uint16_t& currentSequenceNumber) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tconst bool received = (status == Status::SmallDelta || status == Status::LargeDelta);\n\n\t\t\t\tpacketResults.emplace_back(++currentSequenceNumber, received);\n\t\t\t}\n\t\t}\n\n\t\tsize_t FeedbackRtpTransportPacket::OneBitVectorChunk::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->statuses.size() <= 14, \"packet info size must be 14 or less\");\n\n\t\t\tuint16_t bytes{ 0x8000 };\n\t\t\tuint8_t i{ 13u };\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tbytes |= status << i;\n\t\t\t\ti -= 1;\n\t\t\t}\n\n\t\t\tUtils::Byte::Set2Bytes(buffer, 0, bytes);\n\n\t\t\treturn 2u;\n\t\t}\n\n\t\tFeedbackRtpTransportPacket::TwoBitVectorChunk::TwoBitVectorChunk(uint16_t buffer, uint16_t count)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(buffer & 0xC000, \"invalid two bit vector chunk\");\n\n\t\t\tfor (uint8_t i{ 0u }; i < 7 && count > 0; ++i, --count)\n\t\t\t{\n\t\t\t\tauto status = static_cast<Status>((buffer >> 2 * (7 - 1 - i)) & 0x03);\n\n\t\t\t\tthis->statuses.emplace_back(status);\n\t\t\t}\n\t\t}\n\n\t\tbool FeedbackRtpTransportPacket::TwoBitVectorChunk::AddDeltas(\n\t\t  const uint8_t* data, size_t len, std::vector<int16_t>& deltas, size_t& offset)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tif (status == Status::NotReceived)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telse if (status == Status::SmallDelta)\n\t\t\t\t{\n\t\t\t\t\tif (len < 1u)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for small delta\");\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto delta = static_cast<int16_t>(Utils::Byte::Get1Byte(data, offset));\n\n\t\t\t\t\tdeltas.push_back(delta);\n\t\t\t\t\toffset += 1u;\n\t\t\t\t\tlen -= 1u;\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telse if (status == Status::LargeDelta)\n\t\t\t\t{\n\t\t\t\t\tif (len < 2u)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for large delta\");\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tauto delta = static_cast<int16_t>(Utils::Byte::Get2Bytes(data, offset));\n\n\t\t\t\t\tdeltas.push_back(delta);\n\t\t\t\t\toffset += 2u;\n\t\t\t\t\tlen -= 2u;\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::TwoBitVectorChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::ostringstream out;\n\n\t\t\t// Dump status slots.\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tout << \"|\" << FeedbackRtpTransportPacket::Status2String.at(status);\n\t\t\t}\n\n\t\t\t// Dump empty slots.\n\t\t\tfor (size_t i{ this->statuses.size() }; i < 7; ++i)\n\t\t\t{\n\t\t\t\tout << \"|--\";\n\t\t\t}\n\n\t\t\tout << \"|\";\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<TwoBitVectorChunk>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  %s\", out.str().c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</TwoBitVectorChunk>\");\n\t\t}\n\n\t\tuint16_t FeedbackRtpTransportPacket::TwoBitVectorChunk::GetReceivedStatusCount() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint16_t count{ 0u };\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tif (status == Status::SmallDelta || status == Status::LargeDelta)\n\t\t\t\t{\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn count;\n\t\t}\n\n\t\tvoid FeedbackRtpTransportPacket::TwoBitVectorChunk::FillResults(\n\t\t  std::vector<struct FeedbackRtpTransportPacket::PacketResult>& packetResults,\n\t\t  uint16_t& currentSequenceNumber) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tconst bool received = (status == Status::SmallDelta || status == Status::LargeDelta);\n\n\t\t\t\tpacketResults.emplace_back(++currentSequenceNumber, received);\n\t\t\t}\n\t\t}\n\n\t\tsize_t FeedbackRtpTransportPacket::TwoBitVectorChunk::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->statuses.size() <= 7, \"packet info size must be 7 or less\");\n\n\t\t\tuint16_t bytes{ 0x8000 };\n\t\t\tuint8_t i{ 12u };\n\n\t\t\tbytes |= 0x01 << 14;\n\n\t\t\tfor (auto status : this->statuses)\n\t\t\t{\n\t\t\t\tbytes |= status << i;\n\t\t\t\ti -= 2;\n\t\t\t}\n\n\t\t\tUtils::Byte::Set2Bytes(buffer, 0, bytes);\n\n\t\t\treturn 2u;\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/Packet.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::Packet\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/Bye.hpp\"\n#include \"RTC/RTCP/Feedback.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"RTC/RTCP/Sdes.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"RTC/RTCP/XR.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Namespace variables. */\n\n\t\talignas(4) uint8_t SerializationBuffer[SerializationBufferSize];\n\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst absl::flat_hash_map<Type, std::string> Packet::Type2String =\n\t\t{\n\t\t\t{ Type::SR,    \"SR\"    },\n\t\t\t{ Type::RR,    \"RR\"    },\n\t\t\t{ Type::SDES,  \"SDES\"  },\n\t\t\t{ Type::BYE,   \"BYE\"   },\n\t\t\t{ Type::APP,   \"APP\"   },\n\t\t\t{ Type::RTPFB, \"RTPFB\" },\n\t\t\t{ Type::PSFB,  \"PSFB\"  },\n\t\t\t{ Type::XR,    \"XR\"    }\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tPacket* Packet::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// First, Currently parsing and Last RTCP packets in the compound packet.\n\t\t\tPacket* first{ nullptr }; // NOLINT(misc-const-correctness)\n\t\t\tPacket* current{ nullptr };\n\t\t\tPacket* last{ nullptr };\n\n\t\t\twhile (len > 0u)\n\t\t\t{\n\t\t\t\tif (!Packet::IsRtcp(data, len))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"data is not a RTCP packet\");\n\n\t\t\t\t\treturn first;\n\t\t\t\t}\n\n\t\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\t\t\t\tconst size_t packetLen = static_cast<size_t>(ntohs(header->length) + 1) * 4;\n\n\t\t\t\tif (len < packetLen)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtcp,\n\t\t\t\t\t  \"packet length exceeds remaining data [len:%zu, \"\n\t\t\t\t\t  \"packet len:%zu]\",\n\t\t\t\t\t  len,\n\t\t\t\t\t  packetLen);\n\n\t\t\t\t\treturn first;\n\t\t\t\t}\n\n\t\t\t\tswitch (Type(header->packetType))\n\t\t\t\t{\n\t\t\t\t\tcase Type::SR:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = SenderReportPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tif (!current)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (header->count > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPacket* rr = ReceiverReportPacket::Parse(data, packetLen, current->GetSize());\n\n\t\t\t\t\t\t\tif (!rr)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcurrent->SetNext(rr);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::RR:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = ReceiverReportPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::SDES:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = SdesPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::BYE:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = ByePacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::APP:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = nullptr;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::RTPFB:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = FeedbackRtpPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::PSFB:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = FeedbackPsPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Type::XR:\n\t\t\t\t\t{\n\t\t\t\t\t\tcurrent = ExtendedReportPacket::Parse(data, packetLen);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(rtcp, \"unknown RTCP packet type [packetType:%\" PRIu8 \"]\", header->packetType);\n\n\t\t\t\t\t\tcurrent = nullptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!current)\n\t\t\t\t{\n\t\t\t\t\tstd::string packetType = TypeToString(Type(header->packetType));\n\n\t\t\t\t\tif (Type(header->packetType) == Type::PSFB)\n\t\t\t\t\t{\n\t\t\t\t\t\tpacketType +=\n\t\t\t\t\t\t  \" \" + FeedbackPsPacket::MessageTypeToString(FeedbackPs::MessageType(header->count));\n\t\t\t\t\t}\n\t\t\t\t\telse if (Type(header->packetType) == Type::RTPFB)\n\t\t\t\t\t{\n\t\t\t\t\t\tpacketType +=\n\t\t\t\t\t\t  \" \" + FeedbackRtpPacket::MessageTypeToString(FeedbackRtp::MessageType(header->count));\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_WARN_TAG(rtcp, \"error parsing %s Packet\", packetType.c_str());\n\n\t\t\t\t\treturn first;\n\t\t\t\t}\n\n\t\t\t\tdata += packetLen;\n\t\t\t\tlen -= packetLen;\n\n\t\t\t\tif (!first)\n\t\t\t\t{\n\t\t\t\t\tfirst = current;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tlast->SetNext(current);\n\t\t\t\t}\n\n\t\t\t\tlast = current->GetNext() != nullptr ? current->GetNext() : current;\n\t\t\t}\n\n\t\t\treturn first;\n\t\t}\n\n\t\tconst std::string& Packet::TypeToString(Type type)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = Packet::Type2String.find(type);\n\n\t\t\tif (it == Packet::Type2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t Packet::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->header = reinterpret_cast<CommonHeader*>(buffer);\n\n\t\t\tconst size_t length = (GetSize() / 4) - 1;\n\n\t\t\t// Fill the common header.\n\t\t\tthis->header->version    = 2;\n\t\t\tthis->header->padding    = 0;\n\t\t\tthis->header->count      = static_cast<uint8_t>(GetCount());\n\t\t\tthis->header->packetType = static_cast<uint8_t>(GetType());\n\t\t\tthis->header->length     = htons(length);\n\n\t\t\treturn CommonHeaderSize;\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/ReceiverReport.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::ReceiverReport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tReceiverReport* ReceiverReport::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<Header*>(reinterpret_cast<const Header*>(data));\n\n\t\t\t// Packet size must be >= header size.\n\t\t\tif (len < HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for receiver report, packet discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn new ReceiverReport(header);\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tvoid ReceiverReport::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ReceiverReport>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  fraction lost: %\" PRIu8, GetFractionLost());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  total lost: %\" PRIi32, GetTotalLost());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  last seq: %\" PRIu32, GetLastSeq());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  jitter: %\" PRIu32, GetJitter());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  lsr: %\" PRIu32, GetLastSenderReport());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  dlsr: %\" PRIu32, GetDelaySinceLastSenderReport());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</ReceiverReport>\");\n\t\t}\n\n\t\tsize_t ReceiverReport::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Copy the header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\t/* Static Class members */\n\n\t\tsize_t ReceiverReportPacket::maxReportsPerPacket = 31;\n\n\t\t/* Class methods. */\n\n\t\t/**\n\t\t * ReceiverReportPacket::Parse()\n\t\t * @param  data   - Points to the begining of the incoming RTCP packet.\n\t\t * @param  len    - Total length of the packet.\n\t\t * @param  offset - points to the first Receiver Report in the incoming packet.\n\t\t */\n\t\tReceiverReportPacket* ReceiverReportPacket::Parse(const uint8_t* data, size_t len, size_t offset)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\t// Ensure there is space for the common header and the SSRC of packet sender.\n\t\t\tif (len < Packet::CommonHeaderSize + 4u /* ssrc */)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for receiver report packet, packet discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tstd::unique_ptr<ReceiverReportPacket> packet(new ReceiverReportPacket(header));\n\n\t\t\tconst uint32_t ssrc =\n\t\t\t  Utils::Byte::Get4Bytes(reinterpret_cast<uint8_t*>(header), Packet::CommonHeaderSize);\n\n\t\t\tpacket->SetSsrc(ssrc);\n\n\t\t\tif (offset == 0)\n\t\t\t{\n\t\t\t\toffset = Packet::CommonHeaderSize + 4u /* ssrc */;\n\t\t\t}\n\n\t\t\tuint8_t count = header->count;\n\n\t\t\twhile ((count-- != 0u) && (len > offset))\n\t\t\t{\n\t\t\t\tReceiverReport* report = ReceiverReport::Parse(data + offset, len - offset);\n\n\t\t\t\tif (report != nullptr)\n\t\t\t\t{\n\t\t\t\t\tpacket->AddReport(report);\n\t\t\t\t\toffset += report->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn packet.release();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t ReceiverReportPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset   = 0;\n\t\t\tuint8_t* header = { nullptr };\n\n\t\t\tfor (size_t i = 0; i < this->GetCount(); i++)\n\t\t\t{\n\t\t\t\t// Create a new RR packet header for each 31 reports.\n\t\t\t\tif (i % maxReportsPerPacket == 0)\n\t\t\t\t{\n\t\t\t\t\t// Reference current common header.\n\t\t\t\t\theader = buffer + offset;\n\t\t\t\t\toffset += Packet::Serialize(buffer + offset);\n\n\t\t\t\t\t// Copy the SSRC.\n\t\t\t\t\tUtils::Byte::Set4Bytes(header, Packet::CommonHeaderSize, this->ssrc);\n\t\t\t\t\toffset += 4u;\n\t\t\t\t}\n\n\t\t\t\t// Serialize next report.\n\t\t\t\toffset += this->reports[i]->Serialize(buffer + offset);\n\n\t\t\t\t// Adjust the header count field.\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->count =\n\t\t\t\t  static_cast<uint8_t>((i % maxReportsPerPacket) + 1);\n\n\t\t\t\t// Adjust the header length field.\n\t\t\t\tsize_t length = (Packet::CommonHeaderSize + 4u /* this->ssrc */);\n\t\t\t\tlength += ReceiverReport::HeaderSize * ((i % maxReportsPerPacket) + 1);\n\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->length = htons((length / 4) - 1);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid ReceiverReportPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ReceiverReportPacket>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->ssrc);\n\t\t\tfor (auto* report : this->reports)\n\t\t\t{\n\t\t\t\treport->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</ReceiverReportPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/Sdes.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::Sdes\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/Sdes.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Item Class variables. */\n\n\t\t// clang-format off\n\t\tconst absl::flat_hash_map<SdesItem::Type, std::string> SdesItem::Type2String =\n\t\t{\n\t\t\t{ SdesItem::Type::END,   \"END\"   },\n\t\t\t{ SdesItem::Type::CNAME, \"CNAME\" },\n\t\t\t{ SdesItem::Type::NAME,  \"NAME\"  },\n\t\t\t{ SdesItem::Type::EMAIL, \"EMAIL\" },\n\t\t\t{ SdesItem::Type::PHONE, \"PHONE\" },\n\t\t\t{ SdesItem::Type::LOC,   \"LOC\"   },\n\t\t\t{ SdesItem::Type::TOOL,  \"TOOL\"  },\n\t\t\t{ SdesItem::Type::NOTE,  \"NOTE\"  },\n\t\t\t{ SdesItem::Type::PRIV,  \"PRIV\"  }\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tSdesItem* SdesItem::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<Header*>(reinterpret_cast<const Header*>(data));\n\n\t\t\t// If item type is 0, there is no need for length field (unless padding\n\t\t\t// is needed).\n\t\t\tif (len > 0 && header->type == SdesItem::Type::END)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// data size must be >= header + length value.\n\t\t\tif (len < HeaderSize || len < HeaderSize + header->length)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for SDES item, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn new SdesItem(header);\n\t\t}\n\n\t\tconst std::string& SdesItem::TypeToString(SdesItem::Type type)\n\t\t{\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = SdesItem::Type2String.find(type);\n\n\t\t\tif (it == SdesItem::Type2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tSdesItem::SdesItem(SdesItem::Type type, size_t len, const char* value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Allocate memory.\n\t\t\tthis->raw.reset(new uint8_t[2 + len]);\n\n\t\t\t// Update the header pointer.\n\t\t\tthis->header = reinterpret_cast<Header*>(this->raw.get());\n\n\t\t\tthis->header->type   = type;\n\t\t\tthis->header->length = len;\n\n\t\t\t// Copy the value into raw.\n\t\t\tstd::memcpy(this->header->value, value, this->header->length);\n\t\t}\n\n\t\tvoid SdesItem::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SdesItem>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  type: %s\", SdesItem::TypeToString(this->GetType()).c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %\" PRIu8, this->header->length);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  value: %.*s\", this->header->length, this->header->value);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SdesItem>\");\n\t\t}\n\n\t\tsize_t SdesItem::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add minimum header.\n\t\t\tstd::memcpy(buffer, this->header, 2);\n\n\t\t\t// Copy the content.\n\t\t\tstd::memcpy(buffer + 2, this->header->value, this->header->length);\n\n\t\t\treturn 2 + this->header->length;\n\t\t}\n\n\t\t/* Class methods. */\n\n\t\tSdesChunk* SdesChunk::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// data size must be > SSRC field.\n\t\t\tif (len < 4u /* ssrc */)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for SDES chunk, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tconst uint32_t ssrc = Utils::Byte::Get4Bytes(data, 0);\n\n\t\t\tstd::unique_ptr<SdesChunk> chunk(new SdesChunk(ssrc));\n\n\t\t\tsize_t offset{ 4u }; /* ssrc */\n\t\t\tsize_t chunkLength{ 4u };\n\n\t\t\twhile (len > offset)\n\t\t\t{\n\t\t\t\tauto* item = SdesItem::Parse(data + offset, len - offset);\n\n\t\t\t\tif (!item)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tchunk->AddItem(item);\n\t\t\t\tchunkLength += item->GetSize();\n\t\t\t\toffset += item->GetSize();\n\t\t\t}\n\n\t\t\t// Once all items have been parsed, there must be 1, 2, 3 or 4 null octets\n\t\t\t// (this is mandatory). The first one (AKA item type 0) means \"end of\n\t\t\t// items\", and the others maybe needed for padding the chunk to 4 bytes.\n\n\t\t\t// First ensure that there is at least one null octet.\n\t\t\tif (offset == len || (offset < len && Utils::Byte::Get1Byte(data, offset) != 0u))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"invalid SDES chunk (missing mandatory null octet), discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// So we have the mandatory null octet.\n\t\t\t++chunkLength;\n\t\t\t++offset;\n\n\t\t\t// Then check that there are 0, 1, 2 or 3 (no more) null octets that pad\n\t\t\t// the chunk to 4 bytes.\n\t\t\tauto neededAdditionalNullOctets = Utils::Byte::PadTo4Bytes(static_cast<uint16_t>(chunkLength)) -\n\t\t\t                                  static_cast<uint16_t>(chunkLength);\n\t\t\tuint16_t foundAdditionalNullOctets{ 0u };\n\n\t\t\tfor (uint16_t i{ 0u }; len > offset && i < neededAdditionalNullOctets; ++i)\n\t\t\t{\n\t\t\t\tif (Utils::Byte::Get1Byte(data, offset) != 0u)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtcp,\n\t\t\t\t\t  \"invalid SDES chunk (missing additional null octets [needed:%\" PRIu16 \", found:%\" PRIu16\n\t\t\t\t\t  \"]), discarded\",\n\t\t\t\t\t  neededAdditionalNullOctets,\n\t\t\t\t\t  foundAdditionalNullOctets);\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\t++foundAdditionalNullOctets;\n\t\t\t\t++chunkLength;\n\t\t\t\t++offset;\n\t\t\t}\n\n\t\t\tif (foundAdditionalNullOctets != neededAdditionalNullOctets)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtcp,\n\t\t\t\t  \"invalid SDES chunk (missing additional null octets [needed:%\" PRIu16 \", found:%\" PRIu16\n\t\t\t\t  \"]), discarded\",\n\t\t\t\t  neededAdditionalNullOctets,\n\t\t\t\t  foundAdditionalNullOctets);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn chunk.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t SdesChunk::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Copy the SSRC.\n\t\t\tUtils::Byte::Set4Bytes(buffer, 0, this->ssrc);\n\n\t\t\tsize_t offset{ 4u }; // ssrc.\n\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\toffset += item->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\t// Add the mandatory null octet.\n\t\t\tbuffer[offset] = 0;\n\n\t\t\t++offset;\n\n\t\t\t// 32 bits padding.\n\t\t\tconst size_t padding = (-offset) & 3;\n\n\t\t\tfor (size_t i{ 0u }; i < padding; ++i)\n\t\t\t{\n\t\t\t\tbuffer[offset + i] = 0;\n\t\t\t}\n\n\t\t\treturn offset + padding;\n\t\t}\n\n\t\tvoid SdesChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SdesChunk>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->ssrc);\n\t\t\tfor (auto* item : this->items)\n\t\t\t{\n\t\t\t\titem->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SdesChunk>\");\n\t\t}\n\n\t\t/* Static Class members */\n\n\t\tsize_t SdesPacket::maxChunksPerPacket = 31;\n\n\t\t/* Class methods. */\n\n\t\tSdesPacket* SdesPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\t\t\tstd::unique_ptr<SdesPacket> packet(new SdesPacket(header));\n\t\t\tsize_t offset = Packet::CommonHeaderSize;\n\t\t\tuint8_t count = header->count;\n\n\t\t\twhile (count-- && (len > offset))\n\t\t\t{\n\t\t\t\tauto* chunk = SdesChunk::Parse(data + offset, len - offset);\n\n\t\t\t\tif (chunk != nullptr)\n\t\t\t\t{\n\t\t\t\t\tpacket->AddChunk(chunk);\n\t\t\t\t\toffset += chunk->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (packet->GetCount() != header->count)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"RTCP count value doesn't match found number of chunks, discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t SdesPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset   = 0;\n\t\t\tsize_t length   = 0;\n\t\t\tuint8_t* header = { nullptr };\n\n\t\t\tfor (size_t i{ 0u }; i < this->GetCount(); ++i)\n\t\t\t{\n\t\t\t\t// Create a new SDES packet header for each 31 chunks.\n\t\t\t\tif (i % SdesPacket::maxChunksPerPacket == 0)\n\t\t\t\t{\n\t\t\t\t\t// Reference current common header.\n\t\t\t\t\theader = buffer + offset;\n\t\t\t\t\toffset += Packet::Serialize(buffer + offset);\n\n\t\t\t\t\tlength = Packet::CommonHeaderSize;\n\t\t\t\t}\n\n\t\t\t\t// Serialize the next chunk.\n\t\t\t\tauto chunkSize = chunks[i]->Serialize(buffer + offset);\n\n\t\t\t\toffset += chunkSize;\n\t\t\t\tlength += chunkSize;\n\n\t\t\t\t// Adjust the header count field.\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->count =\n\t\t\t\t  static_cast<uint8_t>((i % SdesPacket::maxChunksPerPacket) + 1);\n\n\t\t\t\t// Adjust the header length field.\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->length = htons((length / 4) - 1);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid SdesPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SdesPacket>\");\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tchunk->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SdesPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/SenderReport.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::SenderReport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tSenderReport* SenderReport::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<Header*>(reinterpret_cast<const Header*>(data));\n\n\t\t\t// Packet size must be >= header size.\n\t\t\tif (len < HeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for sender report, packet discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn new SenderReport(header);\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tvoid SenderReport::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SenderReport>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ntp sec: %\" PRIu32, GetNtpSec());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ntp frac: %\" PRIu32, GetNtpFrac());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rtp ts: %\" PRIu32, GetRtpTs());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  packet count: %\" PRIu32, GetPacketCount());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  octet count: %\" PRIu32, GetOctetCount());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SenderReport>\");\n\t\t}\n\n\t\tsize_t SenderReport::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Copy the header.\n\t\t\tstd::memcpy(buffer, this->header, HeaderSize);\n\n\t\t\treturn HeaderSize;\n\t\t}\n\n\t\t/* Class methods. */\n\n\t\tSenderReportPacket* SenderReportPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\tstd::unique_ptr<SenderReportPacket> packet(new SenderReportPacket(header));\n\t\t\tconst size_t offset = Packet::CommonHeaderSize;\n\n\t\t\tSenderReport* report = SenderReport::Parse(data + offset, len - offset);\n\n\t\t\tif (report)\n\t\t\t{\n\t\t\t\tpacket->AddReport(report);\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t SenderReportPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset{ 0 };\n\t\t\tuint8_t* header = { nullptr };\n\n\t\t\t// Serialize packets (common header + 1 report) each.\n\t\t\tfor (auto* report : this->reports)\n\t\t\t{\n\t\t\t\t// Reference current common header.\n\t\t\t\theader = buffer + offset;\n\n\t\t\t\toffset += Packet::Serialize(buffer + offset);\n\t\t\t\toffset += report->Serialize(buffer + offset);\n\n\t\t\t\t// Adjust the header count field.\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->count = 0;\n\n\t\t\t\t// Adjust the header length field.\n\t\t\t\tsize_t length = Packet::CommonHeaderSize;\n\t\t\t\tlength += SenderReport::HeaderSize;\n\n\t\t\t\treinterpret_cast<Packet::CommonHeader*>(header)->length = htons((length / 4) - 1);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid SenderReportPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SenderReportPacket>\");\n\t\t\tfor (auto* report : this->reports)\n\t\t\t{\n\t\t\t\treport->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SenderReportPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/XR.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::XR\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tExtendedReportBlock* ExtendedReportBlock::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\t// Ensure there is space for the common header and the SSRC of packet sender.\n\t\t\tif (len < Packet::CommonHeaderSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for a extended report block, report discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tswitch (ExtendedReportBlock::Type(header->blockType))\n\t\t\t{\n\t\t\t\tcase RTC::RTCP::ExtendedReportBlock::Type::RRT:\n\t\t\t\t{\n\t\t\t\t\treturn ReceiverReferenceTime::Parse(data, len);\n\t\t\t\t}\n\n\t\t\t\tcase RTC::RTCP::ExtendedReportBlock::Type::DLRR:\n\t\t\t\t{\n\t\t\t\t\treturn DelaySinceLastRr::Parse(data, len);\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(rtcp, \"unknown RTCP XR block type [blockType:%\" PRIu8 \"]\", header->blockType);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\t/* Class methods. */\n\n\t\tExtendedReportPacket* ExtendedReportPacket::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<CommonHeader*>(reinterpret_cast<const CommonHeader*>(data));\n\n\t\t\t// Ensure there is space for the common header and the SSRC of packet sender.\n\t\t\tif (len < Packet::CommonHeaderSize + 4u)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for a extended report packet, packet discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tstd::unique_ptr<ExtendedReportPacket> packet(new ExtendedReportPacket(header));\n\n\t\t\tconst uint32_t ssrc =\n\t\t\t  Utils::Byte::Get4Bytes(reinterpret_cast<uint8_t*>(header), Packet::CommonHeaderSize);\n\n\t\t\tpacket->SetSsrc(ssrc);\n\n\t\t\tauto offset = Packet::CommonHeaderSize + 4u /* ssrc */;\n\n\t\t\twhile (len > offset)\n\t\t\t{\n\t\t\t\tExtendedReportBlock* report = ExtendedReportBlock::Parse(data + offset, len - offset);\n\n\t\t\t\tif (report)\n\t\t\t\t{\n\t\t\t\t\tpacket->AddReport(report);\n\t\t\t\t\toffset += report->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn packet.release();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packet.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t ExtendedReportPacket::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t offset = Packet::Serialize(buffer);\n\n\t\t\t// Copy the SSRC.\n\t\t\tUtils::Byte::Set4Bytes(buffer, Packet::CommonHeaderSize, this->ssrc);\n\t\t\toffset += 4u /*ssrc*/;\n\n\t\t\t// Serialize reports.\n\t\t\tfor (auto* report : this->reports)\n\t\t\t{\n\t\t\t\toffset += report->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid ExtendedReportPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ExtendedReportPacket>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, this->ssrc);\n\t\t\tfor (auto* report : this->reports)\n\t\t\t{\n\t\t\t\treport->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</ExtendedReportPacket>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/XrDelaySinceLastRr.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::XrDelaySinceLastRr\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tDelaySinceLastRr::SsrcInfo* DelaySinceLastRr::SsrcInfo::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Ensure there is space for the common header and the SSRC of packet sender.\n\t\t\tif (len < BodySize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for a extended DSLR sub-block, sub-block discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Get the header.\n\t\t\tauto* body = const_cast<SsrcInfo::Body*>(reinterpret_cast<const SsrcInfo::Body*>(data));\n\n\t\t\tauto* ssrcInfo = new DelaySinceLastRr::SsrcInfo(body);\n\n\t\t\treturn ssrcInfo;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tvoid DelaySinceLastRr::SsrcInfo::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SsrcInfo>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  lrr: %\" PRIu32, GetLastReceiverReport());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  dlrr: %\" PRIu32, GetDelaySinceLastReceiverReport());\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SsrcInfo>\");\n\t\t}\n\n\t\tsize_t DelaySinceLastRr::SsrcInfo::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Copy the body.\n\t\t\tstd::memcpy(buffer, this->body, DelaySinceLastRr::SsrcInfo::BodySize);\n\n\t\t\treturn DelaySinceLastRr::SsrcInfo::BodySize;\n\t\t}\n\n\t\t/* Class methods. */\n\n\t\tDelaySinceLastRr* DelaySinceLastRr::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* header = const_cast<ExtendedReportBlock::CommonHeader*>(\n\t\t\t  reinterpret_cast<const ExtendedReportBlock::CommonHeader*>(data));\n\t\t\tstd::unique_ptr<DelaySinceLastRr> report(new DelaySinceLastRr(header));\n\t\t\tsize_t offset{ ExtendedReportBlock::CommonHeaderSize };\n\t\t\tuint16_t reportLength = ntohs(header->length) * 4;\n\n\t\t\twhile (len > offset && reportLength >= DelaySinceLastRr::SsrcInfo::BodySize)\n\t\t\t{\n\t\t\t\tDelaySinceLastRr::SsrcInfo* ssrcInfo =\n\t\t\t\t  DelaySinceLastRr::SsrcInfo::Parse(data + offset, len - offset);\n\n\t\t\t\tif (ssrcInfo)\n\t\t\t\t{\n\t\t\t\t\treport->AddSsrcInfo(ssrcInfo);\n\t\t\t\t\toffset += ssrcInfo->GetSize();\n\t\t\t\t\treportLength -= ssrcInfo->GetSize();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\treturn report.release();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn report.release();\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tsize_t DelaySinceLastRr::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t length = static_cast<uint16_t>((SsrcInfo::BodySize * this->ssrcInfos.size() / 4));\n\n\t\t\t// Fill the common header.\n\t\t\tthis->header->blockType = static_cast<uint8_t>(this->type);\n\t\t\tthis->header->reserved  = 0;\n\t\t\tthis->header->length    = htons(length);\n\n\t\t\tstd::memcpy(buffer, this->header, ExtendedReportBlock::CommonHeaderSize);\n\n\t\t\tsize_t offset{ ExtendedReportBlock::CommonHeaderSize };\n\n\t\t\t// Serialize sub-blocks.\n\t\t\tfor (auto* ssrcInfo : this->ssrcInfos)\n\t\t\t{\n\t\t\t\toffset += ssrcInfo->Serialize(buffer + offset);\n\t\t\t}\n\n\t\t\treturn offset;\n\t\t}\n\n\t\tvoid DelaySinceLastRr::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<DelaySinceLastRr>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  block type: %\" PRIu8, (uint8_t)this->type);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  reserved: 0\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  length: %\" PRIu16,\n\t\t\t  static_cast<uint16_t>((SsrcInfo::BodySize * this->ssrcInfos.size() / 4)));\n\t\t\tfor (auto* ssrcInfo : this->ssrcInfos)\n\t\t\t{\n\t\t\t\tssrcInfo->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</DelaySinceLastRr>\");\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTCP/XrReceiverReferenceTime.cpp",
    "content": "#define MS_CLASS \"RTC::RTCP::XrReceiverReferenceTime\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy\n\nnamespace RTC\n{\n\tnamespace RTCP\n\t{\n\t\t/* Class methods. */\n\n\t\tReceiverReferenceTime* ReceiverReferenceTime::Parse(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Ensure there is space for the common header and the body.\n\t\t\tif (len < ExtendedReportBlock::CommonHeaderSize + ReceiverReferenceTime::BodySize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtcp, \"not enough space for a extended RRT block, block discarded\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Get the header.\n\t\t\tauto* header = const_cast<ExtendedReportBlock::CommonHeader*>(\n\t\t\t  reinterpret_cast<const ExtendedReportBlock::CommonHeader*>(data));\n\n\t\t\treturn new ReceiverReferenceTime(header);\n\t\t}\n\n\t\tvoid ReceiverReferenceTime::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<ReceiverReferenceTime>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  block type: %\" PRIu8, static_cast<uint8_t>(this->type));\n\t\t\tMS_DUMP_CLEAN(indentation, \"  reserved: 0\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: 2\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ntp sec: %\" PRIu32, GetNtpSec());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ntp frac: %\" PRIu32, GetNtpFrac());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</ReceiverReferenceTime>\");\n\t\t}\n\n\t\tsize_t ReceiverReferenceTime::Serialize(uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Fill the common header.\n\t\t\tthis->header->blockType = static_cast<uint8_t>(this->type);\n\t\t\tthis->header->reserved  = 0;\n\t\t\tthis->header->length    = htons(2);\n\n\t\t\tstd::memcpy(buffer, this->header, ExtendedReportBlock::CommonHeaderSize);\n\n\t\t\tsize_t offset{ ExtendedReportBlock::CommonHeaderSize };\n\n\t\t\t// Copy the body.\n\t\t\tstd::memcpy(buffer + offset, this->body, ReceiverReferenceTime::BodySize);\n\n\t\t\toffset += ReceiverReferenceTime::BodySize;\n\n\t\t\treturn offset;\n\t\t}\n\t} // namespace RTCP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/AV1.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Codecs::AV1\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/AV1.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Class methods. */\n\n\t\t\tAV1::PayloadDescriptor* AV1::Parse(\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (!dependencyDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptor = new PayloadDescriptor(dependencyDescriptor);\n\n\t\t\t\treturn payloadDescriptor;\n\t\t\t}\n\n\t\t\tvoid AV1::ProcessRtpPacket(\n\t\t\t  RTP::Packet* packet,\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>& templateDependencyStructure)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tstd::unique_ptr<Codecs::DependencyDescriptor> dependencyDescriptor;\n\n\t\t\t\t// Read dependency descriptor.\n\t\t\t\tpacket->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure);\n\n\t\t\t\tPayloadDescriptor* payloadDescriptor = AV1::Parse(dependencyDescriptor);\n\n\t\t\t\tif (!payloadDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor);\n\n\t\t\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tAV1::PayloadDescriptor::PayloadDescriptor(\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\t// Read fields.\n\t\t\t\tthis->startOfFrame  = dependencyDescriptor->startOfFrame;\n\t\t\t\tthis->endOfFrame    = dependencyDescriptor->endOfFrame;\n\t\t\t\tthis->frameNumber   = dependencyDescriptor->frameNumber;\n\t\t\t\tthis->spatialLayer  = dependencyDescriptor->spatialLayer;\n\t\t\t\tthis->temporalLayer = dependencyDescriptor->temporalLayer;\n\t\t\t\tthis->isKeyFrame    = dependencyDescriptor->isKeyFrame;\n\n\t\t\t\tthis->dependencyDescriptor = std::move(dependencyDescriptor);\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<AV1::PayloadDescriptor>\");\n\t\t\t\tthis->dependencyDescriptor->Dump(indentation + 1);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</AV1::PayloadDescriptor>\");\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptor::Encode()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (!this->encoder.has_value())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"there is no encoder present\");\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tUpdateActiveDecodeTargets(\n\t\t\t\t  this->encoder->encodingData.maxSpatialLayer, this->encoder->encodingData.maxTemporalLayer);\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptor::Restore() const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\t// Nothing to do as next time this packet is sent will rewrite\n\t\t\t\t// the active decode targets mask.\n\t\t\t}\n\n\t\t\t// NOLINTNEXTLINE(readability-make-member-function-const)\n\t\t\tvoid AV1::PayloadDescriptor::UpdateActiveDecodeTargets(uint16_t spatialLayer, uint16_t temporalLayer)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->dependencyDescriptor->UpdateActiveDecodeTargets(spatialLayer, temporalLayer);\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptor::Encoder::Encode(PayloadDescriptor* payloadDescriptor) const\n\t\t\t{\n\t\t\t\tpayloadDescriptor->UpdateActiveDecodeTargets(\n\t\t\t\t  this->encodingData.maxSpatialLayer, this->encodingData.maxTemporalLayer);\n\t\t\t}\n\n\t\t\tAV1::PayloadDescriptorHandler::PayloadDescriptorHandler(AV1::PayloadDescriptor* payloadDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor.reset(payloadDescriptor);\n\t\t\t}\n\n\t\t\tbool AV1::PayloadDescriptorHandler::Process(\n\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& marker)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* context = static_cast<Codecs::AV1::EncodingContext*>(encodingContext);\n\n\t\t\t\tMS_ASSERT(context->GetTargetSpatialLayer() >= 0, \"target spatial layer cannot be -1\");\n\t\t\t\tMS_ASSERT(context->GetTargetTemporalLayer() >= 0, \"target temporal layer cannot be -1\");\n\n\t\t\t\tauto packetSpatialLayer  = GetSpatialLayer();\n\t\t\t\tauto packetTemporalLayer = GetTemporalLayer();\n\t\t\t\tauto tmpSpatialLayer     = context->GetCurrentSpatialLayer();\n\t\t\t\tauto tmpTemporalLayer    = context->GetCurrentTemporalLayer();\n\n\t\t\t\t// If packet spatial or temporal layer is higher than maximum announced\n\t\t\t\t// one, drop the packet.\n\t\t\t\tif (packetSpatialLayer >= context->GetSpatialLayers() || packetTemporalLayer >= context->GetTemporalLayers())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp, \"too high packet layers %\" PRIu8 \":%\" PRIu8, packetSpatialLayer, packetTemporalLayer);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Upgrade current spatial layer if needed.\n\t\t\t\tif (context->GetTargetSpatialLayer() > context->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tif (this->payloadDescriptor->isKeyFrame)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"upgrading tmpSpatialLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8 \":%\" PRIu8\n\t\t\t\t\t\t  \")\",\n\t\t\t\t\t\t  context->GetCurrentSpatialLayer(),\n\t\t\t\t\t\t  context->GetTargetSpatialLayer(),\n\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\ttmpSpatialLayer  = context->GetTargetSpatialLayer();\n\t\t\t\t\t\ttmpTemporalLayer = 0; // Just in case.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Downgrade current spatial layer if needed.\n\t\t\t\telse if (context->GetTargetSpatialLayer() < context->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tif (packetSpatialLayer == context->GetTargetSpatialLayer() && this->payloadDescriptor->endOfFrame)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"downgrading tmpSpatialLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t  \":%\" PRIu8 \") without keyframe (full SVC)\",\n\t\t\t\t\t\t  context->GetCurrentSpatialLayer(),\n\t\t\t\t\t\t  context->GetTargetSpatialLayer(),\n\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\ttmpSpatialLayer  = context->GetTargetSpatialLayer();\n\t\t\t\t\t\ttmpTemporalLayer = 0; // Just in case.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Filter spatial layers higher than current one.\n\t\t\t\tif (packetSpatialLayer > tmpSpatialLayer)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Upgrade current temporal layer if needed.\n\t\t\t\tif (context->GetTargetTemporalLayer() > context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tif (packetTemporalLayer > context->GetCurrentTemporalLayer())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"upgrading tmpTemporalLayer from %\" PRIu16 \" to %\" PRIu8 \" (packet:%\" PRIu8 \":%\" PRIu8\n\t\t\t\t\t\t  \")\",\n\t\t\t\t\t\t  context->GetCurrentTemporalLayer(),\n\t\t\t\t\t\t  packetTemporalLayer,\n\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\ttmpTemporalLayer = packetTemporalLayer;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Downgrade current temporal layer if needed.\n\t\t\t\telse if (context->GetTargetTemporalLayer() < context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tif (packetTemporalLayer == context->GetTargetTemporalLayer() && this->payloadDescriptor->endOfFrame)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"downgrading tmpTemporalLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t  \":%\" PRIu8 \")\",\n\t\t\t\t\t\t  context->GetCurrentTemporalLayer(),\n\t\t\t\t\t\t  context->GetTargetTemporalLayer(),\n\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\ttmpTemporalLayer = packetTemporalLayer;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Filter temporal layers higher than current one.\n\t\t\t\tif (packetTemporalLayer > tmpTemporalLayer)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Set marker bit if needed.\n\t\t\t\tif (packetSpatialLayer == tmpSpatialLayer && this->payloadDescriptor->endOfFrame)\n\t\t\t\t{\n\t\t\t\t\tmarker = true;\n\t\t\t\t}\n\n\t\t\t\t// Update current spatial layer if needed.\n\t\t\t\tif (tmpSpatialLayer != context->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentSpatialLayer(tmpSpatialLayer);\n\t\t\t\t}\n\n\t\t\t\t// Update current temporal layer if needed.\n\t\t\t\tif (tmpTemporalLayer != context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(tmpTemporalLayer);\n\t\t\t\t}\n\n\t\t\t\t// TODO: Enable once we rewrite the Dependency Descriptor header extension.\n\t\t\t\t// // Store the encoding data for retransmissions.\n\t\t\t\t// this->payloadDescriptor->CreateEncoder({\n\t\t\t\t//   static_cast<uint32_t>(context->GetCurrentSpatialLayer()),\n\t\t\t\t//   static_cast<uint32_t>(context->GetCurrentTemporalLayer())\n\t\t\t\t// });\n\t\t\t\t// this->payloadDescriptor->Encode();\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptorHandler::Encode(\n\t\t\t  RTP::Packet* /*packet*/, Codecs::PayloadDescriptor::Encoder* encoder)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* av1Encoder = static_cast<AV1::PayloadDescriptor::Encoder*>(encoder);\n\n\t\t\t\tav1Encoder->Encode(this->payloadDescriptor.get());\n\t\t\t}\n\n\t\t\tvoid AV1::PayloadDescriptorHandler::Restore(RTP::Packet* /*packet*/)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor->Restore();\n\t\t\t}\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/DependencyDescriptor.cpp",
    "content": "#define MS_CLASS \"RTC::Codecs::DependencyDescriptor\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/DependencyDescriptor.hpp\"\n#include \"Logger.hpp\"\n#include <string>\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Static members. */\n\n\t\t\t// clang-format off\n\t\t\tstd::unordered_map<DependencyDescriptor::DecodeTargetIndication, std::string> DependencyDescriptor::dtiToString =\n\t\t\t{\n\t\t\t\t{ DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, \"-\" },\n\t\t\t\t{ DependencyDescriptor::DecodeTargetIndication::DISCARDABLE, \"D\" },\n\t\t\t\t{ DependencyDescriptor::DecodeTargetIndication::SWITCH,      \"S\" },\n\t\t\t\t{ DependencyDescriptor::DecodeTargetIndication::REQUIRED,    \"R\" },\n\t\t\t};\n\t\t\t// clang-format on\n\n\t\t\t/* Class methods. */\n\n\t\t\tDependencyDescriptor* DependencyDescriptor::Parse(\n\t\t\t  const uint8_t* data,\n\t\t\t  size_t len,\n\t\t\t  DependencyDescriptor::Listener* listener,\n\t\t\t  std::unique_ptr<TemplateDependencyStructure>& templateDependencyStructure)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (len < 3)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring payload with length < 3\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tif (len > Consts::TwoBytesRtpExtensionMaxLength)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring payload with length > %\" PRIu8, Consts::TwoBytesRtpExtensionMaxLength);\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tif (templateDependencyStructure == nullptr)\n\t\t\t\t{\n\t\t\t\t\ttemplateDependencyStructure = std::make_unique<TemplateDependencyStructure>();\n\t\t\t\t}\n\n\t\t\t\tstd::unique_ptr<DependencyDescriptor> dependencyDescriptor(\n\t\t\t\t  new DependencyDescriptor(data, len, listener, templateDependencyStructure.get()));\n\n\t\t\t\tif (!dependencyDescriptor->ReadMandatoryDescriptorFields())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"failed to read mandatory fields\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tif (len > 3)\n\t\t\t\t{\n\t\t\t\t\tif (!dependencyDescriptor->ReadExtendedDescriptorFields())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"failed to read extended fields\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!dependencyDescriptor->ReadFrameDependencyDefinition())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"failed to read frame dependency definition\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\treturn dependencyDescriptor.release();\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tDependencyDescriptor::DependencyDescriptor(\n\t\t\t  const uint8_t* data,\n\t\t\t  size_t len,\n\t\t\t  DependencyDescriptor::Listener* listener,\n\t\t\t  TemplateDependencyStructure* templateDependencyStructure)\n\t\t\t  : templateDependencyStructure(templateDependencyStructure),\n\t\t\t    listener(listener),\n\t\t\t    bitStream(const_cast<uint8_t*>(data), len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\t\t\t}\n\n\t\t\tuint8_t DependencyDescriptor::GetSpatialLayer() const\n\t\t\t{\n\t\t\t\treturn this->templateDependencyStructure->templateLayers[this->templateId].spatialLayer;\n\t\t\t}\n\n\t\t\tuint8_t DependencyDescriptor::GetTemporalLayer() const\n\t\t\t{\n\t\t\t\treturn this->templateDependencyStructure->templateLayers[this->templateId].temporalLayer;\n\t\t\t}\n\n\t\t\tvoid DependencyDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<DependencyDescriptor>\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  startOfFrame: %s\", this->startOfFrame ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  endOfFrame: %s\", this->endOfFrame ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  frameDependencyTemplateId: %u\", this->frameDependencyTemplateId);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  frameNumber: %u\", this->frameNumber);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  templateId: %u\", this->templateId);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  spatialLayer: %u\", this->spatialLayer);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  temporalLayer: %u\", this->temporalLayer);\n\n\t\t\t\tif (this->isKeyFrame)\n\t\t\t\t{\n\t\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<TemplateDependencyStructure>\");\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1, \"  spatialLayers: %u\", this->templateDependencyStructure->spatialLayers);\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1, \"  temporalLayers: %u\", this->templateDependencyStructure->temporalLayers);\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t  \"  templateIdOffset: %u\",\n\t\t\t\t\t  this->templateDependencyStructure->templateIdOffset);\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t  \"  decodeTargetCount: %u\",\n\t\t\t\t\t  this->templateDependencyStructure->decodeTargetCount);\n\t\t\t\t\tMS_DUMP_CLEAN(indentation + 2, \"<TemplateLayers>\");\n\t\t\t\t\tfor (const auto& layer : this->templateDependencyStructure->templateLayers)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"<FrameDependencyTemplate>\");\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"  spatialLayerId: %u\", layer.spatialLayer);\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"  temporalLayerId: %u\", layer.temporalLayer);\n\t\t\t\t\t\tstd::string dtis;\n\t\t\t\t\t\tfor (const auto& dti : layer.decodeTargetIndications)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdtis += dtiToString[dti];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"  decodeTargetIndications: %s\", dtis.c_str());\n\t\t\t\t\t\tstd::string fdiffs;\n\t\t\t\t\t\tfor (const auto& fdiff : layer.frameDiffs)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!fdiffs.empty())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfdiffs += \",\";\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfdiffs += std::to_string(fdiff);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"  frameDiffs: %s\", fdiffs.c_str());\n\t\t\t\t\t\tstd::string fdiffChains;\n\t\t\t\t\t\tfor (const auto& fdiffChain : layer.frameDiffChains)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (!fdiffChains.empty())\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfdiffChains += \",\";\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tfdiffChains += std::to_string(fdiffChain);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"  frameDiffChains: %s\", fdiffChains.c_str());\n\t\t\t\t\t\tMS_DUMP_CLEAN(indentation + 3, \"</FrameDependencyTemplate>\");\n\t\t\t\t\t}\n\t\t\t\t\tMS_DUMP_CLEAN(indentation + 2, \"</TemplateLayers>\");\n\t\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</TemplateDependencyStructure>\");\n\t\t\t\t}\n\n\t\t\t\tif (this->activeDecodeTargetsBitmask.has_value())\n\t\t\t\t{\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t  \"activeDecodeTargetsBitmask: %s\",\n\t\t\t\t\t  std::bitset<32>(this->activeDecodeTargetsBitmask.value()).to_string().c_str());\n\t\t\t\t}\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</DependencyDescriptor>\");\n\t\t\t}\n\n\t\t\tvoid DependencyDescriptor::UpdateListener(DependencyDescriptor::Listener* listener)\n\t\t\t{\n\t\t\t\tthis->listener = listener;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadMandatoryDescriptorFields()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->bitStream.GetLeftBits() < 24)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tthis->startOfFrame              = this->bitStream.GetBit();\n\t\t\t\tthis->endOfFrame                = this->bitStream.GetBit();\n\t\t\t\tthis->frameDependencyTemplateId = this->bitStream.GetBits(6);\n\t\t\t\tthis->frameNumber               = this->bitStream.GetBits(16);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadExtendedDescriptorFields()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->bitStream.GetLeftBits() < 5)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tauto templateDependencyStructurePresentFlag = this->bitStream.GetBit();\n\t\t\t\tauto activeDecodeTargetLayersPresentFlag    = this->bitStream.GetBit();\n\n\t\t\t\t// Advance 3 positios due to non interesting fields.\n\t\t\t\tbitStream.SkipBits(3);\n\n\t\t\t\tif (templateDependencyStructurePresentFlag)\n\t\t\t\t{\n\t\t\t\t\tif (!ReadTemplateDependencyStructure())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->activeDecodeTargetsBitmask =\n\t\t\t\t\t  (1 << this->templateDependencyStructure->decodeTargetCount) - 1;\n\t\t\t\t}\n\n\t\t\t\tif (activeDecodeTargetLayersPresentFlag)\n\t\t\t\t{\n\t\t\t\t\tif (this->bitStream.GetLeftBits() < this->templateDependencyStructure->decodeTargetCount)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->activeDecodeTargetsBitmask =\n\t\t\t\t\t  this->bitStream.GetBits(this->templateDependencyStructure->decodeTargetCount);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadTemplateDependencyStructure()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->bitStream.GetLeftBits() < 11)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tthis->templateDependencyStructure->templateIdOffset  = this->bitStream.GetBits(6);\n\t\t\t\tthis->templateDependencyStructure->decodeTargetCount = this->bitStream.GetBits(5) + 1;\n\n\t\t\t\tif (!ReadTemplateLayers())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (!ReadTemplateDecodeTargetIndications())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (!ReadTemplateFrameDiffs())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (!ReadTemplateFrameDiffChains())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadTemplateLayers()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tuint8_t temporalId    = 0;\n\t\t\t\tuint8_t spatialId     = 0;\n\t\t\t\tuint32_t nextLayerIdc = 0;\n\n\t\t\t\tthis->templateDependencyStructure->templateLayers.clear();\n\n\t\t\t\t// Set the key frame flag.\n\t\t\t\tthis->isKeyFrame = true;\n\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tthis->templateDependencyStructure->templateLayers.emplace_back(spatialId, temporalId);\n\n\t\t\t\t\tif (this->bitStream.GetLeftBits() < 2)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tnextLayerIdc = this->bitStream.GetBits(2);\n\n\t\t\t\t\t// nextLayerIdc == 0, same spatialId and temporalId.\n\t\t\t\t\tif (nextLayerIdc == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\ttemporalId++;\n\t\t\t\t\t}\n\t\t\t\t\telse if (nextLayerIdc == 2)\n\t\t\t\t\t{\n\t\t\t\t\t\ttemporalId = 0;\n\t\t\t\t\t\tspatialId++;\n\t\t\t\t\t}\n\t\t\t\t} while (nextLayerIdc != 3);\n\n\t\t\t\tthis->templateDependencyStructure->spatialLayers  = spatialId;\n\t\t\t\tthis->templateDependencyStructure->temporalLayers = temporalId;\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadTemplateDecodeTargetIndications()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto templateCount = this->templateDependencyStructure->templateLayers.size();\n\n\t\t\t\tfor (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex)\n\t\t\t\t{\n\t\t\t\t\tfor (uint8_t dtIndex = 0; dtIndex < this->templateDependencyStructure->decodeTargetCount;\n\t\t\t\t\t     ++dtIndex)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->bitStream.GetLeftBits() < 2)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis->templateDependencyStructure->templateLayers[templateIndex]\n\t\t\t\t\t\t  .decodeTargetIndications.push_back(\n\t\t\t\t\t\t    static_cast<DecodeTargetIndication>(this->bitStream.GetBits(2)));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadTemplateFrameDiffs()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto templateCount = this->templateDependencyStructure->templateLayers.size();\n\n\t\t\t\tfor (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex)\n\t\t\t\t{\n\t\t\t\t\tif (this->bitStream.GetLeftBits() < 1)\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tbool followsFlag = this->bitStream.GetBit();\n\n\t\t\t\t\twhile (followsFlag)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->bitStream.GetLeftBits() < 5)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst uint8_t fdiff = this->bitStream.GetBits(4) + 1;\n\n\t\t\t\t\t\tthis->templateDependencyStructure->templateLayers[templateIndex].frameDiffs.push_back(\n\t\t\t\t\t\t  fdiff);\n\n\t\t\t\t\t\tfollowsFlag = this->bitStream.GetBit();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadTemplateFrameDiffChains()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto chainCount =\n\t\t\t\t  this->bitStream.ReadNs(this->templateDependencyStructure->decodeTargetCount + 1);\n\n\t\t\t\tif (!chainCount.has_value())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (chainCount == 0)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tfor (uint8_t dtIndex = 0; dtIndex < this->templateDependencyStructure->decodeTargetCount;\n\t\t\t\t     ++dtIndex)\n\t\t\t\t{\n\t\t\t\t\tauto chain = this->bitStream.ReadNs(chainCount.value());\n\n\t\t\t\t\tif (!chain.has_value())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->decodeTargetProtectedBy.push_back(chain.value());\n\t\t\t\t}\n\n\t\t\t\tauto templateCount = this->templateDependencyStructure->templateLayers.size();\n\n\t\t\t\tfor (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex)\n\t\t\t\t{\n\t\t\t\t\tfor (uint32_t chainIndex = 0; chainIndex < chainCount; ++chainIndex)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->bitStream.GetLeftBits() < 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis->templateDependencyStructure->templateLayers[templateIndex].frameDiffChains.push_back(\n\t\t\t\t\t\t  this->bitStream.GetBits(4));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::ReadFrameDependencyDefinition()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tconst uint8_t templateIndex = (this->frameDependencyTemplateId + 64 -\n\t\t\t\t                               this->templateDependencyStructure->templateIdOffset) %\n\t\t\t\t                              64;\n\n\t\t\t\tif (this->templateDependencyStructure->templateLayers.size() <= templateIndex)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"invalid template index %u\", templateIndex);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tthis->templateId = templateIndex;\n\n\t\t\t\t// Retrieve spatial and temporal layers.\n\t\t\t\tthis->spatialLayer =\n\t\t\t\t  this->templateDependencyStructure->templateLayers[this->templateId].spatialLayer;\n\t\t\t\tthis->temporalLayer =\n\t\t\t\t  this->templateDependencyStructure->templateLayers[this->templateId].temporalLayer;\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tconst uint8_t* DependencyDescriptor::Serialize(uint8_t& len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_ASSERT(!this->isKeyFrame, \"serialization of key frames is not supported\");\n\n\t\t\t\tthis->bitStream.Reset();\n\t\t\t\tWriteMandatoryDescriptorFields();\n\t\t\t\tWriteExtendedDescriptorFields();\n\n\t\t\t\tlen = std::ceil((this->bitStream.GetOffset() + 7) >> 3);\n\n\t\t\t\treturn this->bitStream.GetData();\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::WriteMandatoryDescriptorFields()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->bitStream.PutBit(this->startOfFrame ? 1 : 0);\n\t\t\t\tthis->bitStream.PutBit(this->endOfFrame ? 1 : 0);\n\t\t\t\tthis->bitStream.PutBits(6, this->frameDependencyTemplateId);\n\t\t\t\tthis->bitStream.PutBits(16, this->frameNumber);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::WriteExtendedDescriptorFields()\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\t// Template dependency structure present flag.\n\t\t\t\tthis->bitStream.PutBit(0);\n\t\t\t\t// Active decode targets present flag.\n\t\t\t\tthis->bitStream.PutBit(this->activeDecodeTargetsBitmask.has_value() ? 1 : 0);\n\n\t\t\t\t// Custom dtis flag.\n\t\t\t\tthis->bitStream.PutBit(0);\n\t\t\t\t// Custom fdiffs flag.\n\t\t\t\tthis->bitStream.PutBit(0);\n\t\t\t\t// Custom chains flag.\n\t\t\t\tthis->bitStream.PutBit(0);\n\n\t\t\t\t// NOTE: Write template dependency structure if ever needed.\n\n\t\t\t\t// Active Decode Targets\n\t\t\t\tif (this->activeDecodeTargetsBitmask.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->bitStream.PutBits(\n\t\t\t\t\t  this->templateDependencyStructure->decodeTargetCount,\n\t\t\t\t\t  this->activeDecodeTargetsBitmask.value());\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tbool DependencyDescriptor::UpdateActiveDecodeTargets(\n\t\t\t  uint32_t maxSpatialLayer, uint32_t maxTemporalLayer)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\t// We don't update active decode targets for key frames,\n\t\t\t\t// as by definition they enable all decode targets.\n\t\t\t\tif (this->isKeyFrame)\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnDependencyDescriptorUpdated(\n\t\t\t\t\t  this->bitStream.GetData(), this->bitStream.GetLength());\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tauto availableSpatialLayers  = this->templateDependencyStructure->spatialLayers;\n\t\t\t\tauto availableTemporalLayers = this->templateDependencyStructure->temporalLayers;\n\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  maxSpatialLayer <= availableSpatialLayers,\n\t\t\t\t  \"maxSpatialLayer must be less than or equal to availableSpatialLayers\");\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  maxTemporalLayer <= availableTemporalLayers,\n\t\t\t\t  \"maxTemporalLayer must be less than or equal to availableTemporalLayers\");\n\n\t\t\t\tuint32_t activeDecodeTargetsBitmask = 0;\n\t\t\t\tsize_t bitIndex                     = 0;\n\n\t\t\t\tfor (uint32_t spatialLayer = 0; spatialLayer <= maxSpatialLayer; ++spatialLayer)\n\t\t\t\t{\n\t\t\t\t\tfor (uint32_t temporalLayer = 0; temporalLayer <= availableTemporalLayers; ++temporalLayer)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (temporalLayer <= maxTemporalLayer)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Set a 1.\n\t\t\t\t\t\t\tactiveDecodeTargetsBitmask |= (1 << bitIndex);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Set a 0.\n\t\t\t\t\t\t\tactiveDecodeTargetsBitmask &= ~(1 << bitIndex);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbitIndex += 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->activeDecodeTargetsBitmask = activeDecodeTargetsBitmask;\n\n\t\t\t\tuint8_t len;\n\t\t\t\tconst auto* data = this->Serialize(len);\n\t\t\t\tthis->listener->OnDependencyDescriptorUpdated(data, len);\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/H264.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Codecs::H264\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/H264.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Class methods. */\n\n\t\t\tH264::PayloadDescriptor* H264::Parse(\n\t\t\t  const uint8_t* data, size_t len, Codecs::DependencyDescriptor* dependencyDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor(new PayloadDescriptor());\n\n\t\t\t\tif (dependencyDescriptor)\n\t\t\t\t{\n\t\t\t\t\t// Read fields.\n\t\t\t\t\tpayloadDescriptor->startOfFrame  = dependencyDescriptor->startOfFrame;\n\t\t\t\t\tpayloadDescriptor->endOfFrame    = dependencyDescriptor->endOfFrame;\n\t\t\t\t\tpayloadDescriptor->spatialLayer  = dependencyDescriptor->spatialLayer;\n\t\t\t\t\tpayloadDescriptor->temporalLayer = dependencyDescriptor->temporalLayer;\n\n\t\t\t\t\tpayloadDescriptor->isKeyFrame = dependencyDescriptor->isKeyFrame;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tpayloadDescriptor->isKeyFrame = IsKeyFrame(data, len);\n\t\t\t\t}\n\n\t\t\t\treturn payloadDescriptor.release();\n\t\t\t}\n\n\t\t\tbool H264::IsKeyFrame(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (len < 2)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring payload with length < 2\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst uint8_t nal = *data & 0x1F;\n\n\t\t\t\tswitch (nal)\n\t\t\t\t{\n\t\t\t\t\t// Single NAL unit packet.\n\t\t\t\t\t// IDR (instantaneous decoding picture).\n\t\t\t\t\tcase 7:\n\t\t\t\t\t{\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Aggreation packet.\n\t\t\t\t\t// STAP-A.\n\t\t\t\t\tcase 24:\n\t\t\t\t\t{\n\t\t\t\t\t\tsize_t offset{ 1 };\n\n\t\t\t\t\t\tlen -= 1;\n\n\t\t\t\t\t\t// Iterate NAL units.\n\t\t\t\t\t\twhile (len >= 3)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto naluSize        = Utils::Byte::Get2Bytes(data, offset);\n\t\t\t\t\t\t\tconst uint8_t subnal = *(data + offset + sizeof(naluSize)) & 0x1F;\n\n\t\t\t\t\t\t\tif (subnal == 7)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Check if there is room for the indicated NAL unit size.\n\t\t\t\t\t\t\tif (len < (naluSize + sizeof(naluSize)))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\toffset += naluSize + sizeof(naluSize);\n\t\t\t\t\t\t\tlen -= naluSize + sizeof(naluSize);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Aggreation packet.\n\t\t\t\t\t// FU-A, FU-B.\n\t\t\t\t\tcase 28:\n\t\t\t\t\tcase 29:\n\t\t\t\t\t{\n\t\t\t\t\t\tconst uint8_t subnal   = *(data + 1) & 0x1F;\n\t\t\t\t\t\tconst uint8_t startBit = *(data + 1) & 0x80;\n\n\t\t\t\t\t\tif (subnal == 7 && startBit == 128)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvoid H264::ProcessRtpPacket(\n\t\t\t  RTP::Packet* packet,\n\t\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>& templateDependencyStructure)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* data = packet->GetPayload();\n\t\t\t\tauto len   = packet->GetPayloadLength();\n\t\t\t\tstd::unique_ptr<Codecs::DependencyDescriptor> dependencyDescriptor;\n\n\t\t\t\t// Read dependency descriptor.\n\t\t\t\tpacket->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure);\n\n\t\t\t\tPayloadDescriptor* payloadDescriptor = H264::Parse(data, len, dependencyDescriptor.get());\n\n\t\t\t\tif (!payloadDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor);\n\n\t\t\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tvoid H264::PayloadDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<H264::PayloadDescriptor>\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  startOfFrame:%\" PRIu8 \"|endOfFrame:%\" PRIu8,\n\t\t\t\t  this->startOfFrame,\n\t\t\t\t  this->endOfFrame);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  spatialLayer:%\" PRIu8, this->spatialLayer);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  temporalLayer:%\" PRIu8, this->temporalLayer);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  isKeyFrame: %s\", this->isKeyFrame ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</H264::PayloadDescriptor>\");\n\t\t\t}\n\n\t\t\tH264::PayloadDescriptorHandler::PayloadDescriptorHandler(H264::PayloadDescriptor* payloadDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor.reset(payloadDescriptor);\n\t\t\t}\n\n\t\t\tbool H264::PayloadDescriptorHandler::Process(\n\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& /*marker*/)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* context = static_cast<Codecs::H264::EncodingContext*>(encodingContext);\n\n\t\t\t\tMS_ASSERT(context->GetTargetTemporalLayer() >= 0, \"target temporal layer cannot be -1\");\n\n\t\t\t\tif (this->payloadDescriptor->temporalLayer > context->GetTargetTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Update/fix current temporal layer.\n\t\t\t\tif (this->payloadDescriptor->temporalLayer > context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(this->payloadDescriptor->temporalLayer);\n\t\t\t\t}\n\n\t\t\t\tif (context->GetCurrentTemporalLayer() > context->GetTargetTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(context->GetTargetTemporalLayer());\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/Opus.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Codecs::Opus\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/Opus.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Class methods. */\n\n\t\t\tOpus::PayloadDescriptor* Opus::Parse(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (len < 1)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring empty payload\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor(new PayloadDescriptor());\n\n\t\t\t\tconst uint8_t byte = data[0];\n\n\t\t\t\tpayloadDescriptor->stereo = (byte >> 2) & 0x01;\n\t\t\t\tpayloadDescriptor->code   = byte & 0x03;\n\n\t\t\t\tswitch (payloadDescriptor->code)\n\t\t\t\t{\n\t\t\t\t\tcase 0:\n\t\t\t\t\tcase 1:\n\t\t\t\t\t{\n\t\t\t\t\t\t// In code 0 and 1 packets, DTX is determined by total length = 1 (TOC\n\t\t\t\t\t\t// byte only).\n\t\t\t\t\t\tif (len == 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpayloadDescriptor->isDtx = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase 2:\n\t\t\t\t\t{\n\t\t\t\t\t\t// As per spec, a 1-byte code 2 packet is always invalid.\n\t\t\t\t\t\tif (len == 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (1)\");\n\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// In code 2 packets, DTX is determined by total length = 2 (TOC byte\n\t\t\t\t\t\t// only). Per spec, the only valid 2-byte code 2 packet is one where\n\t\t\t\t\t\t// the length of both frames is zero.\n\t\t\t\t\t\tif (len == 2)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tpayloadDescriptor->isDtx = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase 3:\n\t\t\t\t\t{\n\t\t\t\t\t\t// As per spec, a 1-byte code 3 packet is always invalid.\n\t\t\t\t\t\tif (len == 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (2)\");\n\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// A code 3 packet can never be DTX.\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\n\t\t\t\treturn payloadDescriptor.release();\n\t\t\t}\n\n\t\t\tvoid Opus::ProcessRtpPacket(RTP::Packet* packet)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* data = packet->GetPayload();\n\t\t\t\tauto len   = packet->GetPayloadLength();\n\n\t\t\t\tPayloadDescriptor* payloadDescriptor = Opus::Parse(data, len);\n\n\t\t\t\tif (!payloadDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor);\n\n\t\t\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tvoid Opus::PayloadDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<Opus::PayloadDescriptor>\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  stereo: %\" PRIu8, this->stereo);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  code: %\" PRIu8, this->code);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  is dtx: %s\", this->isDtx ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</Opus::PayloadDescriptor>\");\n\t\t\t}\n\n\t\t\tOpus::PayloadDescriptorHandler::PayloadDescriptorHandler(Opus::PayloadDescriptor* payloadDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor.reset(payloadDescriptor);\n\t\t\t}\n\n\t\t\tbool Opus::PayloadDescriptorHandler::Process(\n\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& /*marker*/)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* context = static_cast<Codecs::Opus::EncodingContext*>(encodingContext);\n\n\t\t\t\treturn !(this->payloadDescriptor->isDtx && context->GetIgnoreDtx());\n\t\t\t};\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/VP8.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Codecs::VP8\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/VP8.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Class methods. */\n\n\t\t\tVP8::PayloadDescriptor* VP8::Parse(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (len < 1)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring empty payload\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor(new PayloadDescriptor());\n\n\t\t\t\tsize_t offset{ 0 };\n\t\t\t\tuint8_t byte = data[offset];\n\n\t\t\t\tpayloadDescriptor->extended       = (byte >> 7) & 0x01;\n\t\t\t\tpayloadDescriptor->nonReference   = (byte >> 5) & 0x01;\n\t\t\t\tpayloadDescriptor->start          = (byte >> 4) & 0x01;\n\t\t\t\tpayloadDescriptor->partitionIndex = byte & 0x07;\n\n\t\t\t\tif (payloadDescriptor->extended)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (2)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tbyte = data[offset];\n\n\t\t\t\t\tpayloadDescriptor->i = (byte >> 7) & 0x01;\n\t\t\t\t\tpayloadDescriptor->l = (byte >> 6) & 0x01;\n\t\t\t\t\tpayloadDescriptor->t = (byte >> 5) & 0x01;\n\t\t\t\t\tpayloadDescriptor->k = (byte >> 4) & 0x01;\n\t\t\t\t}\n\n\t\t\t\tif (payloadDescriptor->i)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (3)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tbyte = data[offset];\n\n\t\t\t\t\tif ((byte >> 7) & 0x01)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (4)\");\n\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpayloadDescriptor->hasTwoBytesPictureId = true;\n\t\t\t\t\t\tpayloadDescriptor->pictureId            = (byte & 0x7F) << 8;\n\t\t\t\t\t\tpayloadDescriptor->pictureId += data[offset];\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tpayloadDescriptor->hasOneBytePictureId = true;\n\t\t\t\t\t\tpayloadDescriptor->pictureId           = byte & 0x7F;\n\t\t\t\t\t}\n\n\t\t\t\t\tpayloadDescriptor->hasPictureId = true;\n\t\t\t\t}\n\n\t\t\t\tif (payloadDescriptor->l)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (5)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tpayloadDescriptor->hasTl0PictureIndex = true;\n\t\t\t\t\tpayloadDescriptor->tl0PictureIndex    = data[offset];\n\t\t\t\t}\n\n\t\t\t\tif (payloadDescriptor->t || payloadDescriptor->k)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (6)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tbyte = data[offset];\n\n\t\t\t\t\tpayloadDescriptor->hasTlIndex = true;\n\t\t\t\t\tpayloadDescriptor->tlIndex    = (byte >> 6) & 0x03;\n\t\t\t\t\tpayloadDescriptor->y          = (byte >> 5) & 0x01;\n\t\t\t\t\tpayloadDescriptor->keyIndex   = byte & 0x1F;\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t  // NOLINTNEXTLINE(bugprone-inc-dec-in-conditions)\n\t\t\t\t  (len >= ++offset + 1) && payloadDescriptor->start &&\n\t\t\t\t  payloadDescriptor->partitionIndex == 0 && (!(data[offset] & 0x01)) // Inverse Keyframe bit.\n\t\t\t\t)\n\t\t\t\t{\n\t\t\t\t\tpayloadDescriptor->isKeyFrame = true;\n\t\t\t\t}\n\n\t\t\t\treturn payloadDescriptor.release();\n\t\t\t}\n\n\t\t\tvoid VP8::ProcessRtpPacket(RTP::Packet* packet)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* data = packet->GetPayload();\n\t\t\t\tauto len   = packet->GetPayloadLength();\n\n\t\t\t\tPayloadDescriptor* payloadDescriptor = VP8::Parse(data, len);\n\n\t\t\t\tif (!payloadDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor);\n\n\t\t\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\n\t\t\t\t// Modify the RtpPacket payload in order to always have two byte pictureId.\n\t\t\t\tif (payloadDescriptor->hasOneBytePictureId)\n\t\t\t\t{\n\t\t\t\t\t// Shift the RTP payload one byte from the begining of the pictureId field.\n\t\t\t\t\tpacket->ShiftPayload(2, 1);\n\n\t\t\t\t\t// Set the two byte pictureId marker bit.\n\t\t\t\t\tdata[2] = 0x80;\n\n\t\t\t\t\t// Update the payloadDescriptor.\n\t\t\t\t\tpayloadDescriptor->hasOneBytePictureId  = false;\n\t\t\t\t\tpayloadDescriptor->hasTwoBytesPictureId = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tvoid VP8::PayloadDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<VP8::PayloadDescriptor>\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  i:%\" PRIu8 \"|l:%\" PRIu8 \"|t:%\" PRIu8 \"|k:%\" PRIu8,\n\t\t\t\t  this->i,\n\t\t\t\t  this->l,\n\t\t\t\t  this->t,\n\t\t\t\t  this->k);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  extended: %\" PRIu8, this->extended);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  nonReference: %\" PRIu8, this->nonReference);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  start: %\" PRIu8, this->start);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  partitionIndex: %\" PRIu8, this->partitionIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  pictureId: %\" PRIu16, this->pictureId);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  tl0PictureIndex: %\" PRIu8, this->tl0PictureIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  tlIndex: %\" PRIu8, this->tlIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  y: %\" PRIu8, this->y);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  keyIndex: %\" PRIu8, this->keyIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  isKeyFrame: %s\", this->isKeyFrame ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  hasPictureId: %s\", this->hasPictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasOneBytePictureId: %s\", this->hasOneBytePictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasTwoBytesPictureId: %s\", this->hasTwoBytesPictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasTl0PictureIndex: %s\", this->hasTl0PictureIndex ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  hasTlIndex: %s\", this->hasTlIndex ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</VP8::PayloadDescriptor>\");\n\t\t\t}\n\n\t\t\tvoid VP8::PayloadDescriptor::Encode(uint8_t* data, uint16_t pictureId, uint8_t tl0PictureIndex) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (data == nullptr)\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"data is nullptr\");\n\t\t\t\t}\n\n\t\t\t\t// Nothing to do.\n\t\t\t\tif (!this->extended)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tdata += 2;\n\n\t\t\t\tif (this->i)\n\t\t\t\t{\n\t\t\t\t\tif (this->hasTwoBytesPictureId)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint16_t netPictureId = htons(pictureId);\n\n\t\t\t\t\t\tstd::memcpy(data, &netPictureId, 2);\n\t\t\t\t\t\tdata[0] |= 0x80;\n\t\t\t\t\t\tdata += 2;\n\t\t\t\t\t}\n\t\t\t\t\telse if (this->hasOneBytePictureId)\n\t\t\t\t\t{\n\t\t\t\t\t\t*data = pictureId;\n\t\t\t\t\t\tdata++;\n\n\t\t\t\t\t\tif (pictureId > 127)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(rtp, \"casting pictureId value to one byte\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (this->l)\n\t\t\t\t{\n\t\t\t\t\t*data = tl0PictureIndex;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid VP8::PayloadDescriptor::Encode(uint8_t* data) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->encoder == std::nullopt)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis->encoder->Encode(data, this);\n\t\t\t}\n\n\t\t\tvoid VP8::PayloadDescriptor::Restore(uint8_t* data) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->hasPictureId && this->hasTl0PictureIndex)\n\t\t\t\t{\n\t\t\t\t\tEncode(data, this->pictureId, this->tl0PictureIndex);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvoid VP8::PayloadDescriptor::Encoder::Encode(\n\t\t\t  uint8_t* data, const PayloadDescriptor* payloadDescriptor) const\n\t\t\t{\n\t\t\t\tpayloadDescriptor->Encode(\n\t\t\t\t  data, this->encodingData.pictureId, this->encodingData.tl0PictureIndex);\n\t\t\t}\n\n\t\t\tVP8::PayloadDescriptorHandler::PayloadDescriptorHandler(VP8::PayloadDescriptor* payloadDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor.reset(payloadDescriptor);\n\t\t\t}\n\n\t\t\tbool VP8::PayloadDescriptorHandler::Process(\n\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& /*marker*/)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* context = static_cast<Codecs::VP8::EncodingContext*>(encodingContext);\n\n\t\t\t\tMS_ASSERT(context->GetTargetTemporalLayer() >= 0, \"target temporal layer cannot be -1\");\n\n\t\t\t\t// Check if the payload should contain temporal layer info.\n\t\t\t\tif (context->GetTemporalLayers() > 1 && !this->payloadDescriptor->hasTlIndex)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"stream is supposed to have >1 temporal layers but does not have TlIndex field\");\n\t\t\t\t}\n\n\t\t\t\t// Check whether pictureId and tl0PictureIndex sync is required.\n\t\t\t\tif (\n\t\t\t\t  context->syncRequired && this->payloadDescriptor->hasPictureId &&\n\t\t\t\t  this->payloadDescriptor->hasTl0PictureIndex)\n\t\t\t\t{\n\t\t\t\t\tcontext->pictureIdManager.Sync(this->payloadDescriptor->pictureId - 1);\n\t\t\t\t\tcontext->tl0PictureIndexManager.Sync(this->payloadDescriptor->tl0PictureIndex - 1);\n\n\t\t\t\t\tcontext->syncRequired = false;\n\t\t\t\t}\n\n\t\t\t\t// Incremental pictureId. Check the temporal layer.\n\t\t\t\tif (\n\t\t\t\t  this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTlIndex &&\n\t\t\t\t  this->payloadDescriptor->hasTl0PictureIndex &&\n\t\t\t\t  !RTC::SeqManager<uint16_t, 15>::IsSeqLowerThan(\n\t\t\t\t    this->payloadDescriptor->pictureId, context->pictureIdManager.GetMaxInput()))\n\t\t\t\t{\n\t\t\t\t\t// Drop if:\n\t\t\t\t\t// - Temporal layer is higher than target.\n\t\t\t\t\t// - Temporal layer is higher than current and sync flag is not set.\n\t\t\t\t\tif (\n\t\t\t\t\t  this->payloadDescriptor->tlIndex > context->GetTargetTemporalLayer() ||\n\t\t\t\t\t  (this->payloadDescriptor->tlIndex > context->GetCurrentTemporalLayer() &&\n\t\t\t\t\t   !this->payloadDescriptor->y))\n\t\t\t\t\t{\n\t\t\t\t\t\tcontext->pictureIdManager.Drop(this->payloadDescriptor->pictureId);\n\n\t\t\t\t\t\tif (this->payloadDescriptor->tlIndex == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontext->tl0PictureIndexManager.Drop(this->payloadDescriptor->tl0PictureIndex);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Update pictureId and tl0PictureIndex values.\n\t\t\t\tuint16_t pictureId;\n\t\t\t\tuint8_t tl0PictureIndex;\n\n\t\t\t\t// Do not send a dropped pictureId.\n\t\t\t\tif (\n\t\t\t\t  this->payloadDescriptor->hasPictureId &&\n\t\t\t\t  !context->pictureIdManager.Input(this->payloadDescriptor->pictureId, pictureId))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Do not send a dropped tl0PictureIndex.\n\t\t\t\tif (\n\t\t\t\t  this->payloadDescriptor->hasTl0PictureIndex &&\n\t\t\t\t  !context->tl0PictureIndexManager.Input(\n\t\t\t\t    this->payloadDescriptor->tl0PictureIndex, tl0PictureIndex))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Update/fix current temporal layer.\n\t\t\t\tif (this->payloadDescriptor->hasTlIndex && this->payloadDescriptor->tlIndex == context->GetTargetTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(this->payloadDescriptor->tlIndex);\n\t\t\t\t}\n\t\t\t\telse if (!this->payloadDescriptor->hasTlIndex)\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(0);\n\t\t\t\t}\n\n\t\t\t\tif (context->GetCurrentTemporalLayer() > context->GetTargetTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(context->GetTargetTemporalLayer());\n\t\t\t\t}\n\n\t\t\t\t// Do not send tlIndex higher than current one.\n\t\t\t\tif (this->payloadDescriptor->tlIndex > context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTl0PictureIndex)\n\t\t\t\t{\n\t\t\t\t\t// Store the encoding data for retransmissions.\n\t\t\t\t\tthis->payloadDescriptor->CreateEncoder({ pictureId, tl0PictureIndex });\n\t\t\t\t\tthis->payloadDescriptor->Encode(packet->GetPayload());\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t};\n\n\t\t\tvoid VP8::PayloadDescriptorHandler::Encode(\n\t\t\t  RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* vp8Encoder = static_cast<VP8::PayloadDescriptor::Encoder*>(encoder);\n\n\t\t\t\tvp8Encoder->Encode(packet->GetPayload(), this->payloadDescriptor.get());\n\t\t\t}\n\n\t\t\tvoid VP8::PayloadDescriptorHandler::Restore(RTP::Packet* packet)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTl0PictureIndex)\n\t\t\t\t{\n\t\t\t\t\tthis->payloadDescriptor->Restore(packet->GetPayload());\n\t\t\t\t}\n\t\t\t}\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Codecs/VP9.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Codecs::VP9\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Codecs/VP9.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\tnamespace Codecs\n\t\t{\n\t\t\t/* Class methods. */\n\n\t\t\tVP9::PayloadDescriptor* VP9::Parse(const uint8_t* data, size_t len)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tif (len < 1)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\"ignoring empty payload\");\n\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tstd::unique_ptr<PayloadDescriptor> payloadDescriptor(new PayloadDescriptor());\n\n\t\t\t\tsize_t offset{ 0 };\n\t\t\t\tuint8_t byte = data[offset];\n\n\t\t\t\tpayloadDescriptor->i = (byte >> 7) & 0x01;\n\t\t\t\tpayloadDescriptor->p = (byte >> 6) & 0x01;\n\t\t\t\tpayloadDescriptor->l = (byte >> 5) & 0x01;\n\t\t\t\tpayloadDescriptor->f = (byte >> 4) & 0x01;\n\t\t\t\tpayloadDescriptor->b = (byte >> 3) & 0x01;\n\t\t\t\tpayloadDescriptor->e = (byte >> 2) & 0x01;\n\t\t\t\tpayloadDescriptor->v = (byte >> 1) & 0x01;\n\n\t\t\t\tif (payloadDescriptor->i)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (1)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tbyte = data[offset];\n\n\t\t\t\t\tif (byte >> 7 & 0x01)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (2)\");\n\n\t\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpayloadDescriptor->pictureId = (byte & 0x7F) << 8;\n\t\t\t\t\t\tpayloadDescriptor->pictureId += data[offset];\n\t\t\t\t\t\tpayloadDescriptor->hasTwoBytesPictureId = true;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tpayloadDescriptor->pictureId           = byte & 0x7F;\n\t\t\t\t\t\tpayloadDescriptor->hasOneBytePictureId = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tpayloadDescriptor->hasPictureId = true;\n\t\t\t\t}\n\n\t\t\t\tif (payloadDescriptor->l)\n\t\t\t\t{\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (3)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\tbyte = data[offset];\n\n\t\t\t\t\tpayloadDescriptor->interLayerDependency = byte & 0x01;\n\t\t\t\t\tpayloadDescriptor->switchingUpPoint     = byte >> 4 & 0x01;\n\t\t\t\t\tpayloadDescriptor->slIndex              = byte >> 1 & 0x07;\n\t\t\t\t\tpayloadDescriptor->tlIndex              = byte >> 5 & 0x07;\n\t\t\t\t\tpayloadDescriptor->hasSlIndex           = true;\n\t\t\t\t\tpayloadDescriptor->hasTlIndex           = true;\n\n\t\t\t\t\tif (len < ++offset + 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_DEV(\"ignoring invalid payload (4)\");\n\n\t\t\t\t\t\treturn nullptr;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Read TL0PICIDX if flexible mode is unset.\n\t\t\t\t\tif (!payloadDescriptor->f)\n\t\t\t\t\t{\n\t\t\t\t\t\tpayloadDescriptor->tl0PictureIndex    = data[offset];\n\t\t\t\t\t\tpayloadDescriptor->hasTl0PictureIndex = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!payloadDescriptor->p && payloadDescriptor->b && payloadDescriptor->slIndex == 0)\n\t\t\t\t{\n\t\t\t\t\tpayloadDescriptor->isKeyFrame = true;\n\t\t\t\t}\n\n\t\t\t\treturn payloadDescriptor.release();\n\t\t\t}\n\n\t\t\tvoid VP9::ProcessRtpPacket(RTP::Packet* packet)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* data = packet->GetPayload();\n\t\t\t\tauto len   = packet->GetPayloadLength();\n\n\t\t\t\tPayloadDescriptor* payloadDescriptor = VP9::Parse(data, len);\n\n\t\t\t\tif (!payloadDescriptor)\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (payloadDescriptor->isKeyFrame)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"key frame [spatialLayer:%\" PRIu8 \", temporalLayer:%\" PRIu8 \"]\",\n\t\t\t\t\t  packet->GetSpatialLayer(),\n\t\t\t\t\t  packet->GetTemporalLayer());\n\t\t\t\t}\n\n\t\t\t\tauto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor);\n\n\t\t\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\t\t\t}\n\n\t\t\t/* Instance methods. */\n\n\t\t\tvoid VP9::PayloadDescriptor::Dump(int indentation) const\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"<VP9::PayloadDescriptor>\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  i:%\" PRIu8 \"|p:%\" PRIu8 \"|l:%\" PRIu8 \"|f:%\" PRIu8 \"|b:%\" PRIu8 \"|e:%\" PRIu8 \"|v:%\" PRIu8,\n\t\t\t\t  this->i,\n\t\t\t\t  this->p,\n\t\t\t\t  this->l,\n\t\t\t\t  this->f,\n\t\t\t\t  this->b,\n\t\t\t\t  this->e,\n\t\t\t\t  this->v);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  pictureId: %\" PRIu16, this->pictureId);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  slIndex: %\" PRIu8, this->slIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  tlIndex: %\" PRIu8, this->tlIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  tl0PictureIndex: %\" PRIu8, this->tl0PictureIndex);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  interLayerDependency: %\" PRIu8, this->interLayerDependency);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  switchingUpPoint: %\" PRIu8, this->switchingUpPoint);\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  isKeyFrame: %s\", this->isKeyFrame ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  hasPictureId: %s\", this->hasPictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasOneBytePictureId: %s\", this->hasOneBytePictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasTwoBytesPictureId: %s\", this->hasTwoBytesPictureId ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  hasTl0PictureIndex: %s\", this->hasTl0PictureIndex ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  hasSlIndex: %s\", this->hasSlIndex ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  hasTlIndex: %s\", this->hasTlIndex ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"</VP9::PayloadDescriptor>\");\n\t\t\t}\n\n\t\t\tVP9::PayloadDescriptorHandler::PayloadDescriptorHandler(VP9::PayloadDescriptor* payloadDescriptor)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tthis->payloadDescriptor.reset(payloadDescriptor);\n\t\t\t}\n\n\t\t\tbool VP9::PayloadDescriptorHandler::Process(\n\t\t\t  Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& marker)\n\t\t\t{\n\t\t\t\tMS_TRACE();\n\n\t\t\t\tauto* context = static_cast<Codecs::VP9::EncodingContext*>(encodingContext);\n\n\t\t\t\tMS_ASSERT(context->GetTargetSpatialLayer() >= 0, \"target spatial layer cannot be -1\");\n\t\t\t\tMS_ASSERT(context->GetTargetTemporalLayer() >= 0, \"target temporal layer cannot be -1\");\n\n\t\t\t\tauto packetSpatialLayer  = GetSpatialLayer();\n\t\t\t\tauto packetTemporalLayer = GetTemporalLayer();\n\t\t\t\tauto tmpSpatialLayer     = context->GetCurrentSpatialLayer();\n\t\t\t\tauto tmpTemporalLayer    = context->GetCurrentTemporalLayer();\n\n\t\t\t\t// If packet spatial or temporal layer is higher than maximum announced\n\t\t\t\t// one, drop the packet.\n\t\t\t\tif (packetSpatialLayer >= context->GetSpatialLayers() || packetTemporalLayer >= context->GetTemporalLayers())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp, \"too high packet layers %\" PRIu8 \":%\" PRIu8, packetSpatialLayer, packetTemporalLayer);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Check whether pictureId sync is required.\n\t\t\t\tif (context->syncRequired && this->payloadDescriptor->hasPictureId)\n\t\t\t\t{\n\t\t\t\t\tcontext->pictureIdManager.Sync(this->payloadDescriptor->pictureId - 1);\n\n\t\t\t\t\tcontext->syncRequired = false;\n\t\t\t\t}\n\n\t\t\t\tconst bool isOldPacket =\n\t\t\t\t  (this->payloadDescriptor->hasPictureId &&\n\t\t\t\t   RTC::SeqManager<uint16_t, 15>::IsSeqLowerThan(\n\t\t\t\t     this->payloadDescriptor->pictureId, context->pictureIdManager.GetMaxInput()));\n\n\t\t\t\tif (!isOldPacket)\n\t\t\t\t{\n\t\t\t\t\t// Upgrade current spatial layer if needed.\n\t\t\t\t\tif (context->GetTargetSpatialLayer() > context->GetCurrentSpatialLayer())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->payloadDescriptor->isKeyFrame)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"upgrading tmpSpatialLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t\t  \":%\" PRIu8 \")\",\n\t\t\t\t\t\t\t  context->GetCurrentSpatialLayer(),\n\t\t\t\t\t\t\t  context->GetTargetSpatialLayer(),\n\t\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\t\ttmpSpatialLayer  = context->GetTargetSpatialLayer();\n\t\t\t\t\t\t\ttmpTemporalLayer = 0; // Just in case.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Downgrade current spatial layer if needed.\n\t\t\t\t\telse if (context->GetTargetSpatialLayer() < context->GetCurrentSpatialLayer())\n\t\t\t\t\t{\n\t\t\t\t\t\t// In K-SVC we must wait for a keyframe.\n\t\t\t\t\t\tif (context->IsKSvc())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (this->payloadDescriptor->isKeyFrame)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t\t  \"downgrading tmpSpatialLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t\t\t  \":%\" PRIu8 \") after keyframe (K-SVC)\",\n\t\t\t\t\t\t\t\t  context->GetCurrentSpatialLayer(),\n\t\t\t\t\t\t\t\t  context->GetTargetSpatialLayer(),\n\t\t\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\t\t\ttmpSpatialLayer  = context->GetTargetSpatialLayer();\n\t\t\t\t\t\t\t\ttmpTemporalLayer = 0; // Just in case.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// In full SVC we do not need a keyframe.\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (packetSpatialLayer == context->GetTargetSpatialLayer() && this->payloadDescriptor->e)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t\t  \"downgrading tmpSpatialLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t\t\t  \":%\" PRIu8 \") without keyframe (full SVC)\",\n\t\t\t\t\t\t\t\t  context->GetCurrentSpatialLayer(),\n\t\t\t\t\t\t\t\t  context->GetTargetSpatialLayer(),\n\t\t\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\t\t\ttmpSpatialLayer  = context->GetTargetSpatialLayer();\n\t\t\t\t\t\t\t\ttmpTemporalLayer = 0; // Just in case.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Filter spatial layers that are either\n\t\t\t\t// * higher than current one\n\t\t\t\t// * different than the current one when KSVC is enabled and this is not a keyframe\n\t\t\t\t// (interframe p bit = 1)\n\t\t\t\tconst uint16_t spatialLayerForPictureId =\n\t\t\t\t  isOldPacket ? context->GetSpatialLayerForPictureId(this->payloadDescriptor->pictureId)\n\t\t\t\t              : tmpSpatialLayer;\n\n\t\t\t\tif (\n\t\t\t\t  packetSpatialLayer > spatialLayerForPictureId ||\n\t\t\t\t  (context->IsKSvc() && this->payloadDescriptor->p &&\n\t\t\t\t   packetSpatialLayer != spatialLayerForPictureId))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Check and handle temporal layer (unless old packet).\n\t\t\t\tif (!isOldPacket)\n\t\t\t\t{\n\t\t\t\t\t// Upgrade current temporal layer if needed.\n\t\t\t\t\tif (context->GetTargetTemporalLayer() > context->GetCurrentTemporalLayer())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t  packetTemporalLayer >= context->GetCurrentTemporalLayer() + 1 &&\n\t\t\t\t\t\t  (context->GetCurrentTemporalLayer() == -1 || this->payloadDescriptor->switchingUpPoint) &&\n\t\t\t\t\t\t  this->payloadDescriptor->b)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"upgrading tmpTemporalLayer from %\" PRIu16 \" to %\" PRIu8 \" (packet:%\" PRIu8\n\t\t\t\t\t\t\t  \":%\" PRIu8 \")\",\n\t\t\t\t\t\t\t  context->GetCurrentTemporalLayer(),\n\t\t\t\t\t\t\t  packetTemporalLayer,\n\t\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\t\ttmpTemporalLayer = packetTemporalLayer;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Downgrade current temporal layer if needed.\n\t\t\t\t\telse if (context->GetTargetTemporalLayer() < context->GetCurrentTemporalLayer())\n\t\t\t\t\t{\n\t\t\t\t\t\tif (packetTemporalLayer == context->GetTargetTemporalLayer() && this->payloadDescriptor->e)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"downgrading tmpTemporalLayer from %\" PRIu16 \" to %\" PRIu16 \" (packet:%\" PRIu8\n\t\t\t\t\t\t\t  \":%\" PRIu8 \")\",\n\t\t\t\t\t\t\t  context->GetCurrentTemporalLayer(),\n\t\t\t\t\t\t\t  context->GetTargetTemporalLayer(),\n\t\t\t\t\t\t\t  packetSpatialLayer,\n\t\t\t\t\t\t\t  packetTemporalLayer);\n\n\t\t\t\t\t\t\ttmpTemporalLayer = context->GetTargetTemporalLayer();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Filter temporal layers higher than current one.\n\t\t\t\tconst uint16_t temporalLayerForPictureId =\n\t\t\t\t  isOldPacket ? context->GetTemporalLayerForPictureId(this->payloadDescriptor->pictureId)\n\t\t\t\t              : tmpTemporalLayer;\n\n\t\t\t\tif (packetTemporalLayer > temporalLayerForPictureId)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Set marker bit if needed.\n\t\t\t\tif (packetSpatialLayer == tmpSpatialLayer && this->payloadDescriptor->e)\n\t\t\t\t{\n\t\t\t\t\tmarker = true;\n\t\t\t\t}\n\n\t\t\t\t// Update the pictureId manager.\n\t\t\t\tif (this->payloadDescriptor->hasPictureId)\n\t\t\t\t{\n\t\t\t\t\tuint16_t pictureId;\n\n\t\t\t\t\tcontext->pictureIdManager.Input(this->payloadDescriptor->pictureId, pictureId);\n\t\t\t\t}\n\n\t\t\t\t// Update current spatial layer if needed.\n\t\t\t\tif (tmpSpatialLayer != context->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentSpatialLayer(tmpSpatialLayer, this->payloadDescriptor->pictureId);\n\t\t\t\t}\n\n\t\t\t\t// Update current temporal layer if needed.\n\t\t\t\tif (tmpTemporalLayer != context->GetCurrentTemporalLayer())\n\t\t\t\t{\n\t\t\t\t\tcontext->SetCurrentTemporalLayer(tmpTemporalLayer, this->payloadDescriptor->pictureId);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t} // namespace Codecs\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/Packet.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::Packet\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/Packet.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#endif\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/Consts.hpp\"\n#include <cstring>  // std::memmove(), std::memset()\n#include <iterator> // std::ostream_iterator\n#include <sstream>  // std::ostringstream\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Class variables. */\n\n\t\tthread_local uint32_t Packet::nextMediasoupPacketId{ Utils::Crypto::GetRandomUInt<uint32_t>(\n\t\t\t0, std::numeric_limits<uint32_t>::max() / 2) };\n\n\t\t/* Class methods. */\n\n\t\tbool Packet::IsRtp(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn (\n\t\t\t  bufferLength >= Packet::FixedHeaderMinLength &&\n\t\t\t  // @see RFC 7983.\n\t\t\t  (buffer[0] > 127 && buffer[0] < 192) &&\n\t\t\t  // RTP Version must be 2.\n\t\t\t  (buffer[0] >> 6) == 2);\n\t\t}\n\n\t\tPacket* Packet::Parse(const uint8_t* buffer, size_t packetLength, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (packetLength > bufferLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"packetLength (%zu bytes) cannot be bigger than bufferLength (%zu bytes)\",\n\t\t\t\t  packetLength,\n\t\t\t\t  bufferLength);\n\t\t\t}\n\n\t\t\tif (!Packet::IsRtp(buffer, packetLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"not a RTP Packet\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* packet = new Packet(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\tpacket->SetLength(packetLength);\n\n\t\t\tif (!packet->Validate(/*storeExtensions*/ true))\n\t\t\t{\n\t\t\t\tdelete packet;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tPacket* Packet::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!Packet::IsRtp(buffer, bufferLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"not a RTP Packet\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* packet = new Packet(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\tpacket->SetLength(bufferLength);\n\n\t\t\tif (!packet->Validate(/*storeExtensions*/ true))\n\t\t\t{\n\t\t\t\tdelete packet;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tPacket* Packet::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Packet::FixedHeaderMinLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"no space for fixed header\");\n\t\t\t}\n\n\t\t\tauto* packet      = new Packet(buffer, bufferLength);\n\t\t\tauto* fixedHeader = packet->GetFixedHeaderPointer();\n\n\t\t\tfixedHeader->version        = 2;\n\t\t\tfixedHeader->padding        = 0;\n\t\t\tfixedHeader->extension      = 0;\n\t\t\tfixedHeader->csrcCount      = 0;\n\t\t\tfixedHeader->marker         = 0;\n\t\t\tfixedHeader->payloadType    = 0;\n\t\t\tfixedHeader->sequenceNumber = 0;\n\t\t\tfixedHeader->timestamp      = 0;\n\t\t\tfixedHeader->ssrc           = 0;\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum Packet length.\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tuint32_t Packet::GetNextMediasoupPacketId()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn Packet::nextMediasoupPacketId++;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tPacket::Packet(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Packet::FixedHeaderMinLength);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t// Initialize logger.\n\t\t\t// NOTE: Here we use DepLibUV directly since `this->logger` doesn't\n\t\t\t// have any purpose during tests.\n\t\t\tthis->logger.timestamp        = DepLibUV::GetTimeMs();\n\t\t\tthis->logger.recvRtpTimestamp = GetTimestamp();\n\t\t\tthis->logger.recvSeqNumber    = GetSequenceNumber();\n#endif\n\t\t}\n\n\t\tPacket::~Packet()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid Packet::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<RTP::Packet>\");\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %zu (buffer length: %zu)\", GetLength(), GetBufferLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sequence number: %\" PRIu16, GetSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  timestamp: %\" PRIu32, GetTimestamp());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  marker: %s\", HasMarker() ? \"true\" : \"false\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload type: %\" PRIu8, GetPayloadType());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssrc: %\" PRIu32, GetSsrc());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  csrcs: %s\", HasCsrcs() ? \"true\" : \"false\");\n\n\t\t\tif (HasHeaderExtension())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  header extension: id:%\" PRIu16 \", value length:%zu\",\n\t\t\t\t  GetHeaderExtensionId(),\n\t\t\t\t  GetHeaderExtensionValueLength());\n\t\t\t}\n\n\t\t\tif (HasExtensions())\n\t\t\t{\n\t\t\t\tstd::vector<std::string> extIds;\n\t\t\t\tstd::ostringstream extIdsStream;\n\n\t\t\t\tif (HasOneByteExtensions())\n\t\t\t\t{\n\t\t\t\t\tfor (const auto offset : this->oneByteExtensions)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (offset == -1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* extension = reinterpret_cast<OneByteExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\t\t\textIds.push_back(\n\t\t\t\t\t\t  \"{id:\" + std::to_string(extension->id) +\n\t\t\t\t\t\t  \", len:\" + std::to_string(extension->len + 1) + \"}\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\textIds.reserve(this->twoBytesExtensions.size());\n\n\t\t\t\t\tfor (const auto& kv : this->twoBytesExtensions)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst auto offset = kv.second;\n\n\t\t\t\t\t\tif (offset == -1)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* extension =\n\t\t\t\t\t\t  reinterpret_cast<TwoBytesExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\t\t\textIds.push_back(\n\t\t\t\t\t\t  \"{id:\" + std::to_string(extension->id) + \", len:\" + std::to_string(extension->len) +\n\t\t\t\t\t\t  \"}\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!extIds.empty())\n\t\t\t\t{\n\t\t\t\t\tstd::copy(\n\t\t\t\t\t  extIds.begin(), extIds.end() - 1, std::ostream_iterator<std::string>(extIdsStream, \", \"));\n\t\t\t\t\textIdsStream << extIds.back();\n\n\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t  indentation,\n\t\t\t\t\t  \"  RFC5285 extensions (%s): %s\",\n\t\t\t\t\t  HasOneByteExtensions() ? \"One-Byte\" : \"Two-Bytes\",\n\t\t\t\t\t  extIdsStream.str().c_str());\n\t\t\t\t}\n\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<Extensions>\");\n\n\t\t\t\t{\n\t\t\t\t\tstd::string mid;\n\n\t\t\t\t\tif (ReadMid(mid))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  mid: id:%\" PRIu8 \", value:'%s'\",\n\t\t\t\t\t\t  this->headerExtensionIds.mid,\n\t\t\t\t\t\t  mid.c_str());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tstd::string rid;\n\n\t\t\t\t\tif (ReadRid(rid))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  rid: id:%\" PRIu8 \", value:'%s'\",\n\t\t\t\t\t\t  this->headerExtensionIds.rid,\n\t\t\t\t\t\t  rid.c_str());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tstd::string rrid;\n\n\t\t\t\t\tif (ReadRid(rrid))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  rrid: id:%\" PRIu8 \", value:'%s'\",\n\t\t\t\t\t\t  this->headerExtensionIds.rrid,\n\t\t\t\t\t\t  rrid.c_str());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint32_t absSendtime;\n\n\t\t\t\t\tif (ReadAbsSendTime(absSendtime))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  absSendTime: id:%\" PRIu8 \", value:%\" PRIu32,\n\t\t\t\t\t\t  this->headerExtensionIds.absSendTime,\n\t\t\t\t\t\t  absSendtime);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint16_t wideSeqNumber{ 0 };\n\n\t\t\t\t\tif (ReadTransportWideCc01(wideSeqNumber))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  transportWideCc01: id:%\" PRIu8 \", value:%\" PRIu16,\n\t\t\t\t\t\t  this->headerExtensionIds.transportWideCc01,\n\t\t\t\t\t\t  wideSeqNumber);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint8_t volume{ 0 };\n\t\t\t\t\tbool voice{ false };\n\n\t\t\t\t\tif (ReadSsrcAudioLevel(volume, voice))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  ssrcAudioLevel: id:%\" PRIu8 \", volume:%\" PRIu8 \", voice:%s\",\n\t\t\t\t\t\t  this->headerExtensionIds.ssrcAudioLevel,\n\t\t\t\t\t\t  volume,\n\t\t\t\t\t\t  voice ? \"true\" : \"false\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint8_t extenLen;\n\t\t\t\t\tconst uint8_t* extenValue =\n\t\t\t\t\t  GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen);\n\n\t\t\t\t\tif (extenValue)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  dependencyDescriptor: id:%\" PRIu8 \", length:%\" PRIu8,\n\t\t\t\t\t\t  this->headerExtensionIds.dependencyDescriptor,\n\t\t\t\t\t\t  extenLen);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tbool camera{ false };\n\t\t\t\t\tbool flip{ false };\n\t\t\t\t\tuint16_t rotation{ 0 };\n\n\t\t\t\t\tif (ReadVideoOrientation(camera, flip, rotation))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  videoOrientation: id:%\" PRIu8 \", camera:%s, flip:%s, rotation:%\" PRIu16,\n\t\t\t\t\t\t  this->headerExtensionIds.videoOrientation,\n\t\t\t\t\t\t  camera ? \"true\" : \"false\",\n\t\t\t\t\t\t  flip ? \"true\" : \"false\",\n\t\t\t\t\t\t  rotation);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint64_t absCaptureTimestamp{ 0 };\n\t\t\t\t\tint64_t estimatedCaptureClockOffset{ 0 };\n\n\t\t\t\t\tif (ReadAbsCaptureTime(absCaptureTimestamp, estimatedCaptureClockOffset))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  absCaptureTime: id:%\" PRIu8 \", absCaptureTimestamp:%\" PRIu64\n\t\t\t\t\t\t  \", estimatedCaptureClockOffset:%\" PRId64,\n\t\t\t\t\t\t  this->headerExtensionIds.absCaptureTime,\n\t\t\t\t\t\t  absCaptureTimestamp,\n\t\t\t\t\t\t  estimatedCaptureClockOffset);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint16_t minDelay{ 0 };\n\t\t\t\t\tuint16_t maxDelay{ 0 };\n\n\t\t\t\t\tif (ReadPlayoutDelay(minDelay, maxDelay))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  playoutDelay: id:%\" PRIu8 \", minDelay:%\" PRIu16 \", maxDelay:%\" PRIu16,\n\t\t\t\t\t\t  this->headerExtensionIds.playoutDelay,\n\t\t\t\t\t\t  minDelay,\n\t\t\t\t\t\t  maxDelay);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t{\n\t\t\t\t\tuint32_t mediasoupPacketId{ 0 };\n\n\t\t\t\t\tif (ReadMediasoupPacketId(mediasoupPacketId))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t\t\t  indentation + 1,\n\t\t\t\t\t\t  \"  mediasoupPacketId: id:%\" PRIu8 \", mediasoupPacketId:%\" PRIu32,\n\t\t\t\t\t\t  this->headerExtensionIds.mediasoupPacketId,\n\t\t\t\t\t\t  mediasoupPacketId);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</Extensions>\");\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload length: %zu\", GetPayloadLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  padding length: %\" PRIu8, GetPaddingLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  padded to 4 bytes: %s\", IsPaddedTo4Bytes() ? \"yes\" : \"no\");\n\n\t\t\tif (this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"<PayloadDescriptorHandler>\");\n\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  key frame: %s\", IsKeyFrame() ? \"true\" : \"false\");\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  spatial layer: %\" PRIu8, GetSpatialLayer());\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"  temporal layer: %\" PRIu8, GetTemporalLayer());\n#ifdef MS_DUMP_RTP_PAYLOAD_DESCRIPTOR\n\t\t\t\tthis->payloadDescriptorHandler->Dump(indentation + 2);\n#endif\n\t\t\t\tMS_DUMP_CLEAN(indentation + 1, \"</PayloadDescriptorHandler>\");\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"</RTP::Packet>\");\n\t\t}\n\n\t\tPacket* Packet::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedPacket = new Packet(buffer, bufferLength);\n\n\t\t\tSerializable::CloneInto(clonedPacket);\n\n\t\t\t// Clone Extension containers.\n\t\t\tclonedPacket->oneByteExtensions  = this->oneByteExtensions;\n\t\t\tclonedPacket->twoBytesExtensions = this->twoBytesExtensions;\n\n\t\t\t// Clone Extension ids.\n\t\t\tclonedPacket->headerExtensionIds = this->headerExtensionIds;\n\n\t\t\t// Assign the payload descriptor handler.\n\t\t\tclonedPacket->payloadDescriptorHandler = this->payloadDescriptorHandler;\n\n\t\t\tif (this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\tclonedPacket->payloadDescriptorHandler->RtpPacketChanged(clonedPacket);\n\t\t\t}\n\n\t\t\treturn clonedPacket;\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpPacket::Dump> Packet::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add mid.\n\t\t\tstd::string mid;\n\n\t\t\tReadMid(mid);\n\n\t\t\t// Add rid.\n\t\t\tstd::string rid;\n\n\t\t\tReadRid(rid);\n\n\t\t\t// Add rrid.\n\t\t\tstd::string rrid;\n\n\t\t\tReadRid(rrid);\n\n\t\t\t// Add wideSequenceNumber.\n\t\t\tuint16_t wideSequenceNumber{ 0 };\n\t\t\tbool wideSequenceNumberSet{ false };\n\n\t\t\tif (ReadTransportWideCc01(wideSequenceNumber))\n\t\t\t{\n\t\t\t\twideSequenceNumberSet = true;\n\t\t\t}\n\n\t\t\treturn FBS::RtpPacket::CreateDumpDirect(\n\t\t\t  builder,\n\t\t\t  this->GetPayloadType(),\n\t\t\t  this->GetSequenceNumber(),\n\t\t\t  this->GetTimestamp(),\n\t\t\t  this->HasMarker(),\n\t\t\t  this->GetSsrc(),\n\t\t\t  this->IsKeyFrame(),\n\t\t\t  this->GetLength(),\n\t\t\t  this->GetPayloadLength(),\n\t\t\t  this->GetSpatialLayer(),\n\t\t\t  this->GetTemporalLayer(),\n\t\t\t  mid.empty() ? nullptr : mid.c_str(),\n\t\t\t  rid.empty() ? nullptr : rid.c_str(),\n\t\t\t  rrid.empty() ? nullptr : rrid.c_str(),\n\t\t\t  wideSequenceNumberSet ? flatbuffers::Optional<uint16_t>(wideSequenceNumber)\n\t\t\t                        : flatbuffers::nullopt);\n\t\t}\n\n\t\tvoid Packet::SetPayloadType(uint8_t payloadType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetFixedHeaderPointer()->payloadType = payloadType;\n\t\t}\n\n\t\tvoid Packet::SetMarker(bool marker)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetFixedHeaderPointer()->marker = marker;\n\t\t}\n\n\t\tvoid Packet::SetSequenceNumber(uint16_t seq)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetFixedHeaderPointer()->sequenceNumber = htons(seq);\n\t\t}\n\n\t\tvoid Packet::SetTimestamp(uint32_t timestamp)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetFixedHeaderPointer()->timestamp = htonl(timestamp);\n\t\t}\n\n\t\tvoid Packet::SetSsrc(uint32_t ssrc)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetFixedHeaderPointer()->ssrc = htonl(ssrc);\n\t\t}\n\n\t\tvoid Packet::RemoveHeaderExtension()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!HasHeaderExtension())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Clear One-Byte and Two-Bytes Extensions.\n\t\t\tstd::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), -1);\n\t\t\tthis->twoBytesExtensions.clear();\n\n\t\t\tconst auto headerExtensionLength = GetHeaderExtensionLength();\n\n\t\t\tauto* payload            = GetPayloadPointer();\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\t\t\tconst auto paddingLength = GetPaddingLength();\n\n\t\t\t// Update Packet length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(GetLength() - headerExtensionLength);\n\n\t\t\t// Unset the Header Extension flag.\n\t\t\tGetFixedHeaderPointer()->extension = 0;\n\n\t\t\t// Shift the payload.\n\t\t\tstd::memmove(payload - headerExtensionLength, payload, payloadLength + paddingLength);\n\t\t}\n\n\t\tvoid Packet::SetExtensions(ExtensionsType type, const std::vector<Extension>& extensions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Clear One-Byte and Two-Bytes Extensions.\n\t\t\tstd::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), -1);\n\t\t\tthis->twoBytesExtensions.clear();\n\n\t\t\t// Reset Extension ids.\n\t\t\tthis->headerExtensionIds = {};\n\n\t\t\tconst auto hadHeaderExtension                 = HasHeaderExtension();\n\t\t\tconst auto previousHeaderExtensionValueLength = GetHeaderExtensionValueLength();\n\n\t\t\t// If no explicit ExtensionType is given then select the best one based\n\t\t\t// on given Extensions.\n\t\t\tif (type == ExtensionsType::Auto)\n\t\t\t{\n\t\t\t\tuint8_t highestId{ 0 };\n\t\t\t\tuint8_t highestLen{ 0 };\n\n\t\t\t\tfor (const auto& extension : extensions)\n\t\t\t\t{\n\t\t\t\t\thighestId  = std::max(extension.id, highestId);\n\t\t\t\t\thighestLen = std::max(extension.len, highestLen);\n\t\t\t\t}\n\n\t\t\t\ttype = highestId <= 14 && highestLen > 0 && highestLen <= 16 ? ExtensionsType::OneByte\n\t\t\t\t                                                             : ExtensionsType::TwoBytes;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"using %\" PRIu8 \" byte(s) extensions [highestId:%\" PRIu8 \", highestLen:%\" PRIu8 \"]\",\n\t\t\t\t  type,\n\t\t\t\t  highestId,\n\t\t\t\t  highestLen);\n\t\t\t}\n\n\t\t\t// If One-Byte is requested and the Packet already has One-Byte Extensions,\n\t\t\t// keep the Header Extension id.\n\t\t\tif (type == ExtensionsType::OneByte && HasOneByteExtensions())\n\t\t\t{\n\t\t\t\t// Nothing to do.\n\t\t\t}\n\t\t\t// If Two-Bytes is requested and the Packet already has Two-Bytes Extensions,\n\t\t\t// keep the Header Extension id.\n\t\t\telse if (type == ExtensionsType::TwoBytes && HasTwoBytesExtensions())\n\t\t\t{\n\t\t\t\t// Nothing to do.\n\t\t\t}\n\t\t\t// Otherwise, if there is Header Extension of non matching type, modify its id.\n\t\t\telse if (hadHeaderExtension)\n\t\t\t{\n\t\t\t\tif (type == ExtensionsType::OneByte)\n\t\t\t\t{\n\t\t\t\t\tGetHeaderExtensionPointer()->id = htons(0xBEDE);\n\t\t\t\t}\n\t\t\t\telse if (type == ExtensionsType::TwoBytes)\n\t\t\t\t{\n\t\t\t\t\tGetHeaderExtensionPointer()->id = htons(0b0001000000000000);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate total length required for all Extensions (with padding if needed).\n\t\t\tsize_t extensionsLength{ 0 };\n\n\t\t\tif (type == ExtensionsType::OneByte)\n\t\t\t{\n\t\t\t\tfor (const auto& extension : extensions)\n\t\t\t\t{\n\t\t\t\t\tif (extension.id == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid Extension with id 0\");\n\t\t\t\t\t}\n\t\t\t\t\telse if (extension.id > 14)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t  \"invalid Extension with id %\" PRIu8 \" > 14 when using One-Byte Extensions\",\n\t\t\t\t\t\t  extension.id);\n\t\t\t\t\t}\n\t\t\t\t\telse if (extension.len == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t  \"invalid Extension with id %\" PRIu8 \" and length 0 when using One-Byte Extensions\",\n\t\t\t\t\t\t  extension.id);\n\t\t\t\t\t}\n\t\t\t\t\telse if (extension.len > 16)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t  \"invalid Extension with id %\" PRIu8 \" and length %\" PRIu8\n\t\t\t\t\t\t  \" when using One-Byte Extensions\",\n\t\t\t\t\t\t  extension.id,\n\t\t\t\t\t\t  extension.len);\n\t\t\t\t\t}\n\n\t\t\t\t\textensionsLength += (1 + extension.len);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (type == ExtensionsType::TwoBytes)\n\t\t\t{\n\t\t\t\tfor (const auto& extension : extensions)\n\t\t\t\t{\n\t\t\t\t\tif (extension.id == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid Extension with id 0\");\n\t\t\t\t\t}\n\n\t\t\t\t\textensionsLength += (2 + extension.len);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto paddedExtensionsLength          = Utils::Byte::PadTo4Bytes(extensionsLength);\n\t\t\tconst size_t extensionsPaddingLength = paddedExtensionsLength - extensionsLength;\n\n\t\t\t// Calculate the number of bytes to shift (may be negative if the Packet\n\t\t\t// already had Header Extension).\n\t\t\tint16_t shift{ 0 };\n\n\t\t\tif (hadHeaderExtension)\n\t\t\t{\n\t\t\t\tshift = static_cast<int16_t>(paddedExtensionsLength - previousHeaderExtensionValueLength);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tshift = 4 + static_cast<int16_t>(paddedExtensionsLength);\n\t\t\t}\n\n\t\t\tauto* payload            = GetPayloadPointer();\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\t\t\tconst auto paddingLength = GetPaddingLength();\n\n\t\t\tif (hadHeaderExtension && shift != 0)\n\t\t\t{\n\t\t\t\t// Update Packet length.\n\t\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\t\tSetLength(GetLength() + shift);\n\n\t\t\t\t// Update the Header Extension length.\n\t\t\t\tGetHeaderExtensionPointer()->len = htons(paddedExtensionsLength / 4);\n\n\t\t\t\t// Shift the payload.\n\t\t\t\tstd::memmove(payload + shift, payload, payloadLength + paddingLength);\n\t\t\t}\n\t\t\telse if (!hadHeaderExtension)\n\t\t\t{\n\t\t\t\t// Update Packet length.\n\t\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\t\tSetLength(GetLength() + shift);\n\n\t\t\t\t// Set the Header Extension flag.\n\t\t\t\tGetFixedHeaderPointer()->extension = 1;\n\n\t\t\t\tauto* headerExtension = GetHeaderExtensionPointer();\n\n\t\t\t\t// Shift the payload.\n\t\t\t\t// NOTE: We need to move payload before code below, otherwise we would\n\t\t\t\t// override written bytes later.\n\t\t\t\tstd::memmove(payload + shift, payload, payloadLength + paddingLength);\n\n\t\t\t\t// Set the Header Extension id.\n\t\t\t\tif (type == ExtensionsType::OneByte)\n\t\t\t\t{\n\t\t\t\t\theaderExtension->id = htons(0xBEDE);\n\t\t\t\t}\n\t\t\t\telse if (type == ExtensionsType::TwoBytes)\n\t\t\t\t{\n\t\t\t\t\theaderExtension->id = htons(0b0001000000000000);\n\t\t\t\t}\n\n\t\t\t\t// Set the Header Extension length.\n\t\t\t\theaderExtension->len = htons(paddedExtensionsLength / 4);\n\t\t\t}\n\n\t\t\tconst uint8_t* extensionsStart = GetHeaderExtensionValue();\n\t\t\tauto* ptr                      = const_cast<uint8_t*>(extensionsStart);\n\n\t\t\tif (type == ExtensionsType::OneByte)\n\t\t\t{\n\t\t\t\tfor (const auto& extension : extensions)\n\t\t\t\t{\n\t\t\t\t\t// Store the One-Byte Extension offset in the array.\n\t\t\t\t\t// `-1` because we have 14 elements total 0..13 and `id` is in the\n\t\t\t\t\t// range 1..14.\n\t\t\t\t\tthis->oneByteExtensions[extension.id - 1] = ptr - extensionsStart;\n\n\t\t\t\t\t*ptr = (extension.id << 4) | ((extension.len - 1) & 0x0F);\n\t\t\t\t\t++ptr;\n\t\t\t\t\tstd::memmove(ptr, extension.value, extension.len);\n\t\t\t\t\tptr += extension.len;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (type == ExtensionsType::TwoBytes)\n\t\t\t{\n\t\t\t\tfor (const auto& extension : extensions)\n\t\t\t\t{\n\t\t\t\t\t// Store the Two-Bytes Extension offset in the map.\n\t\t\t\t\tthis->twoBytesExtensions[extension.id] = ptr - extensionsStart;\n\n\t\t\t\t\t*ptr = extension.id;\n\t\t\t\t\t++ptr;\n\t\t\t\t\t*ptr = extension.len;\n\t\t\t\t\t++ptr;\n\t\t\t\t\tstd::memmove(ptr, extension.value, extension.len);\n\t\t\t\t\tptr += extension.len;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (size_t i = 0; i < extensionsPaddingLength; ++i)\n\t\t\t{\n\t\t\t\t*ptr = 0;\n\t\t\t\t++ptr;\n\t\t\t}\n\n\t\t\t// Assign Extension ids.\n\t\t\tfor (const auto& extension : extensions)\n\t\t\t{\n\t\t\t\tswitch (extension.type)\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::MID:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.mid = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.rid = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.rrid = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.absSendTime = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.transportWideCc01 = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.ssrcAudioLevel = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.dependencyDescriptor = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.videoOrientation = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.timeOffset = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.absCaptureTime = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.playoutDelay = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->headerExtensionIds.mediasoupPacketId = extension.id;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::AssignExtensionIds(RTP::HeaderExtensionIds& headerExtensionIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Reset Extension ids.\n\t\t\tthis->headerExtensionIds = headerExtensionIds;\n\t\t}\n\n\t\tbool Packet::ReadMid(std::string& mid) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.mid == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.mid, extenLen);\n\n\t\t\tif (!extenValue || extenLen == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tmid.assign(reinterpret_cast<const char*>(extenValue), static_cast<size_t>(extenLen));\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::UpdateMid(const std::string& mid)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.mid, extenLen);\n\n\t\t\tif (!extenValue)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst size_t midLen = mid.length();\n\n\t\t\t// Here we assume that there is MidRtpExtensionMaxLength available bytes,\n\t\t\t// even if now they are padding bytes.\n\t\t\tif (midLen > RTC::Consts::MidRtpExtensionMaxLength)\n\t\t\t{\n\t\t\t\tMS_ERROR(\n\t\t\t\t  \"no enough space for MID value [MidRtpExtensionMaxLength:%\" PRIu8 \", mid:'%s']\",\n\t\t\t\t  RTC::Consts::MidRtpExtensionMaxLength,\n\t\t\t\t  mid.c_str());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tstd::memcpy(extenValue, mid.c_str(), midLen);\n\n\t\t\tSetExtensionLength(this->headerExtensionIds.mid, midLen);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadRid(std::string& rid) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.rid == 0 && this->headerExtensionIds.rrid == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// First try with the RID id then with the Repaired RID id.\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.rid, extenLen);\n\n\t\t\tif (extenValue && extenLen > 0)\n\t\t\t{\n\t\t\t\trid.assign(reinterpret_cast<const char*>(extenValue), static_cast<size_t>(extenLen));\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\textenValue = GetExtensionValue(this->headerExtensionIds.rrid, extenLen);\n\n\t\t\tif (extenValue && extenLen > 0)\n\t\t\t{\n\t\t\t\trid.assign(reinterpret_cast<const char*>(extenValue), static_cast<size_t>(extenLen));\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tbool Packet::ReadAbsSendTime(uint32_t& absSendtime) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.absSendTime == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.absSendTime, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 3u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tabsSendtime = Utils::Byte::Get3Bytes(extenValue, 0);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::UpdateAbsSendTime(uint64_t ms) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.absSendTime, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 3u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tauto absSendTime = Utils::Time::TimeMsToAbsSendTime(ms);\n\n\t\t\tUtils::Byte::Set3Bytes(extenValue, 0, absSendTime);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadTransportWideCc01(uint16_t& wideSeqNumber) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.transportWideCc01 == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.transportWideCc01, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 2u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\twideSeqNumber = Utils::Byte::Get2Bytes(extenValue, 0);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::UpdateTransportWideCc01(uint16_t wideSeqNumber) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.transportWideCc01, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 2u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tUtils::Byte::Set2Bytes(extenValue, 0, wideSeqNumber);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadSsrcAudioLevel(uint8_t& volume, bool& voice) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.ssrcAudioLevel == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.ssrcAudioLevel, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 1u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tvolume = Utils::Byte::Get1Byte(extenValue, 0);\n\t\t\tvoice  = (volume & (1 << 7)) != 0;\n\t\t\tvolume &= ~(1 << 7);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadDependencyDescriptor(\n\t\t  std::unique_ptr<Codecs::DependencyDescriptor>& dependencyDescriptor,\n\t\t  std::unique_ptr<Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t\t    templateDependencyStructure) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.dependencyDescriptor == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen);\n\n\t\t\tauto* value = Codecs::DependencyDescriptor::Parse(\n\t\t\t  extenValue, extenLen, const_cast<Packet*>(this), templateDependencyStructure);\n\n\t\t\tif (!value)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tdependencyDescriptor.reset(value);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::UpdateDependencyDescriptor(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen);\n\n\t\t\tif (!extenValue)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"dependency description not found\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tstd::memcpy(extenValue, data, len);\n\n\t\t\tSetExtensionLength(this->headerExtensionIds.dependencyDescriptor, len);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadVideoOrientation(bool& camera, bool& flip, uint16_t& rotation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.videoOrientation == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.videoOrientation, extenLen);\n\n\t\t\tif (!extenValue || extenLen != 1u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst uint8_t cvoByte       = Utils::Byte::Get1Byte(extenValue, 0);\n\t\t\tconst uint8_t cameraValue   = ((cvoByte & 0b00001000) >> 3);\n\t\t\tconst uint8_t flipValue     = ((cvoByte & 0b00000100) >> 2);\n\t\t\tconst uint8_t rotationValue = (cvoByte & 0b00000011);\n\n\t\t\tcamera = cameraValue != 0;\n\t\t\tflip   = flipValue != 0;\n\n\t\t\t// Using counter clockwise values.\n\t\t\tswitch (rotationValue)\n\t\t\t{\n\t\t\t\tcase 3:\n\t\t\t\t{\n\t\t\t\t\trotation = 270;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 2:\n\t\t\t\t{\n\t\t\t\t\trotation = 180;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 1:\n\t\t\t\t{\n\t\t\t\t\trotation = 90;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\trotation = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadAbsCaptureTime(\n\t\t  uint64_t& absCaptureTimestamp, int64_t& estimatedCaptureClockOffset) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.absCaptureTime == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.absCaptureTime, extenLen);\n\n\t\t\t// Extension value can be 8 or 16 bytes depending on whether it contains\n\t\t\t// estimated capture clock offset or not.\n\t\t\t//\n\t\t\t// https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time\n\t\t\tif (!extenValue || (extenLen != 8u && extenLen != 16u))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tabsCaptureTimestamp = Utils::Byte::Get8Bytes(extenValue, 0);\n\n\t\t\tif (extenLen == 16)\n\t\t\t{\n\t\t\t\testimatedCaptureClockOffset = static_cast<int64_t>(Utils::Byte::Get8Bytes(extenValue, 8));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\testimatedCaptureClockOffset = 0;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadPlayoutDelay(uint16_t& minDelay, uint16_t& maxDelay) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.playoutDelay == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.playoutDelay, extenLen);\n\n\t\t\tif (extenLen != 3)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst uint32_t v = Utils::Byte::Get3Bytes(extenValue, 0);\n\t\t\tminDelay         = v >> 12u;\n\t\t\tmaxDelay         = v & 0xFFFu;\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ReadMediasoupPacketId(uint32_t& mediasoupPacketId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->headerExtensionIds.mediasoupPacketId == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tuint8_t extenLen;\n\t\t\tconst uint8_t* extenValue =\n\t\t\t  GetExtensionValue(this->headerExtensionIds.mediasoupPacketId, extenLen);\n\n\t\t\tif (extenLen != 4u)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tmediasoupPacketId = Utils::Byte::Get4Bytes(extenValue, 0);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid Packet::SetPayload(const uint8_t* payload, size_t payloadLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!payload && payloadLength > 0)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid payloadLength %zu without payload\", payloadLength);\n\t\t\t}\n\n\t\t\tconst auto previousLength        = GetLength();\n\t\t\tconst auto previousPayloadLength = GetPayloadLength();\n\t\t\tconst auto previousPaddingLength = GetPaddingLength();\n\t\t\tconst auto newLength =\n\t\t\t  previousLength - previousPayloadLength - previousPaddingLength + payloadLength;\n\n\t\t\t// Set the new Packet total length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(newLength);\n\n\t\t\t// Unset padding flag.\n\t\t\tGetFixedHeaderPointer()->padding = 0;\n\n\t\t\tif (payload)\n\t\t\t{\n\t\t\t\tstd::memmove(GetPayloadPointer(), payload, payloadLength);\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::SetPayloadLength(size_t payloadLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto previousLength        = GetLength();\n\t\t\tconst auto previousPayloadLength = GetPayloadLength();\n\t\t\tconst auto previousPaddingLength = GetPaddingLength();\n\t\t\tconst auto newLength =\n\t\t\t  previousLength - previousPayloadLength - previousPaddingLength + payloadLength;\n\n\t\t\t// Set the new Packet total length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(newLength);\n\n\t\t\t// Unset padding flag.\n\t\t\tGetFixedHeaderPointer()->padding = 0;\n\t\t}\n\n\t\tvoid Packet::ShiftPayload(size_t payloadOffset, int32_t delta)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (delta == 0)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto* payload            = GetPayloadPointer();\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\t\t\tconst auto absDelta =\n\t\t\t  delta < 0 ? static_cast<uint32_t>(-(int64_t)delta) : static_cast<uint32_t>(delta);\n\n\t\t\tif (payloadOffset >= payloadLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"payloadOffset (%zu) is bigger than payload length (%zu)\", payloadOffset, payloadLength);\n\t\t\t}\n\t\t\telse if (delta < 0 && absDelta > (payloadLength - payloadOffset))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"negative delta (%\" PRIi32 \") too big\", delta);\n\t\t\t}\n\n\t\t\t// Remove padding (if any).\n\t\t\tif (HasPadding())\n\t\t\t{\n\t\t\t\tSetPaddingLength(0);\n\t\t\t}\n\n\t\t\tif (delta > 0)\n\t\t\t{\n\t\t\t\t// Update Packet length.\n\t\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\t\tSetLength(GetLength() + delta);\n\n\t\t\t\tstd::memmove(\n\t\t\t\t  payload + payloadOffset + delta, payload + payloadOffset, payloadLength - payloadOffset);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Update Packet length.\n\t\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\t\tSetLength(GetLength() - absDelta);\n\n\t\t\t\tstd::memmove(\n\t\t\t\t  payload + payloadOffset,\n\t\t\t\t  payload + payloadOffset + absDelta,\n\t\t\t\t  payloadLength - payloadOffset - absDelta);\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::SetPaddingLength(uint8_t paddingLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto previousLength        = GetLength();\n\t\t\tconst auto previousPaddingLength = GetPaddingLength();\n\t\t\tconst auto newLength             = previousLength - previousPaddingLength + paddingLength;\n\n\t\t\t// Set the new Packet total length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(newLength);\n\n\t\t\tif (paddingLength > 0)\n\t\t\t{\n\t\t\t\tGetFixedHeaderPointer()->padding = 1;\n\n\t\t\t\tUtils::Byte::Set1Byte(const_cast<uint8_t*>(GetBuffer()), GetLength() - 1, paddingLength);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tGetFixedHeaderPointer()->padding = 0;\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::PadTo4Bytes()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto previousLength        = GetLength();\n\t\t\tconst auto previousPaddingLength = GetPaddingLength();\n\t\t\tconst auto newNotPaddedLength    = previousLength - previousPaddingLength;\n\t\t\tconst auto newPaddedLength       = Utils::Byte::PadTo4Bytes(newNotPaddedLength);\n\n\t\t\tif (newPaddedLength == previousLength)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the new Packet total length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(newPaddedLength);\n\n\t\t\tconst auto newPaddingLength = newPaddedLength - newNotPaddedLength;\n\n\t\t\tif (newPaddingLength > 0)\n\t\t\t{\n\t\t\t\tGetFixedHeaderPointer()->padding = 1;\n\n\t\t\t\tUtils::Byte::Set1Byte(const_cast<uint8_t*>(GetBuffer()), GetLength() - 1, newPaddingLength);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tGetFixedHeaderPointer()->padding = 0;\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::RtxEncode(uint8_t payloadType, uint32_t ssrc, uint16_t seq)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Remove padding (if any).\n\t\t\tif (HasPadding())\n\t\t\t{\n\t\t\t\t// NOTE: This must be called before SetLength() method below.\n\t\t\t\tSetPaddingLength(0);\n\t\t\t}\n\n\t\t\t// Update Packet length.\n\t\t\t// NOTE: This throws if given length is higher than buffer length.\n\t\t\tSetLength(GetLength() + 2);\n\n\t\t\t// Rewrite the payload type.\n\t\t\tSetPayloadType(payloadType);\n\n\t\t\t// Rewrite the SSRC.\n\t\t\tSetSsrc(ssrc);\n\n\t\t\tauto* payload            = GetPayloadPointer();\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\n\t\t\t// Write the original sequence number at the begining of the payload.\n\t\t\tstd::memmove(payload + 2, payload, payloadLength);\n\t\t\tUtils::Byte::Set2Bytes(payload, 0, GetSequenceNumber());\n\n\t\t\t// Rewrite the sequence number.\n\t\t\tSetSequenceNumber(seq);\n\t\t}\n\n\t\tbool Packet::RtxDecode(uint8_t payloadType, uint32_t ssrc)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* payload            = GetPayloadPointer();\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\n\t\t\t// NOTE: libwebrtc sends some RTX packets with no payload when the stream\n\t\t\t// is started. Just ignore them.\n\t\t\tif (payloadLength < 2)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Rewrite the payload type.\n\t\t\tSetPayloadType(payloadType);\n\n\t\t\t// Rewrite the sequence number.\n\t\t\tSetSequenceNumber(Utils::Byte::Get2Bytes(payload, 0));\n\n\t\t\t// Rewrite the SSRC.\n\t\t\tSetSsrc(ssrc);\n\n\t\t\t// Shift the payload to its original place.\n\t\t\tstd::memmove(payload, payload + 2, payloadLength - 2);\n\n\t\t\t// Remove padding (if any).\n\t\t\tif (HasPadding())\n\t\t\t{\n\t\t\t\t// NOTE: This must be called before SetLength() method below.\n\t\t\t\tSetPaddingLength(0);\n\t\t\t}\n\n\t\t\t// Update Packet length.\n\t\t\tSetLength(GetLength() - 2);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid Packet::SetPayloadDescriptorHandler(Codecs::PayloadDescriptorHandler* payloadDescriptorHandler)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->payloadDescriptorHandler.reset(payloadDescriptorHandler);\n\t\t}\n\n\t\tbool Packet::ProcessPayload(Codecs::EncodingContext* context, bool& marker)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn this->payloadDescriptorHandler->Process(context, this, marker);\n\t\t}\n\n\t\tstd::unique_ptr<Codecs::PayloadDescriptor::Encoder> Packet::GetPayloadEncoder() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn this->payloadDescriptorHandler->GetEncoder();\n\t\t}\n\n\t\tvoid Packet::EncodePayload(Codecs::PayloadDescriptor::Encoder* encoder)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->payloadDescriptorHandler->Encode(this, encoder);\n\t\t}\n\n\t\tvoid Packet::RestorePayload()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->payloadDescriptorHandler)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->payloadDescriptorHandler->Restore(this);\n\t\t}\n\n\t\tbool Packet::Validate(bool storeExtensions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Here we are at the beginning of the Packet.\n\t\t\tconst auto* ptr = const_cast<uint8_t*>(GetBuffer());\n\n\t\t\tif (GetVersion() != 2)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, version must be 2\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tptr += Packet::FixedHeaderMinLength;\n\n\t\t\t// Here we are at the beginning of the optional CCRS list.\n\t\t\tif (HasCsrcs())\n\t\t\t{\n\t\t\t\tauto csrcsLength = GetCsrcCount();\n\n\t\t\t\tif (GetLength() < static_cast<size_t>(ptr - GetBuffer()) + csrcsLength)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, not enough space for the announced CSRC list\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tptr += csrcsLength;\n\t\t\t}\n\n\t\t\t// Here we are at the beginning of the optional Header Extension.\n\t\t\tif (HasHeaderExtension())\n\t\t\t{\n\t\t\t\t// The Header Extension is at least 4 bytes.\n\t\t\t\tif (GetLength() < static_cast<size_t>(ptr - GetBuffer()) + 4)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, not enough space for the announced Header Extension\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst auto headerExtensionLength = GetHeaderExtensionLength();\n\n\t\t\t\tif (GetLength() < static_cast<size_t>(ptr - GetBuffer()) + headerExtensionLength)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp, \"invalid Packet, not enough space for the announced Header Extension value\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (!ParseExtensions(storeExtensions))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, invalid Extensions\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tptr += headerExtensionLength;\n\t\t\t}\n\n\t\t\t// Here we are at the beginning of the optional payload.\n\t\t\tconst auto payloadLength = GetPayloadLength();\n\t\t\tconst auto paddingLength = GetPaddingLength();\n\t\t\tconst auto availablePayloadAndPaddingLength = GetLength() - (GetPayloadPointer() - GetBuffer());\n\n\t\t\tif (payloadLength + paddingLength != availablePayloadAndPaddingLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, not enough space for announced padding\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (HasPadding() && paddingLength == 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"invalid Packet, padding byte cannot be 0\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tptr += availablePayloadAndPaddingLength;\n\n\t\t\t// Here we are at the end of the Packet.\n\t\t\tMS_ASSERT(\n\t\t\t  static_cast<size_t>(ptr - GetBuffer()) == GetLength(),\n\t\t\t  \"Packet computed length does not match its assigned length\");\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Packet::ParseExtensions(bool storeExtensions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (HasOneByteExtensions())\n\t\t\t{\n\t\t\t\tconst uint8_t* extensionsStart = GetHeaderExtensionValue();\n\t\t\t\tconst uint8_t* extensionsEnd   = extensionsStart + GetHeaderExtensionValueLength();\n\t\t\t\tauto* ptr                      = const_cast<uint8_t*>(extensionsStart);\n\n\t\t\t\t// One-Byte Extensions cannot have length 0.\n\t\t\t\twhile (ptr < extensionsEnd)\n\t\t\t\t{\n\t\t\t\t\tconst auto* extension = reinterpret_cast<OneByteExtension*>(ptr);\n\t\t\t\t\tconst uint8_t id      = extension->id;\n\t\t\t\t\t// NOTE: In One-Byte Extensions, announced value must be incremented\n\t\t\t\t\t// by 1.\n\t\t\t\t\tconst size_t len = extension->len + 1;\n\n\t\t\t\t\t// id=0 means alignment.\n\t\t\t\t\tif (id == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t++ptr;\n\t\t\t\t\t}\n\t\t\t\t\t// id=15 in One-Byte extensions means \"stop parsing here\".\n\t\t\t\t\telse if (id == 15)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t// Valid Extension id.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif (ptr + 1 + len > extensionsEnd)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  rtp,\n\t\t\t\t\t\t\t  \"not enough space for the announced value of the One-Byte Extension with id %\" PRIu8,\n\t\t\t\t\t\t\t  id);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeExtensions)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Store the One-Byte Extension offset in the array.\n\t\t\t\t\t\t\t// `-1` because we have 14 elements total 0..13 and `id` is in the\n\t\t\t\t\t\t\t// range 1..14.\n\t\t\t\t\t\t\tthis->oneByteExtensions[id - 1] = ptr - extensionsStart;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tptr += (1 + len);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Counting padding bytes.\n\t\t\t\t\twhile (ptr < extensionsEnd && *ptr == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t++ptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse if (HasTwoBytesExtensions())\n\t\t\t{\n\t\t\t\tconst uint8_t* extensionsStart = GetHeaderExtensionValue();\n\t\t\t\tconst uint8_t* extensionsEnd   = extensionsStart + GetHeaderExtensionValueLength();\n\t\t\t\t// ptr points to the Extension id field (1 byte).\n\t\t\t\t// ptr+1 points to the length field (1 byte, can have value 0).\n\t\t\t\tauto* ptr = const_cast<uint8_t*>(extensionsStart);\n\n\t\t\t\t// Two-Byte Extensions can have length 0.\n\t\t\t\twhile (ptr + 1 < extensionsEnd)\n\t\t\t\t{\n\t\t\t\t\tconst auto* extension = reinterpret_cast<TwoBytesExtension*>(ptr);\n\t\t\t\t\tconst uint8_t id      = extension->id;\n\t\t\t\t\tconst size_t len      = extension->len;\n\n\t\t\t\t\t// id=0 means alignment.\n\t\t\t\t\tif (id == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t++ptr;\n\t\t\t\t\t}\n\t\t\t\t\t// Valid Extension id.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif (ptr + 2 + len > extensionsEnd)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t\t  rtp,\n\t\t\t\t\t\t\t  \"not enough space for the announced value of the Two-Bytes Extension with id %\" PRIu8,\n\t\t\t\t\t\t\t  id);\n\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (storeExtensions)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Store the Two-Bytes Extension offset in the map.\n\t\t\t\t\t\t\tthis->twoBytesExtensions[id] = ptr - extensionsStart;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tptr += (2 + len);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Counting padding bytes.\n\t\t\t\t\twhile (ptr < extensionsEnd && *ptr == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t++ptr;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// If there is no Header Extension of if there is but it doesn't conform\n\t\t\t// to RFC 8285 Extensions, then this is ok.\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::SetExtensionLength(uint8_t id, uint8_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(id > 0, \"id cannot be 0\");\n\n\t\t\tif (HasOneByteExtensions())\n\t\t\t{\n\t\t\t\t// `-1` because we have 14 elements total 0..13 and `id` is in the\n\t\t\t\t// range 1..14.\n\t\t\t\tconst auto offset = this->oneByteExtensions[id - 1];\n\n\t\t\t\tMS_ASSERT(offset != -1, \"extension with id %\" PRIu8 \" not found\", id);\n\n\t\t\t\tauto* extension = reinterpret_cast<OneByteExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\t// In One-Byte Extensions value length 0 means 1.\n\t\t\t\tconst auto currentLen = extension->len + 1;\n\n\t\t\t\t// Fill with 0's if new length is minor.\n\t\t\t\tif (len < currentLen)\n\t\t\t\t{\n\t\t\t\t\tstd::memset(extension->value + len, 0, currentLen - len);\n\t\t\t\t}\n\n\t\t\t\textension->len = len - 1;\n\t\t\t}\n\t\t\telse if (HasTwoBytesExtensions())\n\t\t\t{\n\t\t\t\tconst auto it = this->twoBytesExtensions.find(id);\n\n\t\t\t\tMS_ASSERT(it != this->twoBytesExtensions.end(), \"extension with id %\" PRIu8 \" not found\", id);\n\n\t\t\t\tconst auto offset = it->second;\n\n\t\t\t\tauto* extension = reinterpret_cast<TwoBytesExtension*>(GetHeaderExtensionValue() + offset);\n\n\t\t\t\tconst auto currentLen = extension->len;\n\n\t\t\t\t// Fill with 0's if new length is minor.\n\t\t\t\tif (len < currentLen)\n\t\t\t\t{\n\t\t\t\t\tstd::memset(extension->value + len, 0, currentLen - len);\n\t\t\t\t}\n\n\t\t\t\textension->len = len;\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::OnDependencyDescriptorUpdated(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUpdateDependencyDescriptor(data, len);\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/ProbationGenerator.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::ProbationGenerator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/ProbationGenerator.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <cstring> // std::memcpy(), std::memset()\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\tstatic thread_local uint8_t ProbationPacketBuffer[ProbationGenerator::ProbationPacketMaxLength];\n\t\tstatic constexpr size_t ProbationPacketExtensionsBufferLength{ 200 };\n\t\talignas(4) static thread_local uint8_t\n\t\t  ProbationPacketExtensionsBuffer[ProbationPacketExtensionsBufferLength];\n\t\t// 8 bytes, same as RTC::Consts::MidRtpExtensionMaxLength.\n\t\tstatic const std::string MidValue{ \"probator\" };\n\n\t\t/* Instance methods. */\n\n\t\tProbationGenerator::ProbationGenerator()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Trick to only fill the padding with zeroes once.\n\t\t\tstatic thread_local bool mustInitializePayload{ true };\n\n\t\t\tif (mustInitializePayload)\n\t\t\t{\n\t\t\t\tstd::memset(ProbationPacketBuffer, 0x00, sizeof(ProbationPacketBuffer));\n\n\t\t\t\tmustInitializePayload = false;\n\t\t\t}\n\n\t\t\t// Create the probation RTP Packet.\n\t\t\tthis->probationPacket.reset(\n\t\t\t  RTP::Packet::Factory(ProbationPacketBuffer, sizeof(ProbationPacketBuffer)));\n\n\t\t\t// Sex fixed codec payload type.\n\t\t\tthis->probationPacket->SetPayloadType(ProbationGenerator::PayloadType);\n\n\t\t\t// Set fixed SSRC.\n\t\t\tthis->probationPacket->SetSsrc(ProbationGenerator::Ssrc);\n\n\t\t\t// Set random initial RTP seq number.\n\t\t\tthis->probationPacket->SetSequenceNumber(Utils::Crypto::GetRandomUInt<uint16_t>(0, 65535));\n\n\t\t\t// Set random initial RTP timestamp.\n\t\t\tthis->probationPacket->SetTimestamp(Utils::Crypto::GetRandomUInt<uint32_t>(0, 4294967295));\n\n\t\t\t// Add BWE related RTP header extensions.\n\t\t\tstd::vector<RTP::Packet::Extension> extensions;\n\t\t\tuint8_t extenLen;\n\t\t\tuint8_t* bufferPtr{ ProbationPacketExtensionsBuffer };\n\n\t\t\t// Add urn:ietf:params:rtp-hdrext:sdes:mid.\n\t\t\t{\n\t\t\t\textenLen = MidValue.size();\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::MID,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::MID),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\tstd::memcpy(bufferPtr, MidValue.c_str(), extenLen);\n\n\t\t\t\tbufferPtr += extenLen;\n\t\t\t}\n\n\t\t\t// Add http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time.\n\t\t\t// NOTE: Just the corresponding id and space for its value.\n\t\t\t{\n\t\t\t\textenLen = 3u;\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\tbufferPtr += extenLen;\n\t\t\t}\n\n\t\t\t// Add http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01.\n\t\t\t// NOTE: Just the corresponding id and space for its value.\n\t\t\t{\n\t\t\t\textenLen = 2u;\n\n\t\t\t\textensions.emplace_back(\n\t\t\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01,\n\t\t\t\t  /*id*/ static_cast<uint8_t>(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01),\n\t\t\t\t  /*len*/ extenLen,\n\t\t\t\t  /*value*/ bufferPtr);\n\n\t\t\t\t// Not needed since this is the latest added extension.\n\t\t\t\t// bufferPtr += extenLen;\n\t\t\t}\n\n\t\t\t// Set the extensions into the Packet using One-Byte format.\n\t\t\tthis->probationPacket->SetExtensions(RTP::Packet::ExtensionsType::OneByte, extensions);\n\n\t\t\tthis->probationPacketMinLength = this->probationPacket->GetLength();\n\t\t}\n\n\t\tProbationGenerator::~ProbationGenerator()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\t/**\n\t\t * This method maybe called with desired `len` higher than typical RTP\n\t\t * packet mas length. That's ok since the caller will iterate and call\n\t\t * this method again until it satisfies the total desired `len`.\n\t\t */\n\t\tRTP::Packet* ProbationGenerator::GetNextPacket(size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Pad given length to 4 bytes.\n\t\t\tlen = Utils::Byte::PadTo4Bytes(len);\n\n\t\t\t// Make the Packet length fit into our available limits.\n\t\t\tif (len > ProbationGenerator::ProbationPacketMaxLength)\n\t\t\t{\n\t\t\t\tlen = ProbationGenerator::ProbationPacketMaxLength;\n\t\t\t}\n\t\t\telse if (len < this->probationPacketMinLength)\n\t\t\t{\n\t\t\t\tlen = this->probationPacketMinLength;\n\t\t\t}\n\n\t\t\t// Just send up to StepNumPackets per step.\n\t\t\t// Increase RTP seq number and timestamp.\n\t\t\tconst auto seq             = this->probationPacket->GetSequenceNumber() + 1;\n\t\t\tconst auto timestamp       = this->probationPacket->GetTimestamp() + 20;\n\t\t\tconst size_t payloadLength = len - this->probationPacketMinLength;\n\n\t\t\tthis->probationPacket->SetSequenceNumber(seq);\n\t\t\tthis->probationPacket->SetTimestamp(timestamp);\n\n\t\t\t// Set payload length.\n\t\t\tthis->probationPacket->SetPayloadLength(payloadLength);\n\n\t\t\treturn this->probationPacket.get();\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/RetransmissionBuffer.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::RetransmissionBuffer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/RetransmissionBuffer.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SeqManager.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Class methods. */\n\n\t\tRetransmissionBuffer::Item* RetransmissionBuffer::FillItem(\n\t\t  RetransmissionBuffer::Item* item, RTP::Packet* packet, const RTP::SharedPacket& sharedPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Store original packet and some extra info into the item.\n\t\t\t//\n\t\t\t// NOTE: sharedPacket could be empty at this point but it's ok since the\n\t\t\t// Consumer will fill it.\n\t\t\titem->sharedPacket   = sharedPacket;\n\t\t\titem->encoder        = packet->GetPayloadEncoder();\n\t\t\titem->ssrc           = packet->GetSsrc();\n\t\t\titem->sequenceNumber = packet->GetSequenceNumber();\n\t\t\titem->timestamp      = packet->GetTimestamp();\n\t\t\titem->marker         = packet->HasMarker();\n\n\t\t\treturn item;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tRetransmissionBuffer::RetransmissionBuffer(\n\t\t  uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate)\n\t\t  : maxItems(maxItems), maxRetransmissionDelayMs(maxRetransmissionDelayMs), clockRate(clockRate)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(maxItems > 0u, \"maxItems must be greater than 0\");\n\t\t}\n\n\t\tRetransmissionBuffer::~RetransmissionBuffer()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tClear();\n\t\t}\n\n\t\tvoid RetransmissionBuffer::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<RetransmissionBuffer>\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  buffer [size:%zu, maxSize:%\" PRIu16 \"]\", this->buffer.size(), this->maxItems);\n\t\t\tif (!this->buffer.empty())\n\t\t\t{\n\t\t\t\tconst auto* oldestItem = GetOldest();\n\t\t\t\tconst auto* newestItem = GetNewest();\n\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  oldest item [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t  oldestItem->sequenceNumber,\n\t\t\t\t  oldestItem->timestamp);\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  newest item [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t  newestItem->sequenceNumber,\n\t\t\t\t  newestItem->timestamp);\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  buffer window: %\" PRIu32 \"ms\",\n\t\t\t\t  static_cast<uint32_t>(newestItem->timestamp * 1000 / this->clockRate) -\n\t\t\t\t    static_cast<uint32_t>(oldestItem->timestamp * 1000 / this->clockRate));\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</RetransmissionBuffer>\");\n\t\t}\n\n\t\tRetransmissionBuffer::Item* RetransmissionBuffer::Get(uint16_t seq) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* oldestItem = GetOldest();\n\n\t\t\tif (!oldestItem)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (RTC::SeqManager<uint16_t>::IsSeqLowerThan(seq, oldestItem->sequenceNumber))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tconst uint16_t idx = seq - oldestItem->sequenceNumber;\n\n\t\t\tif (static_cast<size_t>(idx) > this->buffer.size() - 1)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn this->buffer.at(idx);\n\t\t}\n\n\t\t/**\n\t\t * This method tries to insert given packet into the buffer. Here we assume\n\t\t * that packet seq number is legitimate according to the content of the buffer.\n\t\t * We discard the packet if too old and also discard it if its timestamp does\n\t\t * not properly fit (by ensuring that elements in the buffer are not only\n\t\t * ordered by increasing seq but also that their timestamp are incremental).\n\t\t */\n\t\tbool RetransmissionBuffer::Insert(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto ssrc      = packet->GetSsrc();\n\t\t\tconst auto seq       = packet->GetSequenceNumber();\n\t\t\tconst auto timestamp = packet->GetTimestamp();\n\n\t\t\tMS_DEBUG_DEV(\"packet [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\", seq, timestamp);\n\n\t\t\t// Buffer is empty, so just insert new item.\n\t\t\tif (this->buffer.empty())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"buffer empty [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\", seq, timestamp);\n\n\t\t\t\tauto* item = new Item();\n\n\t\t\t\tthis->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket));\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tauto* oldestItem = GetOldest();\n\t\t\tauto* newestItem = GetNewest();\n\n\t\t\t// Special case: Received packet has lower seq than newest packet in the\n\t\t\t// buffer, however its timestamp is higher. If so, clear the whole buffer.\n\t\t\tif (\n\t\t\t  RTC::SeqManager<uint16_t>::IsSeqLowerThan(seq, newestItem->sequenceNumber) &&\n\t\t\t  Utils::Number::IsHigherThan<uint32_t>(timestamp, newestItem->timestamp))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"packet has lower seq but higher timestamp than newest packet in the buffer, emptying the buffer [ssrc:%\" PRIu32\n\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t  ssrc,\n\t\t\t\t  seq,\n\t\t\t\t  timestamp);\n\n\t\t\t\tClear();\n\n\t\t\t\tauto* item = new Item();\n\n\t\t\t\tthis->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket));\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Clear too old packets in the buffer.\n\t\t\t// NOTE: Here we must consider the case in which, due for example to huge\n\t\t\t// packet loss, received packet has higher timestamp but \"older\" seq number\n\t\t\t// than the newest packet in the buffer and, if so, use it to clear too old\n\t\t\t// packets rather than the newest packet in the buffer.\n\t\t\tauto newestTimestamp = Utils::Number::IsHigherThan<uint32_t>(timestamp, newestItem->timestamp)\n\t\t\t                         ? timestamp\n\t\t\t                         : newestItem->timestamp;\n\n\t\t\t// ClearTooOldByTimestamp() returns true if at least one packet has been\n\t\t\t// removed from the front.\n\t\t\tif (ClearTooOldByTimestamp(newestTimestamp))\n\t\t\t{\n\t\t\t\t// Buffer content has been modified so we must check it again.\n\t\t\t\tif (this->buffer.empty())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"buffer empty after clearing too old packets [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\tauto* item = new Item();\n\n\t\t\t\t\tthis->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket));\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\toldestItem = GetOldest();\n\t\t\t\tnewestItem = GetNewest();\n\t\t\t}\n\n\t\t\tMS_ASSERT(oldestItem != nullptr, \"oldest item doesn't exist\");\n\t\t\tMS_ASSERT(newestItem != nullptr, \"newest item doesn't exist\");\n\n\t\t\t// Packet arrived in order (its seq is higher than seq of the newest stored\n\t\t\t// packet) so will become the newest one in the buffer.\n\t\t\tif (RTC::SeqManager<uint16_t>::IsSeqHigherThan(seq, newestItem->sequenceNumber))\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"packet in order [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\", seq, timestamp);\n\n\t\t\t\t// Ensure that the timestamp of the packet is equal or higher than the\n\t\t\t\t// timestamp of the newest stored packet.\n\t\t\t\tif (Utils::Number::IsLowerThan<uint32_t>(timestamp, newestItem->timestamp))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"packet has higher seq but lower timestamp than newest packet in the buffer, discarding it [ssrc:%\" PRIu32\n\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  ssrc,\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Calculate how many blank slots it would be necessary to add when\n\t\t\t\t// pushing new item to the back of the buffer.\n\t\t\t\tuint16_t numBlankSlots = seq - newestItem->sequenceNumber - 1;\n\n\t\t\t\t// We may have to remove oldest items not to exceed the maximum size of\n\t\t\t\t// the buffer.\n\t\t\t\tif (this->buffer.size() + numBlankSlots + 1 > this->maxItems)\n\t\t\t\t{\n\t\t\t\t\tconst uint16_t numItemsToRemove = this->buffer.size() + numBlankSlots + 1 - this->maxItems;\n\n\t\t\t\t\t// If num of items to be removed exceed buffer size minus one (needed to\n\t\t\t\t\t// allocate current packet) then we must clear the entire buffer.\n\t\t\t\t\tif (numItemsToRemove > this->buffer.size() - 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  rtp,\n\t\t\t\t\t\t  \"packet has too high seq and forces buffer emptying [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t\t  \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  ssrc,\n\t\t\t\t\t\t  seq,\n\t\t\t\t\t\t  timestamp);\n\n\t\t\t\t\t\tnumBlankSlots = 0u;\n\n\t\t\t\t\t\tClear();\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"calling RemoveOldest(%\" PRIu16 \") [bufferSize:%zu, numBlankSlots:%\" PRIu16\n\t\t\t\t\t\t  \", maxItems:%\" PRIu16 \"]\",\n\t\t\t\t\t\t  numItemsToRemove,\n\t\t\t\t\t\t  this->buffer.size(),\n\t\t\t\t\t\t  numBlankSlots,\n\t\t\t\t\t\t  this->maxItems);\n\n\t\t\t\t\t\tRemoveOldest(numItemsToRemove);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Push blank slots to the back.\n\t\t\t\tfor (uint16_t i{ 0u }; i < numBlankSlots; ++i)\n\t\t\t\t{\n\t\t\t\t\tthis->buffer.push_back(nullptr);\n\t\t\t\t}\n\n\t\t\t\t// Push the packet, which becomes the newest one in the buffer.\n\t\t\t\tauto* item = new Item();\n\n\t\t\t\tthis->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket));\n\t\t\t}\n\t\t\t// Packet arrived out order and its seq is less than seq of the oldest\n\t\t\t// stored packet, so will become the oldest one in the buffer.\n\t\t\telse if (RTC::SeqManager<uint16_t>::IsSeqLowerThan(seq, oldestItem->sequenceNumber))\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"packet out of order and older than oldest packet in the buffer [seq:%\" PRIu16\n\t\t\t\t  \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t  seq,\n\t\t\t\t  timestamp);\n\n\t\t\t\t// Ensure that packet is not too old to be stored.\n\t\t\t\tif (IsTooOldTimestamp(timestamp, newestItem->timestamp))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\n\t\t\t\t\t  \"packet's timestamp too old, discarding it [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Ensure that the timestamp of the packet is equal or less than the\n\t\t\t\t// timestamp of the oldest stored packet.\n\t\t\t\tif (Utils::Number::IsHigherThan<uint32_t>(timestamp, oldestItem->timestamp))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"packet has lower seq but higher timestamp than oldest packet in the buffer, discarding it [ssrc:%\" PRIu32\n\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  ssrc,\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Calculate how many blank slots it would be necessary to add when\n\t\t\t\t// pushing new item to the fton of the buffer.\n\t\t\t\tconst uint16_t numBlankSlots = oldestItem->sequenceNumber - seq - 1;\n\n\t\t\t\t// If adding this packet (and needed blank slots) to the front makes the\n\t\t\t\t// buffer exceed its max size, discard this packet.\n\t\t\t\tif (this->buffer.size() + numBlankSlots + 1 > this->maxItems)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"discarding received old packet to not exceed max buffer size [ssrc:%\" PRIu32\n\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  ssrc,\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// Push blank slots to the front.\n\t\t\t\tfor (uint16_t i{ 0u }; i < numBlankSlots; ++i)\n\t\t\t\t{\n\t\t\t\t\tthis->buffer.push_front(nullptr);\n\t\t\t\t}\n\n\t\t\t\t// Insert the packet, which becomes the oldest one in the buffer.\n\t\t\t\tauto* item = new Item();\n\n\t\t\t\tthis->buffer.push_front(RetransmissionBuffer::FillItem(item, packet, sharedPacket));\n\t\t\t}\n\t\t\t// Otherwise packet must be inserted between oldest and newest stored items\n\t\t\t// so there is already an allocated slot for it.\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"packet out of order and in between oldest and newest packets in the buffer [seq:%\" PRIu16\n\t\t\t\t  \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t  seq,\n\t\t\t\t  timestamp);\n\n\t\t\t\t// Let's check if an item already exist in same position. If so, assume\n\t\t\t\t// it's duplicated.\n\t\t\t\tauto* item = Get(seq);\n\n\t\t\t\tif (item)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"packet already in the buffer, discarding [seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  seq,\n\t\t\t\t\t  timestamp);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t// idx is the intended position of the received packet in the buffer.\n\t\t\t\tconst uint16_t idx = seq - oldestItem->sequenceNumber;\n\n\t\t\t\t// Validate that packet timestamp is equal or higher than the timestamp of\n\t\t\t\t// the immediate older packet (if any).\n\t\t\t\tfor (int32_t idx2 = idx - 1; idx2 >= 0; --idx2)\n\t\t\t\t{\n\t\t\t\t\tconst auto* olderItem = this->buffer.at(idx2);\n\n\t\t\t\t\t// Blank slot, continue.\n\t\t\t\t\tif (!olderItem)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We are done.\n\t\t\t\t\tif (timestamp >= olderItem->timestamp)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  rtp,\n\t\t\t\t\t\t  \"packet timestamp is lower than timestamp of immediate older packet in the buffer, discarding it [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  ssrc,\n\t\t\t\t\t\t  seq,\n\t\t\t\t\t\t  timestamp);\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Validate that packet timestamp is equal or less than the timestamp of\n\t\t\t\t// the immediate newer packet (if any).\n\t\t\t\tfor (size_t idx2 = idx + 1; idx2 < this->buffer.size(); ++idx2)\n\t\t\t\t{\n\t\t\t\t\tconst auto* newerItem = this->buffer.at(idx2);\n\n\t\t\t\t\t// Blank slot, continue.\n\t\t\t\t\tif (!newerItem)\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// We are done.\n\t\t\t\t\tif (timestamp <= newerItem->timestamp)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  rtp,\n\t\t\t\t\t\t  \"packet timestamp is higher than timestamp of immediate newer packet in the buffer, discarding it [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  ssrc,\n\t\t\t\t\t\t  seq,\n\t\t\t\t\t\t  timestamp);\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Store the packet.\n\t\t\t\titem = new Item();\n\n\t\t\t\tthis->buffer[idx] = RetransmissionBuffer::FillItem(item, packet, sharedPacket);\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->buffer.size() <= this->maxItems,\n\t\t\t  \"buffer contains %zu items (more than %\" PRIu16 \" max items)\",\n\t\t\t  this->buffer.size(),\n\t\t\t  this->maxItems);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid RetransmissionBuffer::Clear()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (auto* item : this->buffer)\n\t\t\t{\n\t\t\t\tif (!item)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Reset the stored item (decrease RTP packet shared pointer counter).\n\t\t\t\titem->Reset();\n\n\t\t\t\tdelete item;\n\t\t\t}\n\n\t\t\tthis->buffer.clear();\n\t\t}\n\n\t\tRetransmissionBuffer::Item* RetransmissionBuffer::GetOldest() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->buffer.empty())\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn this->buffer.front();\n\t\t}\n\n\t\tRetransmissionBuffer::Item* RetransmissionBuffer::GetNewest() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->buffer.empty())\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn this->buffer.back();\n\t\t}\n\n\t\tvoid RetransmissionBuffer::RemoveOldest()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->buffer.empty())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto* item = this->buffer.front();\n\n\t\t\t// Reset the stored item (decrease RTP packet shared pointer counter).\n\t\t\titem->Reset();\n\n\t\t\tdelete item;\n\n\t\t\tthis->buffer.pop_front();\n\n\t\t\tMS_DEBUG_DEV(\"removed 1 item from the front\");\n\n\t\t\t// Remove all nullptr elements from the beginning of the buffer.\n\t\t\t// NOTE: Calling front on an empty container is undefined.\n\t\t\tsize_t numItemsRemoved{ 0u };\n\n\t\t\twhile (!this->buffer.empty() && this->buffer.front() == nullptr)\n\t\t\t{\n\t\t\t\tthis->buffer.pop_front();\n\n\t\t\t\t++numItemsRemoved;\n\t\t\t}\n\n\t\t\tif (numItemsRemoved)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"removed %zu blank slots from the front\", numItemsRemoved);\n\t\t\t}\n\t\t}\n\n\t\tvoid RetransmissionBuffer::RemoveOldest(uint16_t numItems)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  numItems <= this->buffer.size(),\n\t\t\t  \"attempting to remove more items than current buffer size [numItems:%\" PRIu16\n\t\t\t  \", bufferSize:%zu]\",\n\t\t\t  numItems,\n\t\t\t  this->buffer.size());\n\n\t\t\tconst size_t intendedBufferSize = this->buffer.size() - numItems;\n\n\t\t\twhile (this->buffer.size() > intendedBufferSize)\n\t\t\t{\n\t\t\t\tRemoveOldest();\n\t\t\t}\n\t\t}\n\n\t\tbool RetransmissionBuffer::ClearTooOldByTimestamp(uint32_t newestTimestamp)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst RetransmissionBuffer::Item* oldestItem{ nullptr };\n\t\t\tbool itemsRemoved{ false };\n\n\t\t\t// Go through all buffer items starting with the first and free all items\n\t\t\t// that contain too old packets.\n\t\t\twhile ((oldestItem = GetOldest()))\n\t\t\t{\n\t\t\t\tif (IsTooOldTimestamp(oldestItem->timestamp, newestTimestamp))\n\t\t\t\t{\n\t\t\t\t\tRemoveOldest();\n\n\t\t\t\t\titemsRemoved = true;\n\t\t\t\t}\n\t\t\t\t// If current oldest stored packet is not too old, exit the loop since we\n\t\t\t\t// know that packets stored after it are guaranteed to be newer.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn itemsRemoved;\n\t\t}\n\n\t\tbool RetransmissionBuffer::IsTooOldTimestamp(uint32_t timestamp, uint32_t newestTimestamp) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (Utils::Number::IsHigherThan<uint32_t>(timestamp, newestTimestamp))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst int64_t diffTs = newestTimestamp - timestamp;\n\n\t\t\treturn static_cast<uint32_t>(diffTs * 1000 / this->clockRate) > this->maxRetransmissionDelayMs;\n\t\t}\n\n\t\tvoid RetransmissionBuffer::Item::Reset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: Here we MUST NOT call this->sharedPacket.Reset() because that\n\t\t\t// would affect all copies of this SharedRtpPacket by removing their stored\n\t\t\t// packet. We have to replace it entirely.\n\t\t\tthis->sharedPacket   = RTP::SharedPacket();\n\t\t\tthis->ssrc           = 0u;\n\t\t\tthis->sequenceNumber = 0u;\n\t\t\tthis->timestamp      = 0u;\n\t\t\tthis->resentAtMs     = 0u;\n\t\t\tthis->sentTimes      = 0u;\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/RtpStream.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::RtpStream\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr uint16_t MaxDropout{ 3000 };\n\t\tstatic constexpr uint16_t MaxMisorder{ 1500 };\n\t\tstatic constexpr uint32_t RtpSeqMod{ 1 << 16 };\n\t\tstatic constexpr size_t ScoreHistogramLength{ 24 };\n\n\t\t/* Instance methods. */\n\n\t\tRtpStream::RtpStream(\n\t\t  RTP::RtpStream::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  RTP::RtpStream::Params& params,\n\t\t  uint8_t initialScore)\n\t\t  : listener(listener),\n\t\t    shared(shared),\n\t\t    params(params),\n\t\t    score(initialScore),\n\t\t    activeSinceMs(this->shared->GetTimeMs())\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tRtpStream::~RtpStream()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tdelete this->rtxStream;\n\t\t\tthis->rtxStream = nullptr;\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpStream::Dump> RtpStream::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add params.\n\t\t\tauto params = this->params.FillBuffer(builder);\n\n\t\t\t// Add rtxStream.\n\t\t\tflatbuffers::Offset<FBS::RtxStream::RtxDump> rtxStream;\n\n\t\t\tif (HasRtx())\n\t\t\t{\n\t\t\t\trtxStream = this->rtxStream->FillBuffer(builder);\n\t\t\t}\n\n\t\t\treturn FBS::RtpStream::CreateDump(builder, params, this->score, rtxStream);\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpStream::Stats> RtpStream::FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\t\t\tconst auto mediaKind = this->params.mimeType.type == RTC::RtpCodecMimeType::Type::AUDIO\n\t\t\t                         ? FBS::RtpParameters::MediaKind::AUDIO\n\t\t\t                         : FBS::RtpParameters::MediaKind::VIDEO;\n\n\t\t\tauto baseStats = FBS::RtpStream::CreateBaseStatsDirect(\n\t\t\t  builder,\n\t\t\t  nowMs,\n\t\t\t  this->params.ssrc,\n\t\t\t  mediaKind,\n\t\t\t  this->params.mimeType.ToString().c_str(),\n\t\t\t  this->packetsLost,\n\t\t\t  this->fractionLost,\n\t\t\t  this->jitter,\n\t\t\t  this->packetsDiscarded,\n\t\t\t  this->packetsRetransmitted,\n\t\t\t  this->packetsRepaired,\n\t\t\t  this->nackCount,\n\t\t\t  this->nackPacketCount,\n\t\t\t  this->pliCount,\n\t\t\t  this->firCount,\n\t\t\t  !this->params.rid.empty() ? this->params.rid.c_str() : nullptr,\n\t\t\t  this->params.rtxSsrc ? flatbuffers::Optional<uint32_t>(this->params.rtxSsrc)\n\t\t\t                       : flatbuffers::nullopt,\n\t\t\t  this->rtxStream ? this->rtxStream->GetPacketsDiscarded() : 0,\n\t\t\t  this->rtt > 0.0f ? this->rtt : 0,\n\t\t\t  this->score);\n\n\t\t\treturn FBS::RtpStream::CreateStats(\n\t\t\t  builder, FBS::RtpStream::StatsData::BaseStats, baseStats.Union());\n\t\t}\n\n\t\tvoid RtpStream::SetRtx(uint8_t payloadType, uint32_t ssrc)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->params.rtxPayloadType = payloadType;\n\t\t\tthis->params.rtxSsrc        = ssrc;\n\n\t\t\tif (HasRtx())\n\t\t\t{\n\t\t\t\tdelete this->rtxStream;\n\t\t\t\tthis->rtxStream = nullptr;\n\t\t\t}\n\n\t\t\t// Set RTX stream params.\n\t\t\tRTP::RtxStream::Params params;\n\n\t\t\tparams.ssrc             = ssrc;\n\t\t\tparams.payloadType      = payloadType;\n\t\t\tparams.mimeType.type    = GetMimeType().type;\n\t\t\tparams.mimeType.subtype = RTC::RtpCodecMimeType::Subtype::RTX;\n\t\t\tparams.clockRate        = GetClockRate();\n\t\t\tparams.rrid             = GetRid();\n\t\t\tparams.cname            = GetCname();\n\n\t\t\t// Tell the RtpCodecMimeType to update its string based on current type and subtype.\n\t\t\tparams.mimeType.UpdateMimeType();\n\n\t\t\tthis->rtxStream = new RTP::RtxStream(this->shared, params);\n\t\t}\n\n\t\tbool RtpStream::ReceiveStreamPacket(const RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t seq = packet->GetSequenceNumber();\n\n\t\t\t// If this is the first packet seen, initialize stuff.\n\t\t\tif (!this->started)\n\t\t\t{\n\t\t\t\tInitSeq(seq);\n\n\t\t\t\tthis->started     = true;\n\t\t\t\tthis->maxSeq      = seq - 1;\n\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t}\n\n\t\t\t// If not a valid packet ignore it.\n\t\t\tif (!UpdateSeq(packet))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"invalid packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Update highest seen RTP timestamp.\n\t\t\tif (Utils::Number::IsHigherThan<uint32_t>(packet->GetTimestamp(), this->maxPacketTs))\n\t\t\t{\n\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid RtpStream::ResetScore(uint8_t score, bool notify)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->scores.clear();\n\n\t\t\tif (this->score != score)\n\t\t\t{\n\t\t\t\tauto previousScore = this->score;\n\n\t\t\t\tthis->score = score;\n\n\t\t\t\t// If previous score was 0 (and new one is not 0) then update activeSinceMs.\n\t\t\t\tif (previousScore == 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->activeSinceMs = this->shared->GetTimeMs();\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tif (notify)\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnRtpStreamScore(this, score, previousScore);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tbool RtpStream::UpdateSeq(const RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t seq    = packet->GetSequenceNumber();\n\t\t\tconst uint16_t udelta = seq - this->maxSeq;\n\n\t\t\t// If the new packet sequence number is greater than the max seen but not\n\t\t\t// \"so much bigger\", accept it.\n\t\t\t// NOTE: udelta also handles the case of a new cycle, this is:\n\t\t\t//    maxSeq:65536, seq:0 => udelta:1\n\t\t\tif (udelta < MaxDropout)\n\t\t\t{\n\t\t\t\t// In order, with permissible gap.\n\t\t\t\tif (seq < this->maxSeq)\n\t\t\t\t{\n\t\t\t\t\t// Sequence number wrapped: count another 64K cycle.\n\t\t\t\t\tthis->cycles += RtpSeqMod;\n\t\t\t\t}\n\n\t\t\t\tthis->maxSeq = seq;\n\n\t\t\t\t// Timestamp moved backwards despite in-order sequence number. Likely\n\t\t\t\t// caused by prolonged Producer inactivity (e.g., overnight pause).\n\t\t\t\tif (Utils::Number::IsLowerThan<uint32_t>(packet->GetTimestamp(), this->maxPacketTs))\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"timestamp moved backwards, updating [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t  \", old maxPacketTs:%\" PRIu32 \", new maxPacketTs:%\" PRIu32 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t\t  this->maxPacketTs,\n\t\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Too old packet received (older than the allowed misorder).\n\t\t\t// Or too new packet (more than acceptable dropout).\n\t\t\telse if (udelta <= RtpSeqMod - MaxMisorder)\n\t\t\t{\n\t\t\t\t// The sequence number made a very large jump. If two sequential packets\n\t\t\t\t// arrive, accept the latter.\n\t\t\t\tif (seq == this->badSeq)\n\t\t\t\t{\n\t\t\t\t\t// Two sequential packets. Assume that the other side restarted without\n\t\t\t\t\t// telling us so just re-sync (i.e., pretend this was the first packet).\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"too bad sequence number, re-syncing RTP [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\t\tInitSeq(seq);\n\n\t\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\n\t\t\t\t\t// Notify the subclass about it.\n\t\t\t\t\tUserOnSequenceNumberReset();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"bad sequence number, ignoring packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\t\tthis->badSeq = (seq + 1) & (RtpSeqMod - 1);\n\n\t\t\t\t\t// Packet discarded due to late or early arriving.\n\t\t\t\t\tthis->packetsDiscarded++;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Acceptable misorder.\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Do nothing.\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid RtpStream::UpdateScore(uint8_t score)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add the score into the histogram.\n\t\t\tif (this->scores.size() == ScoreHistogramLength)\n\t\t\t{\n\t\t\t\tthis->scores.erase(this->scores.begin());\n\t\t\t}\n\n\t\t\tauto previousScore = this->score;\n\n\t\t\t// Compute new effective score taking into accout entries in the histogram.\n\t\t\tthis->scores.push_back(score);\n\n\t\t\t/*\n\t\t\t * Scoring mechanism is a weighted average.\n\t\t\t *\n\t\t\t * The more recent the score is, the more weight it has.\n\t\t\t * The oldest score has a weight of 1 and subsequent scores weight is\n\t\t\t * increased by one sequentially.\n\t\t\t *\n\t\t\t * Ie:\n\t\t\t * - scores: [1,2,3,4]\n\t\t\t * - this->scores = ((1) + (2+2) + (3+3+3) + (4+4+4+4)) / 10 = 2.8 => 3\n\t\t\t */\n\n\t\t\tsize_t weight{ 0 };\n\t\t\tsize_t samples{ 0 };\n\t\t\tsize_t totalScore{ 0 };\n\n\t\t\tfor (auto score : this->scores)\n\t\t\t{\n\t\t\t\tweight++;\n\t\t\t\tsamples += weight;\n\t\t\t\ttotalScore += weight * score;\n\t\t\t}\n\n\t\t\t// clang-tidy \"thinks\" that this can lead to division by zero but we are\n\t\t\t// smarter.\n\t\t\t// NOLINTNEXTLINE(clang-analyzer-core.DivideZero)\n\t\t\tthis->score = static_cast<uint8_t>(std::round(static_cast<double>(totalScore) / samples));\n\n\t\t\t// Call the listener if the global score has changed.\n\t\t\tif (this->score != previousScore)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  score,\n\t\t\t\t  \"[added score:%\" PRIu8 \", previous computed score:%\" PRIu8 \", new computed score:%\" PRIu8\n\t\t\t\t  \"] (calling listener)\",\n\t\t\t\t  score,\n\t\t\t\t  previousScore,\n\t\t\t\t  this->score);\n\n\t\t\t\t// If previous score was 0 (and new one is not 0) then update activeSinceMs.\n\t\t\t\tif (previousScore == 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->activeSinceMs = this->shared->GetTimeMs();\n\t\t\t\t}\n\n\t\t\t\tthis->listener->OnRtpStreamScore(this, this->score, previousScore);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  score,\n\t\t\t\t  \"[added score:%\" PRIu8 \", previous computed score:%\" PRIu8 \", new computed score:%\" PRIu8\n\t\t\t\t  \"] (no change)\",\n\t\t\t\t  score,\n\t\t\t\t  previousScore,\n\t\t\t\t  this->score);\n#endif\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStream::PacketRetransmitted(const RTP::Packet* /*packet*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->packetsRetransmitted++;\n\t\t}\n\n\t\tvoid RtpStream::PacketRepaired(const RTP::Packet* /*packet*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->packetsRepaired++;\n\t\t}\n\n\t\tinline void RtpStream::InitSeq(uint16_t seq)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Initialize/reset RTP counters.\n\t\t\tthis->baseSeq = seq;\n\t\t\tthis->maxSeq  = seq;\n\t\t\tthis->badSeq  = RtpSeqMod + 1; // So seq == badSeq is false.\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpStream::Params> RtpStream::Params::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn FBS::RtpStream::CreateParamsDirect(\n\t\t\t  builder,\n\t\t\t  this->encodingIdx,\n\t\t\t  this->ssrc,\n\t\t\t  this->payloadType,\n\t\t\t  this->mimeType.ToString().c_str(),\n\t\t\t  this->clockRate,\n\t\t\t  this->rid.c_str(),\n\t\t\t  this->cname.c_str(),\n\t\t\t  this->rtxSsrc != 0 ? flatbuffers::Optional<uint32_t>(this->rtxSsrc) : flatbuffers::nullopt,\n\t\t\t  this->rtxSsrc != 0 ? flatbuffers::Optional<uint8_t>(this->rtxPayloadType)\n\t\t\t                     : flatbuffers::nullopt,\n\t\t\t  this->useNack,\n\t\t\t  this->usePli,\n\t\t\t  this->useFir,\n\t\t\t  this->useInBandFec,\n\t\t\t  this->useDtx,\n\t\t\t  this->spatialLayers,\n\t\t\t  this->temporalLayers);\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/RtpStreamRecv.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::RtpStreamRecv\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr uint64_t InactivityCheckInterval{ 1500u };        // In ms.\n\t\tstatic constexpr uint64_t InactivityCheckIntervalWithDtx{ 5000u }; // In ms.\n\n\t\t/* TransmissionCounter methods. */\n\n\t\tRtpStreamRecv::TransmissionCounter::TransmissionCounter(\n\t\t  SharedInterface* shared, uint8_t spatialLayers, uint8_t temporalLayers, size_t windowSize)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Reserve vectors capacity.\n\t\t\tthis->spatialLayerCounters = std::vector<std::vector<RTC::RtpDataCounter>>(spatialLayers);\n\n\t\t\tfor (auto& spatialLayerCounter : this->spatialLayerCounters)\n\t\t\t{\n\t\t\t\tfor (uint8_t tIdx{ 0u }; tIdx < temporalLayers; ++tIdx)\n\t\t\t\t{\n\t\t\t\t\tspatialLayerCounter.emplace_back(shared, /*ignorePaddingOnlyPackets*/ true, windowSize);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamRecv::TransmissionCounter::Update(const RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto spatialLayer  = packet->GetSpatialLayer();\n\t\t\tauto temporalLayer = packet->GetTemporalLayer();\n\n\t\t\t// Sanity check. Do not allow spatial layers higher than defined.\n\t\t\tspatialLayer =\n\t\t\t  std::min(static_cast<size_t>(spatialLayer), this->spatialLayerCounters.size() - 1);\n\n\t\t\t// Sanity check. Do not allow temporal layers higher than defined.\n\t\t\ttemporalLayer =\n\t\t\t  std::min(static_cast<size_t>(temporalLayer), this->spatialLayerCounters[0].size() - 1);\n\n\t\t\tauto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];\n\n\t\t\tcounter.Update(packet);\n\t\t}\n\n\t\tuint32_t RtpStreamRecv::TransmissionCounter::GetBitrate(uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint32_t rate{ 0u };\n\n\t\t\tfor (auto& spatialLayerCounter : this->spatialLayerCounters)\n\t\t\t{\n\t\t\t\tfor (auto& temporalLayerCounter : spatialLayerCounter)\n\t\t\t\t{\n\t\t\t\t\trate += temporalLayerCounter.GetBitrate(nowMs);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn rate;\n\t\t}\n\n\t\tuint32_t RtpStreamRecv::TransmissionCounter::GetBitrate(\n\t\t  uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), \"spatialLayer too high\");\n\t\t\tMS_ASSERT(\n\t\t\t  temporalLayer < this->spatialLayerCounters[spatialLayer].size(), \"temporalLayer too high\");\n\n\t\t\t// Return 0 if specified layers are not being received.\n\t\t\tauto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];\n\n\t\t\tif (counter.GetBitrate(nowMs) == 0)\n\t\t\t{\n\t\t\t\treturn 0u;\n\t\t\t}\n\n\t\t\tuint32_t rate{ 0u };\n\n\t\t\t// Iterate all temporal layers of spatial layers previous to the given one.\n\t\t\tfor (uint8_t sIdx{ 0u }; sIdx < spatialLayer; ++sIdx)\n\t\t\t{\n\t\t\t\tfor (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[sIdx].size(); ++tIdx)\n\t\t\t\t{\n\t\t\t\t\tauto& temporalLayerCounter = this->spatialLayerCounters[sIdx][tIdx];\n\n\t\t\t\t\trate += temporalLayerCounter.GetBitrate(nowMs);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add the given spatial layer with up to the given temporal layer.\n\t\t\tfor (uint8_t tIdx{ 0u }; tIdx <= temporalLayer; ++tIdx)\n\t\t\t{\n\t\t\t\tauto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx];\n\n\t\t\t\trate += temporalLayerCounter.GetBitrate(nowMs);\n\t\t\t}\n\n\t\t\treturn rate;\n\t\t}\n\n\t\tuint32_t RtpStreamRecv::TransmissionCounter::GetSpatialLayerBitrate(\n\t\t  uint64_t nowMs, uint8_t spatialLayer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), \"spatialLayer too high\");\n\n\t\t\tuint32_t rate{ 0u };\n\n\t\t\tfor (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[spatialLayer].size(); ++tIdx)\n\t\t\t{\n\t\t\t\tauto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx];\n\n\t\t\t\trate += temporalLayerCounter.GetBitrate(nowMs);\n\t\t\t}\n\n\t\t\treturn rate;\n\t\t}\n\n\t\tuint32_t RtpStreamRecv::TransmissionCounter::GetLayerBitrate(\n\t\t  uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), \"spatialLayer too high\");\n\t\t\tMS_ASSERT(\n\t\t\t  temporalLayer < this->spatialLayerCounters[spatialLayer].size(), \"temporalLayer too high\");\n\n\t\t\tauto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer];\n\n\t\t\treturn counter.GetBitrate(nowMs);\n\t\t}\n\n\t\tsize_t RtpStreamRecv::TransmissionCounter::GetPacketCount() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t packetCount{ 0u };\n\n\t\t\tfor (const auto& spatialLayerCounter : this->spatialLayerCounters)\n\t\t\t{\n\t\t\t\tfor (const auto& temporalLayerCounter : spatialLayerCounter)\n\t\t\t\t{\n\t\t\t\t\tpacketCount += temporalLayerCounter.GetPacketCount();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn packetCount;\n\t\t}\n\n\t\tsize_t RtpStreamRecv::TransmissionCounter::GetBytes() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t bytes{ 0u };\n\n\t\t\tfor (const auto& spatialLayerCounter : this->spatialLayerCounters)\n\t\t\t{\n\t\t\t\tfor (const auto& temporalLayerCounter : spatialLayerCounter)\n\t\t\t\t{\n\t\t\t\t\tbytes += temporalLayerCounter.GetBytes();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn bytes;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tRtpStreamRecv::RtpStreamRecv(\n\t\t  RTP::RtpStreamRecv::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  RTP::RtpStream::Params& params,\n\t\t  uint32_t sendNackDelayMs,\n\t\t  bool useRtpInactivityCheck)\n\t\t  : RTP::RtpStream::RtpStream(listener, shared, params, 10),\n\t\t    sendNackDelayMs(sendNackDelayMs),\n\t\t    useRtpInactivityCheck(useRtpInactivityCheck),\n\t\t    transmissionCounter(\n\t\t      shared, params.spatialLayers, params.temporalLayers, this->params.useDtx ? 6000 : 2500),\n\t\t    mediaTransmissionCounter(shared, /*ignorePaddingOnlyPackets*/ true)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->params.useNack)\n\t\t\t{\n\t\t\t\tthis->nackGenerator.reset(new RTC::NackGenerator(this, this->shared, this->sendNackDelayMs));\n\t\t\t}\n\n\t\t\tthis->inactive = false;\n\n\t\t\tif (this->useRtpInactivityCheck)\n\t\t\t{\n\t\t\t\t// Run the RTP inactivity periodic timer (use a different timeout if DTX is\n\t\t\t\t// enabled).\n\t\t\t\tthis->inactivityCheckPeriodicTimer = this->shared->CreateTimer(this);\n\n\t\t\t\tthis->inactivityCheckPeriodicTimer->Start(\n\t\t\t\t  this->params.useDtx ? InactivityCheckIntervalWithDtx : InactivityCheckInterval);\n\t\t\t}\n\t\t}\n\n\t\tRtpStreamRecv::~RtpStreamRecv()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Close the RTP inactivity check periodic timer.\n\t\t\tdelete this->inactivityCheckPeriodicTimer;\n\t\t\tthis->inactivityCheckPeriodicTimer = nullptr;\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpStream::Stats> RtpStreamRecv::FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tauto baseStats = RTP::RtpStream::FillBufferStats(builder);\n\n\t\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::BitrateByLayer>> bitrateByLayer;\n\n\t\t\tif (GetSpatialLayers() > 1 || GetTemporalLayers() > 1)\n\t\t\t{\n\t\t\t\tfor (uint8_t sIdx = 0; sIdx < GetSpatialLayers(); ++sIdx)\n\t\t\t\t{\n\t\t\t\t\tfor (uint8_t tIdx = 0; tIdx < GetTemporalLayers(); ++tIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto layer = std::to_string(sIdx) + \".\" + std::to_string(tIdx);\n\n\t\t\t\t\t\tbitrateByLayer.emplace_back(\n\t\t\t\t\t\t  FBS::RtpStream::CreateBitrateByLayerDirect(\n\t\t\t\t\t\t    builder, layer.c_str(), GetBitrate(nowMs, sIdx, tIdx)));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto stats = FBS::RtpStream::CreateRecvStatsDirect(\n\t\t\t  builder,\n\t\t\t  baseStats,\n\t\t\t  this->transmissionCounter.GetPacketCount(),\n\t\t\t  this->transmissionCounter.GetBytes(),\n\t\t\t  this->transmissionCounter.GetBitrate(nowMs),\n\t\t\t  &bitrateByLayer);\n\n\t\t\treturn FBS::RtpStream::CreateStats(builder, FBS::RtpStream::StatsData::RecvStats, stats.Union());\n\t\t}\n\n\t\tbool RtpStreamRecv::ReceivePacket(RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Call the parent method.\n\t\t\tif (!RTP::RtpStream::ReceiveStreamPacket(packet))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtp, \"packet discarded\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Process the packet at codec level.\n\t\t\tif (packet->GetPayloadType() == GetPayloadType())\n\t\t\t{\n\t\t\t\tRTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure);\n\t\t\t}\n\n\t\t\t// Pass the packet to the NackGenerator.\n\t\t\tif (this->params.useNack)\n\t\t\t{\n\t\t\t\t// If there is RTX just provide the NackGenerator with the packet.\n\t\t\t\tif (HasRtx())\n\t\t\t\t{\n\t\t\t\t\tthis->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false);\n\t\t\t\t}\n\t\t\t\t// If there is no RTX and NackGenerator returns true it means that it\n\t\t\t\t// was a NACKed packet.\n\t\t\t\telse if (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false))\n\t\t\t\t{\n\t\t\t\t\t// Mark the packet as retransmitted and repaired.\n\t\t\t\t\tRTP::RtpStream::PacketRetransmitted(packet);\n\t\t\t\t\tRTP::RtpStream::PacketRepaired(packet);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Calculate Jitter.\n\t\t\tCalculateJitter(packet->GetTimestamp());\n\n\t\t\t// Increase transmission counter.\n\t\t\tthis->transmissionCounter.Update(packet);\n\n\t\t\t// Increase media transmission counter.\n\t\t\tthis->mediaTransmissionCounter.Update(packet);\n\n\t\t\t// Padding only packet, do not consider it for stream activation.\n\t\t\tif (packet->GetPayloadLength() == 0)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Not inactive anymore.\n\t\t\tif (this->inactive)\n\t\t\t{\n\t\t\t\tthis->inactive = false;\n\n\t\t\t\tResetScore(10, /*notify*/ true);\n\t\t\t}\n\n\t\t\t// Restart the inactivityCheckPeriodicTimer.\n\t\t\tif (this->inactivityCheckPeriodicTimer)\n\t\t\t{\n\t\t\t\tthis->inactivityCheckPeriodicTimer->Restart();\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool RtpStreamRecv::ReceiveRtxPacket(RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->params.useNack)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtx, \"NACK not supported\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tMS_ASSERT(packet->GetSsrc() == this->params.rtxSsrc, \"invalid ssrc on RTX packet\");\n\n\t\t\t// Check that the payload type corresponds to the one negotiated.\n\t\t\tif (packet->GetPayloadType() != this->params.rtxPayloadType)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtx,\n\t\t\t\t  \"ignoring RTX packet with invalid payload type [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t  \", pt:%\" PRIu8 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetPayloadType());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (HasRtx())\n\t\t\t{\n\t\t\t\tif (!this->rtxStream->ReceivePacket(packet))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(rtx, \"RTX packet discarded\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t// Get the RTX packet sequence number for logging purposes.\n\t\t\tauto rtxSeq = packet->GetSequenceNumber();\n#endif\n\n\t\t\t// Get the original RTP packet.\n\t\t\tif (!packet->RtxDecode(this->params.payloadType, this->params.ssrc))\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"ignoring empty RTX packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", pt:%\" PRIu8 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetPayloadType());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"received RTX packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"] recovering original [ssrc:%\" PRIu32\n\t\t\t  \", seq:%\" PRIu16 \"]\",\n\t\t\t  this->params.rtxSsrc,\n\t\t\t  rtxSeq,\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t// If not a valid packet ignore it.\n\t\t\tif (!RTP::RtpStream::UpdateSeq(packet))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtx,\n\t\t\t\t  \"invalid RTX packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Process the packet at codec level.\n\t\t\tif (packet->GetPayloadType() == GetPayloadType())\n\t\t\t{\n\t\t\t\tRTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure);\n\t\t\t}\n\n\t\t\t// Mark the packet as retransmitted.\n\t\t\tRTP::RtpStream::PacketRetransmitted(packet);\n\n\t\t\t// Pass the packet to the NackGenerator and return true just if this was a\n\t\t\t// NACKed packet or a RTX packet containing a non yet seen original RTP\n\t\t\t// packet.\n\t\t\tif (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ true))\n\t\t\t{\n\t\t\t\t// Mark the packet as repaired.\n\t\t\t\tRTP::RtpStream::PacketRepaired(packet);\n\n\t\t\t\t// Increase transmission counter.\n\t\t\t\tthis->transmissionCounter.Update(packet);\n\n\t\t\t\t// Padding only packet, do not consider it for stream activation.\n\t\t\t\tif (packet->GetPayloadLength() == 0)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Not inactive anymore.\n\t\t\t\tif (this->inactive)\n\t\t\t\t{\n\t\t\t\t\tthis->inactive = false;\n\n\t\t\t\t\tResetScore(10, /*notify*/ true);\n\t\t\t\t}\n\n\t\t\t\t// Restart the inactivityCheckPeriodicTimer.\n\t\t\t\tif (this->inactivityCheckPeriodicTimer)\n\t\t\t\t{\n\t\t\t\t\tthis->inactivityCheckPeriodicTimer->Restart();\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tRTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtcpReceiverReport()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tuint8_t worstRemoteFractionLost{ 0 };\n\n\t\t\tif (this->params.useInBandFec)\n\t\t\t{\n\t\t\t\t// Notify the listener so we'll get the worst remote fraction lost.\n\t\t\t\tstatic_cast<RTP::RtpStreamRecv::Listener*>(this->listener)\n\t\t\t\t  ->OnRtpStreamNeedWorstRemoteFractionLost(this, worstRemoteFractionLost);\n\n\t\t\t\tif (worstRemoteFractionLost > 0)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(rtcp, \"using worst remote fraction lost:%\" PRIu8, worstRemoteFractionLost);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto* report = new RTC::RTCP::ReceiverReport();\n\n\t\t\treport->SetSsrc(GetSsrc());\n\n\t\t\tconst int32_t prevPacketsLost = this->packetsLost;\n\n\t\t\t// Calculate Packets Expected and Lost.\n\t\t\tauto expected = GetExpectedPackets();\n\n\t\t\tif (expected > this->mediaTransmissionCounter.GetPacketCount())\n\t\t\t{\n\t\t\t\tthis->packetsLost = expected - this->mediaTransmissionCounter.GetPacketCount();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->packetsLost = 0;\n\t\t\t}\n\n\t\t\t// Calculate Fraction Lost.\n\t\t\tconst uint32_t expectedInterval = expected - this->expectedPrior;\n\n\t\t\tthis->expectedPrior = expected;\n\n\t\t\tconst uint32_t receivedInterval =\n\t\t\t  this->mediaTransmissionCounter.GetPacketCount() - this->receivedPrior;\n\n\t\t\tthis->receivedPrior = this->mediaTransmissionCounter.GetPacketCount();\n\n\t\t\tconst int32_t lostInterval = expectedInterval - receivedInterval;\n\n\t\t\tif (expectedInterval == 0 || lostInterval <= 0)\n\t\t\t{\n\t\t\t\tthis->fractionLost = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->fractionLost = std::round((static_cast<double>(lostInterval << 8) / expectedInterval));\n\t\t\t}\n\n\t\t\t// Worst remote fraction lost is not worse than local one.\n\t\t\tif (worstRemoteFractionLost <= this->fractionLost)\n\t\t\t{\n\t\t\t\tthis->reportedPacketsLost += (this->packetsLost - prevPacketsLost);\n\n\t\t\t\treport->SetTotalLost(this->reportedPacketsLost);\n\t\t\t\treport->SetFractionLost(this->fractionLost);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Recalculate packetsLost.\n\t\t\t\tconst uint32_t newLostInterval = (worstRemoteFractionLost * expectedInterval) >> 8;\n\n\t\t\t\tthis->reportedPacketsLost += newLostInterval;\n\n\t\t\t\treport->SetTotalLost(this->reportedPacketsLost);\n\t\t\t\treport->SetFractionLost(worstRemoteFractionLost);\n\t\t\t}\n\n\t\t\t// Fill the rest of the report.\n\t\t\treport->SetLastSeq(static_cast<uint32_t>(this->maxSeq) + this->cycles);\n\t\t\treport->SetJitter(this->jitter);\n\n\t\t\tif (this->lastSrReceived != 0)\n\t\t\t{\n\t\t\t\t// Get delay in milliseconds.\n\t\t\t\tauto delayMs = static_cast<uint32_t>(this->shared->GetTimeMs() - this->lastSrReceived);\n\t\t\t\t// Express delay in units of 1/65536 seconds.\n\t\t\t\tuint32_t dlsr = (delayMs / 1000) << 16;\n\n\t\t\t\tdlsr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 };\n\n\t\t\t\treport->SetDelaySinceLastSenderReport(dlsr);\n\t\t\t\treport->SetLastSenderReport(this->lastSrTimestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treport->SetDelaySinceLastSenderReport(0);\n\t\t\t\treport->SetLastSenderReport(0);\n\t\t\t}\n\n\t\t\treturn report;\n\t\t}\n\n\t\tRTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtxRtcpReceiverReport()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (HasRtx())\n\t\t\t{\n\t\t\t\treturn this->rtxStream->GetRtcpReceiverReport();\n\t\t\t}\n\n\t\t\treturn nullptr;\n\t\t}\n\n\t\tvoid RtpStreamRecv::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->lastSrReceived  = this->shared->GetTimeMs();\n\t\t\tthis->lastSrTimestamp = report->GetNtpSec() << 16;\n\t\t\tthis->lastSrTimestamp += report->GetNtpFrac() >> 16;\n\n\t\t\t// Update info about last Sender Report.\n\t\t\tUtils::Time::Ntp ntp{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\t\tntp.seconds   = report->GetNtpSec();\n\t\t\tntp.fractions = report->GetNtpFrac();\n\n\t\t\tthis->lastSenderReportNtpMs = Utils::Time::Ntp2TimeMs(ntp);\n\t\t\tthis->lastSenderReportTs    = report->GetRtpTs();\n\n\t\t\t// Update the score with the current RR.\n\t\t\tUpdateScore();\n\t\t}\n\n\t\tvoid RtpStreamRecv::ReceiveRtxRtcpSenderReport(RTC::RTCP::SenderReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (HasRtx())\n\t\t\t{\n\t\t\t\tthis->rtxStream->ReceiveRtcpSenderReport(report);\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamRecv::ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t/* Calculate RTT. */\n\n\t\t\t// Get the NTP representation of the current timestamp.\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\t\t\tauto ntp             = Utils::Time::TimeMs2Ntp(nowMs);\n\n\t\t\t// Get the compact NTP representation of the current timestamp.\n\t\t\tuint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16;\n\n\t\t\tcompactNtp |= (ntp.fractions & 0xFFFF0000) >> 16;\n\n\t\t\tconst uint32_t lastRr = ssrcInfo->GetLastReceiverReport();\n\t\t\tconst uint32_t dlrr   = ssrcInfo->GetDelaySinceLastReceiverReport();\n\n\t\t\t// RTT in 1/2^16 second fractions.\n\t\t\tuint32_t rtt{ 0 };\n\n\t\t\t// If no Receiver Extended Report was received by the remote endpoint yet,\n\t\t\t// ignore lastRr and dlrr values in the Sender Extended Report.\n\t\t\tif (lastRr && dlrr && (compactNtp > dlrr + lastRr))\n\t\t\t{\n\t\t\t\trtt = compactNtp - dlrr - lastRr;\n\t\t\t}\n\n\t\t\t// RTT in milliseconds.\n\t\t\tthis->rtt = static_cast<float>(rtt >> 16) * 1000;\n\t\t\tthis->rtt += (static_cast<float>(rtt & 0x0000FFFF) / 65536) * 1000;\n\n\t\t\t// Avoid negative RTT value since it doesn't make sense.\n\t\t\tthis->rtt = std::max(this->rtt, 0.0f);\n\n\t\t\t// Tell it to the NackGenerator.\n\t\t\tif (this->params.useNack)\n\t\t\t{\n\t\t\t\tthis->nackGenerator->UpdateRtt(static_cast<uint32_t>(this->rtt));\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamRecv::RequestKeyFrame()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->params.usePli)\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtcp, rtx, \"sending PLI [ssrc:%\" PRIu32 \"]\", GetSsrc());\n\n\t\t\t\t// Sender SSRC should be 0 since there is no media sender involved, but\n\t\t\t\t// some implementations like gstreamer will fail to process it otherwise.\n\t\t\t\tRTC::RTCP::FeedbackPsPliPacket packet(GetSsrc(), GetSsrc());\n\n\t\t\t\tpacket.Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\t\t\tthis->pliCount++;\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tstatic_cast<RTP::RtpStreamRecv::Listener*>(this->listener)\n\t\t\t\t  ->OnRtpStreamSendRtcpPacket(this, &packet);\n\t\t\t}\n\t\t\telse if (this->params.useFir)\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtcp, rtx, \"sending FIR [ssrc:%\" PRIu32 \"]\", GetSsrc());\n\n\t\t\t\t// Sender SSRC should be 0 since there is no media sender involved, but\n\t\t\t\t// some implementations like gstreamer will fail to process it otherwise.\n\t\t\t\tRTC::RTCP::FeedbackPsFirPacket packet(GetSsrc(), GetSsrc());\n\t\t\t\tauto* item = new RTC::RTCP::FeedbackPsFirItem(GetSsrc(), ++this->firSeqNumber);\n\n\t\t\t\tpacket.AddItem(item);\n\t\t\t\tpacket.Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\t\t\tthis->firCount++;\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tstatic_cast<RTP::RtpStreamRecv::Listener*>(this->listener)\n\t\t\t\t  ->OnRtpStreamSendRtcpPacket(this, &packet);\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamRecv::Pause()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->inactivityCheckPeriodicTimer)\n\t\t\t{\n\t\t\t\tthis->inactivityCheckPeriodicTimer->Stop();\n\t\t\t}\n\n\t\t\tif (this->params.useNack)\n\t\t\t{\n\t\t\t\tthis->nackGenerator->Reset();\n\t\t\t}\n\n\t\t\t// Reset jitter.\n\t\t\tthis->transit = 0;\n\t\t\tthis->jitter  = 0;\n\t\t}\n\n\t\tvoid RtpStreamRecv::Resume()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->inactivityCheckPeriodicTimer && !this->inactive)\n\t\t\t{\n\t\t\t\tthis->inactivityCheckPeriodicTimer->Restart();\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamRecv::CalculateJitter(uint32_t rtpTimestamp)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (GetClockRate() == 0u)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// NOTE: Based on https://github.com/versatica/mediasoup/issues/1018.\n\t\t\tauto transit =\n\t\t\t  static_cast<int>((this->shared->GetTimeMs() * GetClockRate() / 1000) - rtpTimestamp);\n\t\t\tint d = transit - this->transit;\n\n\t\t\t// First transit calculation, save and return.\n\t\t\tif (this->transit == 0)\n\t\t\t{\n\t\t\t\tthis->transit = transit;\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->transit = transit;\n\n\t\t\tif (d < 0)\n\t\t\t{\n\t\t\t\td = -d;\n\t\t\t}\n\n\t\t\tthis->jitter += (1. / 16.) * (static_cast<float>(d) - this->jitter);\n\t\t}\n\n\t\tvoid RtpStreamRecv::UpdateScore()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Calculate number of packets expected in this interval.\n\t\t\tconst auto totalExpected = GetExpectedPackets();\n\t\t\tconst uint32_t expected  = totalExpected - this->expectedPriorScore;\n\n\t\t\tthis->expectedPriorScore = totalExpected;\n\n\t\t\t// Calculate number of packets received in this interval.\n\t\t\tconst auto totalReceived = this->mediaTransmissionCounter.GetPacketCount();\n\t\t\tconst uint32_t received  = totalReceived - this->receivedPriorScore;\n\n\t\t\tthis->receivedPriorScore = totalReceived;\n\n\t\t\t// Calculate number of packets lost in this interval.\n\t\t\tuint32_t lost;\n\n\t\t\tif (expected < received)\n\t\t\t{\n\t\t\t\tlost = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlost = expected - received;\n\t\t\t}\n\n\t\t\t// Calculate number of packets repaired in this interval.\n\t\t\tconst auto totalRepaired = this->packetsRepaired;\n\t\t\tuint32_t repaired        = totalRepaired - this->repairedPriorScore;\n\n\t\t\tthis->repairedPriorScore = totalRepaired;\n\n\t\t\t// Calculate number of packets retransmitted in this interval.\n\t\t\tconst auto totatRetransmitted = this->packetsRetransmitted;\n\t\t\tuint32_t retransmitted        = totatRetransmitted - this->retransmittedPriorScore;\n\n\t\t\tthis->retransmittedPriorScore = totatRetransmitted;\n\n\t\t\tif (this->inactive)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// We didn't expect more packets to come.\n\t\t\tif (expected == 0)\n\t\t\t{\n\t\t\t\tRTP::RtpStream::UpdateScore(10);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlost = std::min(lost, received);\n\n\t\t\tif (repaired > lost)\n\t\t\t{\n\t\t\t\tif (HasRtx())\n\t\t\t\t{\n\t\t\t\t\trepaired = lost;\n\t\t\t\t\tretransmitted -= repaired - lost;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tlost = repaired;\n\t\t\t\t}\n\t\t\t}\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"[totalExpected:%\" PRIu32 \", totalReceived:%zu, totalRepaired:%zu\",\n\t\t\t  totalExpected,\n\t\t\t  totalReceived,\n\t\t\t  totalRepaired);\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"fixed values [expected:%\" PRIu32 \", received:%\" PRIu32 \", lost:%\" PRIu32\n\t\t\t  \", repaired:%\" PRIu32 \", retransmitted:%\" PRIu32,\n\t\t\t  expected,\n\t\t\t  received,\n\t\t\t  lost,\n\t\t\t  repaired,\n\t\t\t  retransmitted);\n#endif\n\n\t\t\tauto repairedRatio  = static_cast<float>(repaired) / static_cast<float>(received);\n\t\t\tauto repairedWeight = std::pow(1 / (repairedRatio + 1), 4);\n\n\t\t\tMS_ASSERT(retransmitted >= repaired, \"repaired packets cannot be more than retransmitted ones\");\n\n\t\t\tif (retransmitted > 0)\n\t\t\t{\n\t\t\t\trepairedWeight *= static_cast<float>(repaired) / retransmitted;\n\t\t\t}\n\n\t\t\tlost -= repaired * repairedWeight;\n\n\t\t\tauto deliveredRatio = static_cast<float>(received - lost) / static_cast<float>(received);\n\t\t\tauto score          = static_cast<uint8_t>(std::round(std::pow(deliveredRatio, 4) * 10));\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"[deliveredRatio:%f, repairedRatio:%f, repairedWeight:%f, new lost:%\" PRIu32\n\t\t\t  \", score:%\" PRIu8 \"]\",\n\t\t\t  deliveredRatio,\n\t\t\t  repairedRatio,\n\t\t\t  repairedWeight,\n\t\t\t  lost,\n\t\t\t  score);\n#endif\n\n\t\t\t// Call the parent method for update score.\n\t\t\tRTP::RtpStream::UpdateScore(score);\n\t\t}\n\n\t\tvoid RtpStreamRecv::UserOnSequenceNumberReset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Nothing to do.\n\t\t}\n\n\t\tinline void RtpStreamRecv::OnTimer(TimerHandleInterface* timer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (timer == this->inactivityCheckPeriodicTimer)\n\t\t\t{\n\t\t\t\tthis->inactive = true;\n\n\t\t\t\tif (GetScore() != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_2TAGS(\n\t\t\t\t\t  rtp, score, \"RTP inactivity detected, resetting score to 0 [ssrc:%\" PRIu32 \"]\", GetSsrc());\n\t\t\t\t}\n\n\t\t\t\tResetScore(0, /*notify*/ true);\n\t\t\t}\n\t\t}\n\n\t\tinline void RtpStreamRecv::OnNackGeneratorNackRequired(const std::vector<uint16_t>& seqNumbers)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->params.useNack, \"NACK required but not supported\");\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtx,\n\t\t\t  \"triggering NACK [ssrc:%\" PRIu32 \", first seq:%\" PRIu16 \", num packets:%zu]\",\n\t\t\t  this->params.ssrc,\n\t\t\t  seqNumbers[0],\n\t\t\t  seqNumbers.size());\n\n\t\t\tRTC::RTCP::FeedbackRtpNackPacket packet(0, GetSsrc());\n\n\t\t\tauto it        = seqNumbers.begin();\n\t\t\tconst auto end = seqNumbers.end();\n\t\t\tsize_t numPacketsRequested{ 0 };\n\n\t\t\twhile (it != end)\n\t\t\t{\n\t\t\t\tuint16_t seq;\n\t\t\t\tuint16_t bitmask{ 0 };\n\n\t\t\t\tseq = *it;\n\t\t\t\t++it;\n\n\t\t\t\twhile (it != end)\n\t\t\t\t{\n\t\t\t\t\tconst uint16_t shift = *it - seq - 1;\n\n\t\t\t\t\tif (shift > 15)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tbitmask |= (1 << shift);\n\t\t\t\t\t++it;\n\t\t\t\t}\n\n\t\t\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(seq, bitmask);\n\n\t\t\t\tpacket.AddItem(nackItem);\n\n\t\t\t\tnumPacketsRequested += nackItem->CountRequestedPackets();\n\t\t\t}\n\n\t\t\t// Ensure that the RTCP packet fits into the RTCP buffer.\n\t\t\tif (packet.GetSize() > RTC::RTCP::SerializationBufferSize)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtx, \"cannot send RTCP NACK packet, size too big (%zu bytes)\", packet.GetSize());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->nackCount++;\n\t\t\tthis->nackPacketCount += numPacketsRequested;\n\n\t\t\tpacket.Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\t\t// Notify the listener.\n\t\t\tstatic_cast<RTP::RtpStreamRecv::Listener*>(this->listener)->OnRtpStreamSendRtcpPacket(this, &packet);\n\t\t}\n\n\t\tinline void RtpStreamRecv::OnNackGeneratorKeyFrameRequired()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_TAG(rtx, \"requesting key frame [ssrc:%\" PRIu32 \"]\", this->params.ssrc);\n\n\t\t\tRequestKeyFrame();\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/RtpStreamSend.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::RtpStreamSend\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/RtpStreamSend.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <vector>\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\t// Limit max number of items in the retransmission buffer.\n\t\tstatic constexpr size_t RetransmissionBufferMaxItems{ 2500u };\n\t\t// 17: 16 bit mask + the initial sequence number.\n\t\tstatic constexpr size_t MaxRequestedPackets{ 17u };\n\t\tstatic thread_local std::vector<RTP::RetransmissionBuffer::Item*> RetransmissionContainer(\n\t\t  MaxRequestedPackets + 1);\n\t\tstatic constexpr uint32_t DefaultRtt{ 100u };\n\n\t\t/* Class Static. */\n\n\t\tconst uint32_t RtpStreamSend::MaxRetransmissionDelayForVideoMs{ 2000u };\n\t\tconst uint32_t RtpStreamSend::MaxRetransmissionDelayForAudioMs{ 1000u };\n\n\t\t/* Instance methods. */\n\n\t\tRtpStreamSend::RtpStreamSend(\n\t\t  RTP::RtpStreamSend::Listener* listener,\n\t\t  SharedInterface* shared,\n\t\t  RTP::RtpStream::Params& params,\n\t\t  std::string& mid)\n\t\t  : RTP::RtpStream::RtpStream(listener, shared, params, 10),\n\t\t    mid(mid),\n\t\t    transmissionCounter(shared, /*ignorePaddingOnlyPackets*/ true)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->params.useNack)\n\t\t\t{\n\t\t\t\tuint32_t maxRetransmissionDelayMs{ 0 };\n\n\t\t\t\tswitch (params.mimeType.type)\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::VIDEO:\n\t\t\t\t\t{\n\t\t\t\t\t\tmaxRetransmissionDelayMs = RtpStreamSend::MaxRetransmissionDelayForVideoMs;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpCodecMimeType::Type::AUDIO:\n\t\t\t\t\t{\n\t\t\t\t\t\tmaxRetransmissionDelayMs = RtpStreamSend::MaxRetransmissionDelayForAudioMs;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->retransmissionBuffer = new RTC::RTP::RetransmissionBuffer(\n\t\t\t\t  RetransmissionBufferMaxItems, maxRetransmissionDelayMs, params.clockRate);\n\t\t\t}\n\t\t}\n\n\t\tRtpStreamSend::~RtpStreamSend()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Delete retransmission buffer.\n\t\t\tdelete this->retransmissionBuffer;\n\t\t\tthis->retransmissionBuffer = nullptr;\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtpStream::Stats> RtpStreamSend::FillBufferStats(\n\t\t  flatbuffers::FlatBufferBuilder& builder)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tauto baseStats = RTP::RtpStream::FillBufferStats(builder);\n\t\t\tauto stats     = FBS::RtpStream::CreateSendStats(\n\t\t\t  builder,\n\t\t\t  baseStats,\n\t\t\t  this->transmissionCounter.GetPacketCount(),\n\t\t\t  this->transmissionCounter.GetBytes(),\n\t\t\t  this->transmissionCounter.GetBitrate(nowMs));\n\n\t\t\treturn FBS::RtpStream::CreateStats(builder, FBS::RtpStream::StatsData::SendStats, stats.Union());\n\t\t}\n\n\t\tvoid RtpStreamSend::SetRtx(uint8_t payloadType, uint32_t ssrc)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tRTP::RtpStream::SetRtx(payloadType, ssrc);\n\n\t\t\tthis->rtxSeq = Utils::Crypto::GetRandomUInt<uint16_t>(0u, 0xFFFF);\n\t\t}\n\n\t\tRtpStreamSend::ReceivePacketResult RtpStreamSend::ReceivePacket(\n\t\t  RTP::Packet* packet, const RTP::SharedPacket& sharedPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  packet->GetSsrc() == this->params.ssrc, \"RTP packet SSRC does not match the encodings SSRC\");\n\n\t\t\t// Call the parent method.\n\t\t\tif (!RtpStream::ReceiveStreamPacket(packet))\n\t\t\t{\n\t\t\t\treturn ReceivePacketResult::DISCARDED;\n\t\t\t}\n\n\t\t\tbool stored{ false };\n\n\t\t\t// If NACK is enabled, store the packet into the buffer.\n\t\t\tif (this->retransmissionBuffer)\n\t\t\t{\n\t\t\t\t// Check if the packet is already stored.\n\t\t\t\tif (this->retransmissionBuffer->Get(packet->GetSequenceNumber()))\n\t\t\t\t{\n\t\t\t\t\t// Packet already stored, do not resend it since the receiver will\n\t\t\t\t\t// fail decrypting it and this packet will be considered not received\n\t\t\t\t\t// in the RTCP transport feedback affecting the bandwidth estimation.\n\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"packet already stored in retransmission buffer [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\t\treturn ReceivePacketResult::DISCARDED;\n\t\t\t\t}\n\n\t\t\t\tstored = this->retransmissionBuffer->Insert(packet, sharedPacket);\n\t\t\t}\n\n\t\t\t// Increase transmission counter.\n\t\t\tthis->transmissionCounter.Update(packet);\n\n\t\t\treturn stored ? ReceivePacketResult::ACCEPTED_AND_STORED\n\t\t\t              : ReceivePacketResult::ACCEPTED_AND_NOT_STORED;\n\t\t}\n\n\t\tvoid RtpStreamSend::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->nackCount++;\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Activate liburing usage.\n\t\t\t\tDepLibUring::SetActive();\n\t\t\t}\n#endif\n\n\t\t\tfor (auto it = nackPacket->Begin(); it != nackPacket->End(); ++it)\n\t\t\t{\n\t\t\t\tconst RTC::RTCP::FeedbackRtpNackItem* item = *it;\n\n\t\t\t\tthis->nackPacketCount += item->CountRequestedPackets();\n\n\t\t\t\tFillRetransmissionContainer(item->GetPacketId(), item->GetLostPacketBitmask());\n\n\t\t\t\tfor (auto* item : RetransmissionContainer)\n\t\t\t\t{\n\t\t\t\t\tif (!item)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  item->sharedPacket.HasPacket(),\n\t\t\t\t\t  \"item in retransmission container doesn't contain a packet [ssrc:%\" PRIu32\n\t\t\t\t\t  \", seq:%\" PRIu16 \", timestamp:%\" PRIu32 \"]\",\n\t\t\t\t\t  item->ssrc,\n\t\t\t\t\t  item->sequenceNumber,\n\t\t\t\t\t  item->timestamp);\n\n\t\t\t\t\tauto* packet = item->sharedPacket.GetPacket();\n\n\t\t\t\t\t// Keep the values of the original packet received by the Consumer.\n\t\t\t\t\tauto origSsrc      = packet->GetSsrc();\n\t\t\t\t\tauto origSeq       = packet->GetSequenceNumber();\n\t\t\t\t\tauto origTimestamp = packet->GetTimestamp();\n\t\t\t\t\tauto origMarker    = packet->HasMarker();\n\t\t\t\t\tstd::string origMid;\n\n\t\t\t\t\t// Put correct info into the packet.\n\t\t\t\t\tpacket->SetSsrc(item->ssrc);\n\t\t\t\t\tpacket->SetSequenceNumber(item->sequenceNumber);\n\t\t\t\t\tpacket->SetTimestamp(item->timestamp);\n\t\t\t\t\tpacket->SetMarker(item->marker);\n\n\t\t\t\t\tif (item->encoder != nullptr)\n\t\t\t\t\t{\n\t\t\t\t\t\tpacket->EncodePayload(item->encoder.get());\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update MID RTP extension value.\n\t\t\t\t\tif (!this->mid.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tpacket->ReadMid(origMid);\n\t\t\t\t\t\tpacket->UpdateMid(this->mid);\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we use RTX, encode it.\n\t\t\t\t\tif (HasRtx())\n\t\t\t\t\t{\n\t\t\t\t\t\t// Increment RTX seq.\n\t\t\t\t\t\tthis->rtxSeq++;\n\n\t\t\t\t\t\tpacket->RtxEncode(this->params.rtxPayloadType, this->params.rtxSsrc, this->rtxSeq);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Retransmit the packet.\n\t\t\t\t\tstatic_cast<RTP::RtpStreamSend::Listener*>(this->listener)\n\t\t\t\t\t  ->OnRtpStreamRetransmitRtpPacket(this, packet);\n\n\t\t\t\t\t// Mark the packet as retransmitted.\n\t\t\t\t\tRTP::RtpStream::PacketRetransmitted(packet);\n\n\t\t\t\t\t// Mark the packet as repaired (only if this is the first retransmission).\n\t\t\t\t\tif (item->sentTimes == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tRTP::RtpStream::PacketRepaired(packet);\n\t\t\t\t\t}\n\n\t\t\t\t\t// If we use RTX, restore it.\n\t\t\t\t\tif (HasRtx())\n\t\t\t\t\t{\n\t\t\t\t\t\t// Restore the packet.\n\t\t\t\t\t\tpacket->RtxDecode(RtpStream::GetPayloadType(), item->ssrc);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Restore MID.\n\t\t\t\t\tif (!this->mid.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tpacket->UpdateMid(origMid);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Restore payload.\n\t\t\t\t\tif (item->encoder != nullptr)\n\t\t\t\t\t{\n\t\t\t\t\t\tpacket->RestorePayload();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Restore RTP header fields.\n\t\t\t\t\tpacket->SetSsrc(origSsrc);\n\t\t\t\t\tpacket->SetSequenceNumber(origSeq);\n\t\t\t\t\tpacket->SetTimestamp(origTimestamp);\n\t\t\t\t\tpacket->SetMarker(origMarker);\n\t\t\t\t}\n\t\t\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Submit all prepared submission entries.\n\t\t\t\tDepLibUring::Submit();\n\t\t\t}\n#endif\n\t\t}\n\n\t\tvoid RtpStreamSend::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (messageType)\n\t\t\t{\n\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t\t{\n\t\t\t\t\tthis->pliCount++;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t\t{\n\t\t\t\t\tthis->firCount++;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\tvoid RtpStreamSend::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t/* Calculate RTT. */\n\n\t\t\t// Get the NTP representation of the current timestamp.\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\t\t\tauto ntp             = Utils::Time::TimeMs2Ntp(nowMs);\n\n\t\t\t// Get the compact NTP representation of the current timestamp.\n\t\t\tuint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16;\n\n\t\t\tcompactNtp |= (ntp.fractions & 0xFFFF0000) >> 16;\n\n\t\t\tconst uint32_t lastSr = report->GetLastSenderReport();\n\t\t\tconst uint32_t dlsr   = report->GetDelaySinceLastSenderReport();\n\n\t\t\t// RTT in 1/2^16 second fractions.\n\t\t\tuint32_t rtt{ 0 };\n\n\t\t\t// If no Sender Report was received by the remote endpoint yet, ignore lastSr\n\t\t\t// and dlsr values in the Receiver Report.\n\t\t\tif (lastSr && dlsr && (compactNtp > dlsr + lastSr))\n\t\t\t{\n\t\t\t\trtt = compactNtp - dlsr - lastSr;\n\t\t\t}\n\n\t\t\t// RTT in milliseconds.\n\t\t\tthis->rtt = static_cast<float>(rtt >> 16) * 1000;\n\t\t\tthis->rtt += (static_cast<float>(rtt & 0x0000FFFF) / 65536) * 1000;\n\n\t\t\t// Avoid negative RTT value since it doesn't make sense.\n\t\t\tthis->rtt = std::max(this->rtt, 0.0f);\n\n\t\t\tthis->packetsLost  = report->GetTotalLost();\n\t\t\tthis->fractionLost = report->GetFractionLost();\n\t\t\tthis->jitter       = static_cast<float>(report->GetJitter());\n\n\t\t\t// Update the score with the received RR.\n\t\t\tUpdateScore(report);\n\t\t}\n\n\t\tvoid RtpStreamSend::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->lastRrReceivedMs = this->shared->GetTimeMs();\n\t\t\tthis->lastRrTimestamp  = report->GetNtpSec() << 16;\n\t\t\tthis->lastRrTimestamp += report->GetNtpFrac() >> 16;\n\t\t}\n\n\t\tRTC::RTCP::SenderReport* RtpStreamSend::GetRtcpSenderReport(uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->transmissionCounter.GetPacketCount() == 0u)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto ntp     = Utils::Time::TimeMs2Ntp(nowMs);\n\t\t\tauto* report = new RTC::RTCP::SenderReport();\n\n\t\t\t// Calculate TS difference between now and maxPacketMs.\n\t\t\tauto diffMs = nowMs - this->maxPacketMs;\n\t\t\tauto diffTs = diffMs * GetClockRate() / 1000;\n\n\t\t\treport->SetSsrc(GetSsrc());\n\t\t\treport->SetPacketCount(this->transmissionCounter.GetPacketCount());\n\t\t\treport->SetOctetCount(this->transmissionCounter.GetBytes());\n\t\t\treport->SetNtpSec(ntp.seconds);\n\t\t\treport->SetNtpFrac(ntp.fractions);\n\t\t\treport->SetRtpTs(this->maxPacketTs + diffTs);\n\n\t\t\t// Update info about last Sender Report.\n\t\t\tthis->lastSenderReportNtpMs = nowMs;\n\t\t\tthis->lastSenderReportTs    = this->maxPacketTs + diffTs;\n\n\t\t\treturn report;\n\t\t}\n\n\t\tRTC::RTCP::DelaySinceLastRr::SsrcInfo* RtpStreamSend::GetRtcpXrDelaySinceLastRrSsrcInfo(uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->lastRrReceivedMs == 0u)\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Get delay in milliseconds.\n\t\t\tauto delayMs = static_cast<uint32_t>(nowMs - this->lastRrReceivedMs);\n\t\t\t// Express delay in units of 1/65536 seconds.\n\t\t\tuint32_t dlrr = (delayMs / 1000) << 16;\n\n\t\t\tdlrr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 };\n\n\t\t\tauto* ssrcInfo = new RTC::RTCP::DelaySinceLastRr::SsrcInfo();\n\n\t\t\tssrcInfo->SetSsrc(GetSsrc());\n\t\t\tssrcInfo->SetDelaySinceLastReceiverReport(dlrr);\n\t\t\tssrcInfo->SetLastReceiverReport(this->lastRrTimestamp);\n\n\t\t\treturn ssrcInfo;\n\t\t}\n\n\t\tRTC::RTCP::SdesChunk* RtpStreamSend::GetRtcpSdesChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto& cname = GetCname();\n\t\t\tauto* sdesChunk   = new RTC::RTCP::SdesChunk(GetSsrc());\n\t\t\tauto* sdesItem =\n\t\t\t  new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, cname.size(), cname.c_str());\n\n\t\t\tsdesChunk->AddItem(sdesItem);\n\n\t\t\treturn sdesChunk;\n\t\t}\n\n\t\tvoid RtpStreamSend::Pause()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Clear retransmission buffer.\n\t\t\tif (this->retransmissionBuffer)\n\t\t\t{\n\t\t\t\tthis->retransmissionBuffer->Clear();\n\t\t\t}\n\n\t\t\t// Reset jitter.\n\t\t\tthis->jitter = 0;\n\t\t}\n\n\t\tvoid RtpStreamSend::Resume()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tuint32_t RtpStreamSend::GetBitrate(\n\t\t  uint64_t /*nowMs*/, uint8_t /*spatialLayer*/, uint8_t /*temporalLayer*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ABORT(\"invalid method call\");\n\t\t}\n\n\t\tuint32_t RtpStreamSend::GetSpatialLayerBitrate(uint64_t /*nowMs*/, uint8_t /*spatialLayer*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ABORT(\"invalid method call\");\n\t\t}\n\n\t\tuint32_t RtpStreamSend::GetLayerBitrate(\n\t\t  uint64_t /*nowMs*/, uint8_t /*spatialLayer*/, uint8_t /*temporalLayer*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ABORT(\"invalid method call\");\n\t\t}\n\n\t\t// This method looks for the requested RTP packets and inserts them into the\n\t\t// RetransmissionContainer vector (and sets to null the next position).\n\t\t//\n\t\t// If RTX is used the stored packet will be RTX encoded now (if not already\n\t\t// encoded in a previous resend).\n\t\t//\n\t\t// NOTE: This method doesn't verify whether requested stored packet is too\n\t\t// old, why? Because, if we verified it, we would do it by comparing its\n\t\t// timestamp with the newest one in the retransmission buffer. However we\n\t\t// already clean old packets upon receipt of any new packet (see Insert()\n\t\t// method in RetransmissionBuffer class).\n\t\tvoid RtpStreamSend::FillRetransmissionContainer(uint16_t seq, uint16_t bitmask)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Ensure the container's first element is 0.\n\t\t\tRetransmissionContainer[0] = nullptr;\n\n\t\t\t// If NACK is not supported, exit.\n\t\t\tif (!this->retransmissionBuffer)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(rtx, \"NACK not supported\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Look for each requested packet.\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\t\t\tconst uint16_t rtt   = (this->rtt > 0.0f ? this->rtt : DefaultRtt);\n\t\t\tuint16_t currentSeq  = seq;\n\t\t\tbool requested{ true };\n\t\t\tsize_t containerIdx{ 0 };\n\n\t\t\t// Variables for debugging.\n\t\t\tconst uint16_t origBitmask = bitmask;\n\t\t\tuint16_t sentBitmask{ 0b0000000000000000 };\n\t\t\tbool isFirstPacket{ true };\n\t\t\tbool firstPacketSent{ false };\n\t\t\tuint8_t bitmaskCounter{ 0 };\n\n\t\t\twhile (requested || bitmask != 0)\n\t\t\t{\n\t\t\t\tbool sent = false;\n\n\t\t\t\tif (requested)\n\t\t\t\t{\n\t\t\t\t\tauto* item = this->retransmissionBuffer->Get(currentSeq);\n\n\t\t\t\t\t// Packet not found.\n\t\t\t\t\tif (!item)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Do nothing.\n\t\t\t\t\t}\n\t\t\t\t\t// Don't resent the packet if it was resent in the last RTT ms.\n\t\t\t\t\telse if (item->resentAtMs != 0u && nowMs - item->resentAtMs <= static_cast<uint64_t>(rtt))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtx,\n\t\t\t\t\t\t  \"ignoring retransmission for a packet already resent in the last RTT ms \"\n\t\t\t\t\t\t  \"[seq:%\" PRIu16 \", rtt:%\" PRIu16 \"]\",\n\t\t\t\t\t\t  item->sequenceNumber,\n\t\t\t\t\t\t  rtt);\n\t\t\t\t\t}\n\t\t\t\t\t// Stored packet is valid for retransmission. Resend it.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Save when this packet was resent.\n\t\t\t\t\t\titem->resentAtMs = nowMs;\n\n\t\t\t\t\t\t// Increase the number of times this packet was sent.\n\t\t\t\t\t\titem->sentTimes++;\n\n\t\t\t\t\t\t// Store the item in the container and then increment its index.\n\t\t\t\t\t\tRetransmissionContainer[containerIdx++] = item;\n\n\t\t\t\t\t\tsent = true;\n\n\t\t\t\t\t\tif (isFirstPacket)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfirstPacketSent = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\trequested = (bitmask & 1) != 0;\n\t\t\t\tbitmask >>= 1;\n\t\t\t\tcurrentSeq++;\n\n\t\t\t\tif (!isFirstPacket)\n\t\t\t\t{\n\t\t\t\t\tsentBitmask |= (sent ? 1 : 0) << bitmaskCounter;\n\t\t\t\t\tbitmaskCounter++;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tisFirstPacket = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If not all the requested packets was sent, log it.\n\t\t\tif (!firstPacketSent || origBitmask != sentBitmask)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"could not resend all packets [seq:%\" PRIu16\n\t\t\t\t  \", first:%s, \"\n\t\t\t\t  \"bitmask:\" MS_UINT16_TO_BINARY_PATTERN \", sent bitmask:\" MS_UINT16_TO_BINARY_PATTERN \"]\",\n\t\t\t\t  seq,\n\t\t\t\t  firstPacketSent ? \"yes\" : \"no\",\n\t\t\t\t  MS_UINT16_TO_BINARY(origBitmask),\n\t\t\t\t  MS_UINT16_TO_BINARY(sentBitmask));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"all packets resent [seq:%\" PRIu16 \", bitmask:\" MS_UINT16_TO_BINARY_PATTERN \"]\",\n\t\t\t\t  seq,\n\t\t\t\t  MS_UINT16_TO_BINARY(origBitmask));\n\t\t\t}\n\n\t\t\t// Set the next container element to null.\n\t\t\tRetransmissionContainer[containerIdx] = nullptr;\n\t\t}\n\n\t\tvoid RtpStreamSend::UpdateScore(RTC::RTCP::ReceiverReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Calculate number of packets sent in this interval.\n\t\t\tauto totalSent = this->transmissionCounter.GetPacketCount();\n\t\t\tauto sent      = totalSent - this->sentPriorScore;\n\n\t\t\tthis->sentPriorScore = totalSent;\n\n\t\t\t// Calculate number of packets lost in this interval.\n\t\t\tconst int32_t totalLost = report->GetTotalLost() > 0 ? report->GetTotalLost() : 0;\n\t\t\tuint32_t lost;\n\n\t\t\tif (totalLost < this->lostPriorScore)\n\t\t\t{\n\t\t\t\tlost = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tlost = totalLost - this->lostPriorScore;\n\t\t\t}\n\n\t\t\tthis->lostPriorScore = totalLost;\n\n\t\t\t// Calculate number of packets repaired in this interval.\n\t\t\tauto totalRepaired = this->packetsRepaired;\n\t\t\tuint32_t repaired  = totalRepaired - this->repairedPriorScore;\n\n\t\t\tthis->repairedPriorScore = totalRepaired;\n\n\t\t\t// Calculate number of packets retransmitted in this interval.\n\t\t\tauto totatRetransmitted      = this->packetsRetransmitted;\n\t\t\tconst uint32_t retransmitted = totatRetransmitted - this->retransmittedPriorScore;\n\n\t\t\tthis->retransmittedPriorScore = totatRetransmitted;\n\n\t\t\t// We didn't send any packet.\n\t\t\tif (sent == 0)\n\t\t\t{\n\t\t\t\tRTP::RtpStream::UpdateScore(10);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlost     = std::min<size_t>(lost, sent);\n\t\t\trepaired = std::min(repaired, lost);\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"[totalSent:%zu, totalLost:%\" PRIi32 \", totalRepaired:%zu\",\n\t\t\t  totalSent,\n\t\t\t  totalLost,\n\t\t\t  totalRepaired);\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"fixed values [sent:%zu, lost:%\" PRIu32 \", repaired:%\" PRIu32 \", retransmitted:%\" PRIu32,\n\t\t\t  sent,\n\t\t\t  lost,\n\t\t\t  repaired,\n\t\t\t  retransmitted);\n#endif\n\n\t\t\tauto repairedRatio  = static_cast<float>(repaired) / static_cast<float>(sent);\n\t\t\tauto repairedWeight = std::pow(1 / (repairedRatio + 1), 4);\n\n\t\t\tMS_ASSERT(retransmitted >= repaired, \"repaired packets cannot be more than retransmitted ones\");\n\n\t\t\tif (retransmitted > 0)\n\t\t\t{\n\t\t\t\trepairedWeight *= static_cast<float>(repaired) / retransmitted;\n\t\t\t}\n\n\t\t\tlost -= repaired * repairedWeight;\n\n\t\t\tauto deliveredRatio = static_cast<float>(sent - lost) / static_cast<float>(sent);\n\t\t\tauto score          = static_cast<uint8_t>(std::round(std::pow(deliveredRatio, 4) * 10));\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  score,\n\t\t\t  \"[deliveredRatio:%f, repairedRatio:%f, repairedWeight:%f, new lost:%\" PRIu32\n\t\t\t  \", score:%\" PRIu8 \"]\",\n\t\t\t  deliveredRatio,\n\t\t\t  repairedRatio,\n\t\t\t  repairedWeight,\n\t\t\t  lost,\n\t\t\t  score);\n#endif\n\n\t\t\t// Call the parent method for update score.\n\t\t\tRTP::RtpStream::UpdateScore(score);\n\t\t}\n\n\t\tvoid RtpStreamSend::UserOnSequenceNumberReset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Clear retransmission buffer.\n\t\t\tif (this->retransmissionBuffer)\n\t\t\t{\n\t\t\t\tthis->retransmissionBuffer->Clear();\n\t\t\t}\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/RtxStream.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::RtxStream\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/RtxStream.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr uint16_t MaxDropout{ 3000 };\n\t\tstatic constexpr uint16_t MaxMisorder{ 1500 };\n\t\tstatic constexpr uint32_t RtpSeqMod{ 1 << 16 };\n\n\t\t/* Instance methods. */\n\n\t\tRtxStream::RtxStream(SharedInterface* shared, RTP::RtxStream::Params& params)\n\t\t  : shared(shared), params(params)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  params.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::RTX,\n\t\t\t  \"mimeType.subtype is not RTX\");\n\t\t}\n\n\t\tRtxStream::~RtxStream()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtxStream::RtxDump> RtxStream::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Add params.\n\t\t\tauto params = this->params.FillBuffer(builder);\n\n\t\t\treturn FBS::RtxStream::CreateRtxDump(builder, params);\n\t\t}\n\n\t\tbool RtxStream::ReceivePacket(const RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t seq = packet->GetSequenceNumber();\n\n\t\t\t// If this is the first packet seen, initialize stuff.\n\t\t\tif (!this->started)\n\t\t\t{\n\t\t\t\tInitSeq(seq);\n\n\t\t\t\tthis->started     = true;\n\t\t\t\tthis->maxSeq      = seq - 1;\n\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t}\n\n\t\t\t// If not a valid packet ignore it.\n\t\t\tif (!UpdateSeq(packet))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  rtx,\n\t\t\t\t  \"invalid packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Update highest seen RTP timestamp.\n\t\t\tif (Utils::Number::IsHigherThan<uint32_t>(packet->GetTimestamp(), this->maxPacketTs))\n\t\t\t{\n\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t}\n\n\t\t\t// Increase packet count.\n\t\t\tthis->packetsCount++;\n\n\t\t\treturn true;\n\t\t}\n\n\t\tRTC::RTCP::ReceiverReport* RtxStream::GetRtcpReceiverReport()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* report = new RTC::RTCP::ReceiverReport();\n\n\t\t\treport->SetSsrc(GetSsrc());\n\n\t\t\tconst int32_t prevPacketsLost = this->packetsLost;\n\n\t\t\t// Calculate Packets Expected and Lost.\n\t\t\tauto expected = GetExpectedPackets();\n\n\t\t\tif (expected > this->packetsCount)\n\t\t\t{\n\t\t\t\tthis->packetsLost = expected - this->packetsCount;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->packetsLost = 0u;\n\t\t\t}\n\n\t\t\t// Calculate Fraction Lost.\n\t\t\tconst uint32_t expectedInterval = expected - this->expectedPrior;\n\n\t\t\tthis->expectedPrior = expected;\n\n\t\t\tconst uint32_t receivedInterval = this->packetsCount - this->receivedPrior;\n\n\t\t\tthis->receivedPrior = this->packetsCount;\n\n\t\t\tconst int32_t lostInterval = expectedInterval - receivedInterval;\n\n\t\t\tif (expectedInterval == 0 || lostInterval <= 0)\n\t\t\t{\n\t\t\t\tthis->fractionLost = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->fractionLost = std::round((static_cast<double>(lostInterval << 8) / expectedInterval));\n\t\t\t}\n\n\t\t\tthis->reportedPacketsLost += (this->packetsLost - prevPacketsLost);\n\n\t\t\treport->SetTotalLost(this->reportedPacketsLost);\n\t\t\treport->SetFractionLost(this->fractionLost);\n\n\t\t\t// Fill the rest of the report.\n\t\t\treport->SetLastSeq(static_cast<uint32_t>(this->maxSeq) + this->cycles);\n\n\t\t\t// NOTE: Do not calculate any jitter.\n\t\t\treport->SetJitter(0);\n\n\t\t\tif (this->lastSrReceived != 0)\n\t\t\t{\n\t\t\t\t// Get delay in milliseconds.\n\t\t\t\tconst uint32_t delayMs = this->shared->GetTimeMs() - this->lastSrReceived;\n\t\t\t\t// Express delay in units of 1/65536 seconds.\n\t\t\t\tuint32_t dlsr = (delayMs / 1000) << 16;\n\n\t\t\t\tdlsr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 };\n\n\t\t\t\treport->SetDelaySinceLastSenderReport(dlsr);\n\t\t\t\treport->SetLastSenderReport(this->lastSrTimestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treport->SetDelaySinceLastSenderReport(0);\n\t\t\t\treport->SetLastSenderReport(0);\n\t\t\t}\n\n\t\t\treturn report;\n\t\t}\n\n\t\tvoid RtxStream::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->lastSrReceived  = this->shared->GetTimeMs();\n\t\t\tthis->lastSrTimestamp = report->GetNtpSec() << 16;\n\t\t\tthis->lastSrTimestamp += report->GetNtpFrac() >> 16;\n\t\t}\n\n\t\tbool RtxStream::UpdateSeq(const RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t seq    = packet->GetSequenceNumber();\n\t\t\tconst uint16_t udelta = seq - this->maxSeq;\n\n\t\t\t// If the new packet sequence number is greater than the max seen but not\n\t\t\t// \"so much bigger\", accept it.\n\t\t\t// NOTE: udelta also handles the case of a new cycle, this is:\n\t\t\t//    maxSeq:65536, seq:0 => udelta:1\n\t\t\tif (udelta < MaxDropout)\n\t\t\t{\n\t\t\t\t// In order, with permissible gap.\n\t\t\t\tif (seq < this->maxSeq)\n\t\t\t\t{\n\t\t\t\t\t// Sequence number wrapped: count another 64K cycle.\n\t\t\t\t\tthis->cycles += RtpSeqMod;\n\t\t\t\t}\n\n\t\t\t\tthis->maxSeq = seq;\n\t\t\t}\n\t\t\t// Too old packet received (older than the allowed misorder).\n\t\t\t// Or to new packet (more than acceptable dropout).\n\t\t\telse if (udelta <= RtpSeqMod - MaxMisorder)\n\t\t\t{\n\t\t\t\t// The sequence number made a very large jump. If two sequential packets\n\t\t\t\t// arrive, accept the latter.\n\t\t\t\tif (seq == this->badSeq)\n\t\t\t\t{\n\t\t\t\t\t// Two sequential packets. Assume that the other side restarted without\n\t\t\t\t\t// telling us so just re-sync (i.e., pretend this was the first packet).\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtx,\n\t\t\t\t\t  \"too bad sequence number, re-syncing RTP [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\t\tInitSeq(seq);\n\n\t\t\t\t\tthis->maxPacketTs = packet->GetTimestamp();\n\t\t\t\t\tthis->maxPacketMs = this->shared->GetTimeMs();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtx,\n\t\t\t\t\t  \"bad sequence number, ignoring packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\t\tthis->badSeq = (seq + 1) & (RtpSeqMod - 1);\n\n\t\t\t\t\t// Packet discarded due to late or early arriving.\n\t\t\t\t\tthis->packetsDiscarded++;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Acceptable misorder.\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Do nothing.\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tinline void RtxStream::InitSeq(uint16_t seq)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Initialize/reset RTP counters.\n\t\t\tthis->baseSeq = seq;\n\t\t\tthis->maxSeq  = seq;\n\t\t\tthis->badSeq  = RtpSeqMod + 1; // So seq == badSeq is false.\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::RtxStream::Params> RtxStream::Params::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn FBS::RtxStream::CreateParamsDirect(\n\t\t\t  builder,\n\t\t\t  this->ssrc,\n\t\t\t  this->payloadType,\n\t\t\t  this->mimeType.ToString().c_str(),\n\t\t\t  this->clockRate,\n\t\t\t  this->rrid.c_str(),\n\t\t\t  this->cname.c_str());\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RTP/SharedPacket.cpp",
    "content": "#define MS_CLASS \"RTC::RTP::SharedPacket\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/Serializable.hpp\"\n#include <new> // std::align_val_t{\n\nnamespace RTC\n{\n\tnamespace RTP\n\t{\n\t\t/* Static. */\n\n\t\t// When cloning a RTP packet, a buffer is allocated for it and its length is\n\t\t// the length of the Packet plus this value (in bytes).\n\t\tstatic constexpr size_t PacketBufferLengthIncrement{ 100 };\n\t\t// Callback to pass to every cloned RTP Packet to deallocate its buffer once\n\t\t// the Packet releases its buffer (for example when the Packet is destroyed).\n\t\tstatic thread_local Serializable::BufferReleasedListener PacketBufferReleasedListener =\n\t\t  // NOLINTNEXTLINE(misc-unused-parameters, readability-non-const-parameter)\n\t\t  [](const Serializable* packet, uint8_t* buffer)\n\t\t{\n\t\t\t// NOTE: Needed since we allocated it using\n\t\t\t::operator delete[](buffer, std::align_val_t{ 4 });\n\n#ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE\n\t\t\tSharedPacket::allocatedMemory -= packet->GetBufferLength();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  0,\n\t\t\t  \"[worker.pid:%\" PRIu64\n\t\t\t  \"] [RTC::RTP::SharedPacket] memory deallocated [packet buffer:%zu, total allocated memory:%\" PRIu64\n\t\t\t  \"]\",\n\t\t\t  Logger::Pid,\n\t\t\t  packet->GetBufferLength(),\n\t\t\t  SharedPacket::allocatedMemory);\n#endif\n\t\t};\n\n\t\t/* Class variables. */\n\n#ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE\n\t\tthread_local uint64_t SharedPacket::allocatedMemory{ 0 };\n#endif\n\n\t\t/* Instance methods. */\n\n\t\tSharedPacket::SharedPacket()\n\t\t  : sharedPtr(std::make_shared<std::unique_ptr<RTP::Packet>>(nullptr))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tSharedPacket::SharedPacket(RTP::Packet* packet)\n\t\t  : sharedPtr(std::make_shared<std::unique_ptr<RTP::Packet>>(nullptr))\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (packet)\n\t\t\t{\n\t\t\t\tStorePacket(packet);\n\t\t\t}\n\t\t}\n\n\t\tvoid SharedPacket::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SharedPacket>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  has packet: %s\", HasPacket() ? \"yes\" : \"no\");\n\t\t\tif (HasPacket())\n\t\t\t{\n\t\t\t\tconst auto* packet = GetPacket();\n\n\t\t\t\tpacket->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SharedPacket>\");\n\t\t}\n\n\t\tvoid SharedPacket::Assign(RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (packet)\n\t\t\t{\n\t\t\t\tStorePacket(packet);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->sharedPtr->reset(nullptr);\n\t\t\t}\n\t\t}\n\n\t\tvoid SharedPacket::Reset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sharedPtr->reset(nullptr);\n\t\t}\n\n\t\tvoid SharedPacket::AssertSamePacket(const RTP::Packet* otherPacket) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* packet = GetPacket();\n\n\t\t\tif (!packet && !otherPacket)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if (packet && !otherPacket)\n\t\t\t{\n\t\t\t\tMS_ABORT(\"there is a packet in sharedPacket but given otherPacket doesn't have value\");\n\t\t\t}\n\t\t\telse if (!packet && otherPacket)\n\t\t\t{\n\t\t\t\tMS_ABORT(\"there is no packet in sharedPacket but given otherPacket has value\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  packet->GetSsrc() == otherPacket->GetSsrc(),\n\t\t\t\t  \"SSRC %\" PRIu32 \" in packet in sharedPacket != SSRC %\" PRIu32 \" in otherPacket\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  otherPacket->GetSsrc());\n\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  packet->GetSequenceNumber() == otherPacket->GetSequenceNumber(),\n\t\t\t\t  \"seq %\" PRIu16 \" in packet in sharedPacket != seq %\" PRIu16 \" in otherPacket\",\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  otherPacket->GetSequenceNumber());\n\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  packet->GetTimestamp() == otherPacket->GetTimestamp(),\n\t\t\t\t  \"timestamp %\" PRIu16 \" in packet in sharedPacket != timestamp %\" PRIu16 \" in otherPacket\",\n\t\t\t\t  packet->GetTimestamp(),\n\t\t\t\t  otherPacket->GetTimestamp());\n\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  packet->GetLength() == otherPacket->GetLength(),\n\t\t\t\t  \"length %zu of packet in sharedPacket != length %zu of otherPacket\",\n\t\t\t\t  packet->GetLength(),\n\t\t\t\t  otherPacket->GetLength());\n\t\t\t}\n\t\t}\n\n\t\tvoid SharedPacket::StorePacket(RTP::Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t bufferLength = packet->GetLength() + PacketBufferLengthIncrement;\n\n\t\t\t// NOTE: Buffer must be 4-byte aligned since RTP packet parsing casts it to\n\t\t\t// structs (e.g. FixedHeader, HeaderExtension) that require 4-byte alignment.\n\t\t\tauto* buffer = static_cast<uint8_t*>(::operator new[](bufferLength, std::align_val_t{ 4 }));\n\t\t\tauto* clonedPacket = packet->Clone(buffer, bufferLength);\n\n\t\t\t// Set a listener in the Packet to deallocate its buffer once the Packet\n\t\t\t// is destroyed or releases its internal buffer.\n\t\t\tclonedPacket->SetBufferReleasedListener(std::addressof(PacketBufferReleasedListener));\n\n\t\t\tthis->sharedPtr->reset(clonedPacket);\n\n#ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE\n\t\t\tSharedPacket::allocatedMemory += bufferLength;\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  0,\n\t\t\t  \"[worker.pid:%\" PRIu64\n\t\t\t  \"] [RTC::RTP::SharedPacket] memory allocated [packet buffer:%zu, total allocated memory:%\" PRIu64\n\t\t\t  \"]\",\n\t\t\t  Logger::Pid,\n\t\t\t  clonedPacket->GetBufferLength(),\n\t\t\t  SharedPacket::allocatedMemory);\n#endif\n\t\t}\n\t} // namespace RTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RateCalculator.cpp",
    "content": "#define MS_CLASS \"RTC::RateCalculator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RateCalculator.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cmath>   // std::trunc()\n#include <cstring> // std::memset()\n\nnamespace RTC\n{\n\tRateCalculator::RateCalculator(size_t windowSizeMs, float scale, uint16_t windowItems)\n\t  : windowSizeMs(windowSizeMs),\n\t    scale(scale),\n\t    windowItems(windowItems),\n\t    itemSizeMs(std::max(windowSizeMs / windowItems, size_t{ 1 }))\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->buffer.resize(windowItems);\n\n\t\tstd::memset(\n\t\t  static_cast<void*>(std::addressof(this->buffer.front())),\n\t\t  0,\n\t\t  sizeof(BufferItem) * this->buffer.size());\n\t}\n\n\tvoid RateCalculator::Update(size_t size, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ignore too old data. Should never happen.\n\t\tif (this->oldestItemStartTime.has_value() && Utils::Number::IsLowerThan<uint64_t>(nowMs, *this->oldestItemStartTime))\n\t\t{\n\t\t\tMS_WARN_DEV(\"nowMs < this->oldestItemStartTime, should never happen\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Increase bytes.\n\t\tthis->bytes += size;\n\n\t\tRemoveOldData(nowMs);\n\n\t\t// If the elapsed time from the newest item start time is greater than the\n\t\t// item size (in milliseconds), increase the item index.\n\t\tif (\n\t\t  this->newestItemIndex < 0 || !this->newestItemStartTime.has_value() ||\n\t\t  Utils::Number::IsHigherOrEqualThan<uint64_t>(\n\t\t    nowMs - *this->newestItemStartTime, this->itemSizeMs))\n\t\t{\n\t\t\tthis->newestItemIndex++;\n\t\t\tthis->newestItemStartTime = nowMs;\n\n\t\t\tif (this->newestItemIndex >= this->windowItems)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"this->newestItemIndex >= this->windowItems, setting this->newestItemIndex = 0\");\n\n\t\t\t\tthis->newestItemIndex = 0;\n\t\t\t}\n\n\t\t\t// Advance oldestItemIndex if buffer is full.\n\t\t\t// NOTE: This avoids a crash:\n\t\t\t//   https://github.com/versatica/mediasoup/issues/1316\n\t\t\tif (this->newestItemIndex == this->oldestItemIndex && this->oldestItemIndex != -1)\n\t\t\t{\n\t\t\t\tif (++this->oldestItemIndex >= this->windowItems)\n\t\t\t\t{\n\t\t\t\t\tthis->oldestItemIndex = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->newestItemIndex != this->oldestItemIndex || this->oldestItemIndex == -1,\n\t\t\t  \"newest index overlaps with the oldest one [newestItemIndex:%\" PRIi32\n\t\t\t  \", oldestItemIndex:%\" PRIi32 \"]\",\n\t\t\t  this->newestItemIndex,\n\t\t\t  this->oldestItemIndex);\n\n\t\t\t// Set the newest item.\n\t\t\tBufferItem& item = this->buffer[this->newestItemIndex];\n\t\t\titem.count       = size;\n\t\t\titem.time        = nowMs;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Update the newest item.\n\t\t\tBufferItem& item = this->buffer[this->newestItemIndex];\n\t\t\titem.count += size;\n\t\t}\n\n\t\t// Set the oldest item index and time, if not set.\n\t\tif (this->oldestItemIndex < 0)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"this->oldestItemIndex < 0, setting this->oldestItemIndex and this->oldestItemStartTime\");\n\n\t\t\tthis->oldestItemIndex     = this->newestItemIndex;\n\t\t\tthis->oldestItemStartTime = nowMs;\n\t\t}\n\n\t\tthis->totalCount += size;\n\n\t\t// Reset lastRate and lastTime so GetRate() will calculate rate again even\n\t\t// if called with same now in the same loop iteration.\n\t\tthis->lastRate = 0;\n\t\tthis->lastTime = std::nullopt;\n\t}\n\n\tuint32_t RateCalculator::GetRate(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->lastTime.has_value() && nowMs == *this->lastTime)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"nowMs == this->lastTime, early return\");\n\n\t\t\treturn this->lastRate;\n\t\t}\n\n\t\tRemoveOldData(nowMs);\n\n\t\tconst float scale = this->scale / this->windowSizeMs;\n\n\t\tthis->lastTime = nowMs;\n\t\tthis->lastRate = static_cast<uint32_t>(std::trunc((this->totalCount * scale) + 0.5f));\n\n\t\treturn this->lastRate;\n\t}\n\n\tvoid RateCalculator::Reset()\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::memset(\n\t\t  static_cast<void*>(std::addressof(this->buffer.front())),\n\t\t  0,\n\t\t  sizeof(BufferItem) * this->buffer.size());\n\n\t\tthis->newestItemStartTime = std::nullopt;\n\t\tthis->newestItemIndex     = -1;\n\t\tthis->oldestItemStartTime = std::nullopt;\n\t\tthis->oldestItemIndex     = -1;\n\t\tthis->totalCount          = 0u;\n\t\tthis->lastRate            = 0u;\n\t\tthis->lastTime            = std::nullopt;\n\t}\n\n\tvoid RateCalculator::RemoveOldData(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->oldestItemStartTime.has_value())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// No item set.\n\t\tif (this->newestItemIndex < 0 || this->oldestItemIndex < 0)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint64_t newOldestTime = nowMs - this->windowSizeMs;\n\n\t\t// Oldest item already removed.\n\t\tif (Utils::Number::IsLowerThan<uint64_t>(newOldestTime, *this->oldestItemStartTime))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// A whole window size time has elapsed since last entry. Reset the buffer.\n\t\tif (\n\t\t  this->newestItemStartTime.has_value() &&\n\t\t  Utils::Number::IsHigherOrEqualThan<uint64_t>(newOldestTime, *this->newestItemStartTime))\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"newOldestTime >= this->newestItemStartTime, resetting the buffer\");\n\n\t\t\tReset();\n\n\t\t\treturn;\n\t\t}\n\n\t\twhile (Utils::Number::IsHigherOrEqualThan<uint64_t>(newOldestTime, *this->oldestItemStartTime))\n\t\t{\n\t\t\tBufferItem& oldestItem = this->buffer[this->oldestItemIndex];\n\t\t\tthis->totalCount -= oldestItem.count;\n\t\t\toldestItem.count = 0u;\n\t\t\toldestItem.time  = 0u;\n\n\t\t\tif (++this->oldestItemIndex >= this->windowItems)\n\t\t\t{\n\t\t\t\tthis->oldestItemIndex = 0;\n\t\t\t}\n\n\t\t\tconst BufferItem& newOldestItem = this->buffer[this->oldestItemIndex];\n\t\t\tthis->oldestItemStartTime       = newOldestItem.time;\n\t\t}\n\t}\n\n\tvoid RtpDataCounter::Update(const RTC::RTP::Packet* packet)\n\t{\n\t\tthis->packets++;\n\n\t\tif (!this->ignorePaddingOnlyPackets || packet->GetPayloadLength() > 0)\n\t\t{\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->rate.Update(packet->GetLength(), nowMs);\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/Router.cpp",
    "content": "#define MS_CLASS \"RTC::Router\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Router.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/ActiveSpeakerObserver.hpp\"\n#include \"RTC/AudioLevelObserver.hpp\"\n#include \"RTC/DirectTransport.hpp\"\n#include \"RTC/PipeTransport.hpp\"\n#include \"RTC/PlainTransport.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include \"RTC/WebRtcTransport.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRouter::Router(SharedInterface* shared, const std::string& id, Listener* listener)\n\t  : id(id), shared(shared), listener(listener)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*ChannelNotificationHandler*/ nullptr);\n\t}\n\n\tRouter::~Router()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\t// Close all Transports.\n\t\tfor (auto& kv : this->mapTransports)\n\t\t{\n\t\t\tauto* transport = kv.second;\n\n\t\t\tdelete transport;\n\t\t}\n\t\tthis->mapTransports.clear();\n\n\t\t// Close all RtpObservers.\n\t\tfor (auto& kv : this->mapRtpObservers)\n\t\t{\n\t\t\tauto* rtpObserver = kv.second;\n\n\t\t\tdelete rtpObserver;\n\t\t}\n\t\tthis->mapRtpObservers.clear();\n\n\t\t// Clear other maps.\n\t\tthis->mapProducerConsumers.clear();\n\t\tthis->mapConsumerProducer.clear();\n\t\tthis->mapProducerRtpObservers.clear();\n\t\tthis->mapProducers.clear();\n\t\tthis->mapDataProducerDataConsumers.clear();\n\t\tthis->mapDataConsumerDataProducer.clear();\n\t\tthis->mapDataProducers.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Router::DumpResponse> Router::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add transportIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> transportIds;\n\t\ttransportIds.reserve(this->mapTransports.size());\n\n\t\tfor (const auto& kv : this->mapTransports)\n\t\t{\n\t\t\tconst auto& transportId = kv.first;\n\n\t\t\ttransportIds.push_back(builder.CreateString(transportId));\n\t\t}\n\n\t\t// Add rtpObserverIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> rtpObserverIds;\n\t\trtpObserverIds.reserve(this->mapRtpObservers.size());\n\n\t\tfor (const auto& kv : this->mapRtpObservers)\n\t\t{\n\t\t\tconst auto& rtpObserverId = kv.first;\n\n\t\t\trtpObserverIds.push_back(builder.CreateString(rtpObserverId));\n\t\t}\n\n\t\t// Add mapProducerIdConsumerIds.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringStringArray>> mapProducerIdConsumerIds;\n\t\tmapProducerIdConsumerIds.reserve(this->mapProducerConsumers.size());\n\n\t\tfor (const auto& kv : this->mapProducerConsumers)\n\t\t{\n\t\t\tauto* producer        = kv.first;\n\t\t\tconst auto& consumers = kv.second;\n\n\t\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> consumerIds;\n\t\t\tconsumerIds.reserve(consumers.size());\n\n\t\t\tfor (auto* consumer : consumers)\n\t\t\t{\n\t\t\t\tconsumerIds.emplace_back(builder.CreateString(consumer->id));\n\t\t\t}\n\n\t\t\tmapProducerIdConsumerIds.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringArrayDirect(builder, producer->id.c_str(), &consumerIds));\n\t\t}\n\n\t\t// Add mapConsumerIdProducerId.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringString>> mapConsumerIdProducerId;\n\t\tmapConsumerIdProducerId.reserve(this->mapConsumerProducer.size());\n\n\t\tfor (const auto& kv : this->mapConsumerProducer)\n\t\t{\n\t\t\tauto* consumer = kv.first;\n\t\t\tauto* producer = kv.second;\n\n\t\t\tmapConsumerIdProducerId.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringDirect(builder, consumer->id.c_str(), producer->id.c_str()));\n\t\t}\n\n\t\t// Add mapProducerIdObserverIds.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringStringArray>> mapProducerIdObserverIds;\n\t\tmapProducerIdObserverIds.reserve(this->mapProducerRtpObservers.size());\n\n\t\tfor (const auto& kv : this->mapProducerRtpObservers)\n\t\t{\n\t\t\tauto* producer           = kv.first;\n\t\t\tconst auto& rtpObservers = kv.second;\n\n\t\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> observerIds;\n\t\t\tobserverIds.reserve(rtpObservers.size());\n\n\t\t\tfor (auto* rtpObserver : rtpObservers)\n\t\t\t{\n\t\t\t\tobserverIds.emplace_back(builder.CreateString(rtpObserver->id));\n\t\t\t}\n\n\t\t\tmapProducerIdObserverIds.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringArrayDirect(builder, producer->id.c_str(), &observerIds));\n\t\t}\n\n\t\t// Add mapDataProducerIdDataConsumerIds.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringStringArray>> mapDataProducerIdDataConsumerIds;\n\t\tmapDataProducerIdDataConsumerIds.reserve(this->mapDataProducerDataConsumers.size());\n\n\t\tfor (const auto& kv : this->mapDataProducerDataConsumers)\n\t\t{\n\t\t\tauto* dataProducer        = kv.first;\n\t\t\tconst auto& dataConsumers = kv.second;\n\n\t\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> dataConsumerIds;\n\t\t\tdataConsumerIds.reserve(dataConsumers.size());\n\n\t\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t\t{\n\t\t\t\tdataConsumerIds.emplace_back(builder.CreateString(dataConsumer->id));\n\t\t\t}\n\n\t\t\tmapDataProducerIdDataConsumerIds.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringArrayDirect(\n\t\t\t    builder, dataProducer->id.c_str(), &dataConsumerIds));\n\t\t}\n\n\t\t// Add mapDataConsumerIdDataProducerId.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringString>> mapDataConsumerIdDataProducerId;\n\t\tmapDataConsumerIdDataProducerId.reserve(this->mapDataConsumerDataProducer.size());\n\n\t\tfor (const auto& kv : this->mapDataConsumerDataProducer)\n\t\t{\n\t\t\tauto* dataConsumer = kv.first;\n\t\t\tauto* dataProducer = kv.second;\n\n\t\t\tmapDataConsumerIdDataProducerId.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringDirect(\n\t\t\t    builder, dataConsumer->id.c_str(), dataProducer->id.c_str()));\n\t\t}\n\n\t\treturn FBS::Router::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  &transportIds,\n\t\t  &rtpObserverIds,\n\t\t  &mapProducerIdConsumerIds,\n\t\t  &mapConsumerIdProducerId,\n\t\t  &mapProducerIdObserverIds,\n\t\t  &mapDataProducerIdDataConsumerIds,\n\t\t  &mapDataConsumerIdDataProducerId);\n\t}\n\n\tvoid Router::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Router_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_WEBRTCTRANSPORT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreateWebRtcTransportRequest>();\n\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoTransport(transportId);\n\n\t\t\t\t// This may throw.\n\t\t\t\tauto* webRtcTransport =\n\t\t\t\t  new RTC::WebRtcTransport(this->shared, transportId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapTransports[transportId] = webRtcTransport;\n\n\t\t\t\tMS_DEBUG_DEV(\"WebRtcTransport created [transportId:%s]\", transportId.c_str());\n\n\t\t\t\tauto dumpOffset = webRtcTransport->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreateWebRtcTransportRequest>();\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoTransport(transportId);\n\n\t\t\t\tconst auto* options    = body->options();\n\t\t\t\tconst auto* listenInfo = options->listen_as<FBS::WebRtcTransport::ListenServer>();\n\n\t\t\t\tauto webRtcServerId = listenInfo->webRtcServerId()->str();\n\n\t\t\t\tauto* webRtcServer = this->listener->OnRouterNeedWebRtcServer(this, webRtcServerId);\n\n\t\t\t\tif (!webRtcServer)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"wrong webRtcServerId (no associated WebRtcServer found)\");\n\t\t\t\t}\n\n\t\t\t\tauto iceCandidates = webRtcServer->GetIceCandidates(\n\t\t\t\t  options->enableUdp(), options->enableTcp(), options->preferUdp(), options->preferTcp());\n\n\t\t\t\t// This may throw.\n\t\t\t\tauto* webRtcTransport = new RTC::WebRtcTransport(\n\t\t\t\t  this->shared, transportId, this, webRtcServer, iceCandidates, options);\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapTransports[transportId] = webRtcTransport;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"WebRtcTransport with WebRtcServer created [transportId:%s]\", transportId.c_str());\n\n\t\t\t\tauto dumpOffset = webRtcTransport->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_PLAINTRANSPORT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreatePlainTransportRequest>();\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoTransport(transportId);\n\n\t\t\t\tauto* plainTransport =\n\t\t\t\t  new RTC::PlainTransport(this->shared, transportId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapTransports[transportId] = plainTransport;\n\n\t\t\t\tMS_DEBUG_DEV(\"PlainTransport created [transportId:%s]\", transportId.c_str());\n\n\t\t\t\tauto dumpOffset = plainTransport->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PlainTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_PIPETRANSPORT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreatePipeTransportRequest>();\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoTransport(transportId);\n\n\t\t\t\tauto* pipeTransport =\n\t\t\t\t  new RTC::PipeTransport(this->shared, transportId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapTransports[transportId] = pipeTransport;\n\n\t\t\t\tMS_DEBUG_DEV(\"PipeTransport created [transportId:%s]\", transportId.c_str());\n\n\t\t\t\tauto dumpOffset = pipeTransport->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::PipeTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_DIRECTTRANSPORT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreateDirectTransportRequest>();\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoTransport(transportId);\n\n\t\t\t\tauto* directTransport =\n\t\t\t\t  new RTC::DirectTransport(this->shared, transportId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapTransports[transportId] = directTransport;\n\n\t\t\t\tMS_DEBUG_DEV(\"DirectTransport created [transportId:%s]\", transportId.c_str());\n\n\t\t\t\tauto dumpOffset = directTransport->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DirectTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_ACTIVESPEAKEROBSERVER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CreateActiveSpeakerObserverRequest>();\n\t\t\t\tauto rtpObserverId = body->rtpObserverId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoRtpObserver(rtpObserverId);\n\n\t\t\t\tauto* activeSpeakerObserver =\n\t\t\t\t  new RTC::ActiveSpeakerObserver(this->shared, rtpObserverId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapRtpObservers[rtpObserverId] = activeSpeakerObserver;\n\n\t\t\t\tMS_DEBUG_DEV(\"ActiveSpeakerObserver created [rtpObserverId:%s]\", rtpObserverId.c_str());\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CREATE_AUDIOLEVELOBSERVER:\n\t\t\t{\n\t\t\t\tconst auto* body   = request->data->body_as<FBS::Router::CreateAudioLevelObserverRequest>();\n\t\t\t\tauto rtpObserverId = body->rtpObserverId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoRtpObserver(rtpObserverId);\n\n\t\t\t\tauto* audioLevelObserver =\n\t\t\t\t  new RTC::AudioLevelObserver(this->shared, rtpObserverId, this, body->options());\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapRtpObservers[rtpObserverId] = audioLevelObserver;\n\n\t\t\t\tMS_DEBUG_DEV(\"AudioLevelObserver created [rtpObserverId:%s]\", rtpObserverId.c_str());\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CLOSE_TRANSPORT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Router::CloseTransportRequest>();\n\t\t\t\tauto transportId = body->transportId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::Transport* transport = GetTransportById(transportId);\n\n\t\t\t\t// Tell the Transport to close all its Producers and Consumers so it will\n\t\t\t\t// notify us about their closures.\n\t\t\t\ttransport->CloseProducersAndConsumers();\n\n\t\t\t\t// Remove it from the map.\n\t\t\t\tthis->mapTransports.erase(transport->id);\n\n\t\t\t\tMS_DEBUG_DEV(\"Transport closed [transportId:%s]\", transport->id.c_str());\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete transport;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::ROUTER_CLOSE_RTPOBSERVER:\n\t\t\t{\n\t\t\t\tconst auto* body   = request->data->body_as<FBS::Router::CloseRtpObserverRequest>();\n\t\t\t\tauto rtpObserverId = body->rtpObserverId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::RtpObserver* rtpObserver = GetRtpObserverById(rtpObserverId);\n\n\t\t\t\t// Remove it from the map.\n\t\t\t\tthis->mapRtpObservers.erase(rtpObserver->id);\n\n\t\t\t\t// Iterate all entries in mapProducerRtpObservers and remove the closed one.\n\t\t\t\tfor (auto& kv : this->mapProducerRtpObservers)\n\t\t\t\t{\n\t\t\t\t\tauto& rtpObservers = kv.second;\n\n\t\t\t\t\trtpObservers.erase(rtpObserver);\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\"RtpObserver closed [rtpObserverId:%s]\", rtpObserver->id.c_str());\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete rtpObserver;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method\");\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Router::CheckNoTransport(const std::string& transportId) const\n\t{\n\t\tif (this->mapTransports.find(transportId) != this->mapTransports.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"a Transport with same id already exists\");\n\t\t}\n\t}\n\n\tvoid Router::CheckNoRtpObserver(const std::string& rtpObserverId) const\n\t{\n\t\tif (this->mapRtpObservers.find(rtpObserverId) != this->mapRtpObservers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"an RtpObserver with same id already exists\");\n\t\t}\n\t}\n\n\tRTC::Transport* Router::GetTransportById(const std::string& transportId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapTransports.find(transportId);\n\n\t\tif (this->mapTransports.find(transportId) == this->mapTransports.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Transport not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tRTC::RtpObserver* Router::GetRtpObserverById(const std::string& rtpObserverId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapRtpObservers.find(rtpObserverId);\n\n\t\tif (this->mapRtpObservers.find(rtpObserverId) == this->mapRtpObservers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"RtpObserver not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tvoid Router::OnTransportNewProducer(RTC::Transport* /*transport*/, RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->mapProducerConsumers.find(producer) == this->mapProducerConsumers.end(),\n\t\t  \"Producer already present in mapProducerConsumers\");\n\n\t\tif (this->mapProducers.find(producer->id) != this->mapProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Producer already present in mapProducers [producerId:%s]\", producer->id.c_str());\n\t\t}\n\n\t\t// Insert the Producer in the maps.\n\t\tthis->mapProducers[producer->id] = producer;\n\t\tthis->mapProducerConsumers[producer];\n\t\tthis->mapProducerRtpObservers[producer];\n\t}\n\n\tvoid Router::OnTransportProducerClosed(RTC::Transport* /*transport*/, RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapProducerConsumersIt    = this->mapProducerConsumers.find(producer);\n\t\tauto mapProducersIt            = this->mapProducers.find(producer->id);\n\t\tauto mapProducerRtpObserversIt = this->mapProducerRtpObservers.find(producer);\n\n\t\tMS_ASSERT(\n\t\t  mapProducerConsumersIt != this->mapProducerConsumers.end(),\n\t\t  \"Producer not present in mapProducerConsumers\");\n\t\tMS_ASSERT(mapProducersIt != this->mapProducers.end(), \"Producer not present in mapProducers\");\n\t\tMS_ASSERT(\n\t\t  mapProducerRtpObserversIt != this->mapProducerRtpObservers.end(),\n\t\t  \"Producer not present in mapProducerRtpObservers\");\n\n\t\t// Close all Consumers associated to the closed Producer.\n\t\tauto& consumers = mapProducerConsumersIt->second;\n\n\t\t// NOTE: While iterating the set of Consumers, we call ProducerClosed() on each\n\t\t// one, which will end calling Router::OnTransportConsumerProducerClosed(),\n\t\t// which will remove the Consumer from mapConsumerProducer but won't remove the\n\t\t// closed Consumer from the set of Consumers in mapProducerConsumers (here will\n\t\t// erase the complete entry in that map).\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\t// Call consumer->ProducerClosed() so the Consumer will notify the Node process,\n\t\t\t// will notify its Transport, and its Transport will delete the Consumer.\n\t\t\tconsumer->ProducerClosed();\n\t\t}\n\n\t\t// Tell all RtpObservers that the Producer has been closed.\n\t\tauto& rtpObservers = mapProducerRtpObserversIt->second;\n\n\t\tfor (auto* rtpObserver : rtpObservers)\n\t\t{\n\t\t\trtpObserver->RemoveProducer(producer);\n\t\t}\n\n\t\t// Remove the Producer from the maps.\n\t\tthis->mapProducers.erase(mapProducersIt);\n\t\tthis->mapProducerConsumers.erase(mapProducerConsumersIt);\n\t\tthis->mapProducerRtpObservers.erase(mapProducerRtpObserversIt);\n\t}\n\n\tvoid Router::OnTransportProducerPaused(RTC::Transport* /*transport*/, RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->ProducerPaused();\n\t\t}\n\n\t\tauto it = this->mapProducerRtpObservers.find(producer);\n\n\t\tif (it != this->mapProducerRtpObservers.end())\n\t\t{\n\t\t\tauto& rtpObservers = it->second;\n\n\t\t\tfor (auto* rtpObserver : rtpObservers)\n\t\t\t{\n\t\t\t\trtpObserver->ProducerPaused(producer);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Router::OnTransportProducerResumed(RTC::Transport* /*transport*/, RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->ProducerResumed();\n\t\t}\n\n\t\tauto it = this->mapProducerRtpObservers.find(producer);\n\n\t\tif (it != this->mapProducerRtpObservers.end())\n\t\t{\n\t\t\tauto& rtpObservers = it->second;\n\n\t\t\tfor (auto* rtpObserver : rtpObservers)\n\t\t\t{\n\t\t\t\trtpObserver->ProducerResumed(producer);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Router::OnTransportProducerNewRtpStream(\n\t  RTC::Transport* /*transport*/,\n\t  RTC::Producer* producer,\n\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t  uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->ProducerNewRtpStream(rtpStream, mappedSsrc);\n\t\t}\n\t}\n\n\tvoid Router::OnTransportProducerRtpStreamScore(\n\t  RTC::Transport* /*transport*/,\n\t  RTC::Producer* producer,\n\t  RTC::RTP::RtpStreamRecv* rtpStream,\n\t  uint8_t score,\n\t  uint8_t previousScore)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->ProducerRtpStreamScore(rtpStream, score, previousScore);\n\t\t}\n\t}\n\n\tvoid Router::OnTransportProducerRtcpSenderReport(\n\t  RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->ProducerRtcpSenderReport(rtpStream, first);\n\t\t}\n\t}\n\n\tvoid Router::OnTransportProducerRtpPacketReceived(\n\t  RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.routerId = this->id;\n#endif\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tif (!consumers.empty())\n\t\t{\n\t\t\t// Cloned ref-counted packet that will be filled for as long as needed\n\t\t\t// avoiding multiple allocations unless absolutely necessary.\n\t\t\t// Clone only happens if needed and only once.\n\t\t\tRTC::RTP::SharedPacket sharedPacket;\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Activate liburing usage.\n\t\t\t\tDepLibUring::SetActive();\n\t\t\t}\n#endif\n\n\t\t\tfor (auto* consumer : consumers)\n\t\t\t{\n\t\t\t\t// Update MID RTP extension value.\n\t\t\t\tconst auto& mid = consumer->GetRtpParameters().mid;\n\n\t\t\t\tif (!mid.empty())\n\t\t\t\t{\n\t\t\t\t\tpacket->UpdateMid(mid);\n\t\t\t\t}\n\n\t\t\t\tconsumer->SendRtpPacket(packet, sharedPacket);\n\n\t\t\t\t// Assert that, if this Consumer filled sharedPacket or it was already\n\t\t\t\t// filled, both packet and sharedPacket are the very same RTP packet.\n\t\t\t\tif (sharedPacket.HasPacket())\n\t\t\t\t{\n\t\t\t\t\tsharedPacket.AssertSamePacket(packet);\n\t\t\t\t}\n\t\t\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Submit all prepared submission entries.\n\t\t\t\tDepLibUring::Submit();\n\t\t\t}\n#endif\n\t\t}\n\n\t\tauto it = this->mapProducerRtpObservers.find(producer);\n\n\t\tif (it != this->mapProducerRtpObservers.end())\n\t\t{\n\t\t\tauto& rtpObservers = it->second;\n\n\t\t\tfor (auto* rtpObserver : rtpObservers)\n\t\t\t{\n\t\t\t\trtpObserver->ReceiveRtpPacket(producer, packet);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Router::OnTransportNeedWorstRemoteFractionLost(\n\t  RTC::Transport* /*transport*/,\n\t  RTC::Producer* producer,\n\t  uint32_t mappedSsrc,\n\t  uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tfor (auto* consumer : consumers)\n\t\t{\n\t\t\tconsumer->NeedWorstRemoteFractionLost(mappedSsrc, worstRemoteFractionLost);\n\t\t}\n\t}\n\n\tvoid Router::OnTransportNewConsumer(\n\t  RTC::Transport* /*transport*/, RTC::Consumer* consumer, const std::string& producerId)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapProducersIt = this->mapProducers.find(producerId);\n\n\t\tif (mapProducersIt == this->mapProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Producer not found [producerId:%s]\", producerId.c_str());\n\t\t}\n\n\t\tauto* producer              = mapProducersIt->second;\n\t\tauto mapProducerConsumersIt = this->mapProducerConsumers.find(producer);\n\n\t\tMS_ASSERT(\n\t\t  mapProducerConsumersIt != this->mapProducerConsumers.end(),\n\t\t  \"Producer not present in mapProducerConsumers\");\n\t\tMS_ASSERT(\n\t\t  this->mapConsumerProducer.find(consumer) == this->mapConsumerProducer.end(),\n\t\t  \"Consumer already present in mapConsumerProducer\");\n\n\t\t// Update the Consumer status based on the Producer status.\n\t\tif (producer->IsPaused())\n\t\t{\n\t\t\tconsumer->ProducerPaused();\n\t\t}\n\n\t\t// Insert the Consumer in the maps.\n\t\tauto& consumers = mapProducerConsumersIt->second;\n\n\t\tconsumers.insert(consumer);\n\t\tthis->mapConsumerProducer[consumer] = producer;\n\n\t\t// Get all streams in the Producer and provide the Consumer with them.\n\t\tfor (const auto& kv : producer->GetRtpStreams())\n\t\t{\n\t\t\tauto* rtpStream           = kv.first;\n\t\t\tconst uint32_t mappedSsrc = kv.second;\n\n\t\t\tconsumer->ProducerRtpStream(rtpStream, mappedSsrc);\n\t\t}\n\n\t\t// Provide the Consumer with the scores of all streams in the Producer.\n\t\tconsumer->ProducerRtpStreamScores(producer->GetRtpStreamScores());\n\t}\n\n\tvoid Router::OnTransportConsumerClosed(RTC::Transport* /*transport*/, RTC::Consumer* consumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE:\n\t\t// This callback is called when the Consumer has been closed but its Producer\n\t\t// remains alive, so the entry in mapProducerConsumers still exists and must\n\t\t// be removed.\n\n\t\tauto mapConsumerProducerIt = this->mapConsumerProducer.find(consumer);\n\n\t\tMS_ASSERT(\n\t\t  mapConsumerProducerIt != this->mapConsumerProducer.end(),\n\t\t  \"Consumer not present in mapConsumerProducer\");\n\n\t\t// Get the associated Producer.\n\t\tauto* producer = mapConsumerProducerIt->second;\n\n\t\tMS_ASSERT(\n\t\t  this->mapProducerConsumers.find(producer) != this->mapProducerConsumers.end(),\n\t\t  \"Producer not present in mapProducerConsumers\");\n\n\t\t// Remove the Consumer from the set of Consumers of the Producer.\n\t\tauto& consumers = this->mapProducerConsumers.at(producer);\n\n\t\tconsumers.erase(consumer);\n\n\t\t// Remove the Consumer from the map.\n\t\tthis->mapConsumerProducer.erase(mapConsumerProducerIt);\n\t}\n\n\tvoid Router::OnTransportConsumerProducerClosed(RTC::Transport* /*transport*/, RTC::Consumer* consumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE:\n\t\t// This callback is called when the Consumer has been closed because its\n\t\t// Producer was closed, so the entry in mapProducerConsumers has already been\n\t\t// removed.\n\n\t\tauto mapConsumerProducerIt = this->mapConsumerProducer.find(consumer);\n\n\t\tMS_ASSERT(\n\t\t  mapConsumerProducerIt != this->mapConsumerProducer.end(),\n\t\t  \"Consumer not present in mapConsumerProducer\");\n\n\t\t// Remove the Consumer from the map.\n\t\tthis->mapConsumerProducer.erase(mapConsumerProducerIt);\n\t}\n\n\tvoid Router::OnTransportConsumerKeyFrameRequested(\n\t  RTC::Transport* /*transport*/, RTC::Consumer* consumer, uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto* producer = this->mapConsumerProducer.at(consumer);\n\n\t\tproducer->RequestKeyFrame(mappedSsrc);\n\t}\n\n\tvoid Router::OnTransportNewDataProducer(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->mapDataProducerDataConsumers.find(dataProducer) ==\n\t\t    this->mapDataProducerDataConsumers.end(),\n\t\t  \"DataProducer already present in mapDataProducerDataConsumers\");\n\n\t\tif (this->mapDataProducers.find(dataProducer->id) != this->mapDataProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\n\t\t\t  \"DataProducer already present in mapDataProducers [dataProducerId:%s]\",\n\t\t\t  dataProducer->id.c_str());\n\t\t}\n\n\t\t// Insert the DataProducer in the maps.\n\t\tthis->mapDataProducers[dataProducer->id] = dataProducer;\n\t\tthis->mapDataProducerDataConsumers[dataProducer];\n\t}\n\n\tvoid Router::OnTransportDataProducerClosed(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapDataProducerDataConsumersIt = this->mapDataProducerDataConsumers.find(dataProducer);\n\t\tauto mapDataProducersIt             = this->mapDataProducers.find(dataProducer->id);\n\n\t\tMS_ASSERT(\n\t\t  mapDataProducerDataConsumersIt != this->mapDataProducerDataConsumers.end(),\n\t\t  \"DataProducer not present in mapDataProducerDataConsumers\");\n\t\tMS_ASSERT(\n\t\t  mapDataProducersIt != this->mapDataProducers.end(),\n\t\t  \"DataProducer not present in mapDataProducers\");\n\n\t\t// Close all DataConsumers associated to the closed DataProducer.\n\t\tauto& dataConsumers = mapDataProducerDataConsumersIt->second;\n\n\t\t// NOTE: While iterating the set of DataConsumers, we call DataProducerClosed()\n\t\t// on each one, which will end calling\n\t\t// Router::OnTransportDataConsumerDataProducerClosed(), which will remove the\n\t\t// DataConsumer from mapDataConsumerDataProducer but won't remove the closed\n\t\t// DataConsumer from the set of DataConsumers in mapDataProducerDataConsumers\n\t\t// (here will erase the complete entry in that map).\n\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t{\n\t\t\t// Call dataConsumer->DataProducerClosed() so the DataConsumer will notify the Node\n\t\t\t// process, will notify its Transport, and its Transport will delete the DataConsumer.\n\t\t\tdataConsumer->DataProducerClosed();\n\t\t}\n\n\t\t// Remove the DataProducer from the maps.\n\t\tthis->mapDataProducers.erase(mapDataProducersIt);\n\t\tthis->mapDataProducerDataConsumers.erase(mapDataProducerDataConsumersIt);\n\t}\n\n\tvoid Router::OnTransportDataProducerPaused(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);\n\n\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t{\n\t\t\tdataConsumer->DataProducerPaused();\n\t\t}\n\t}\n\n\tvoid Router::OnTransportDataProducerResumed(\n\t  RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);\n\n\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t{\n\t\t\tdataConsumer->DataProducerResumed();\n\t\t}\n\t}\n\n\t// TODO: SCTP: Remove when we migrate to the new SCTP stack.\n\tvoid Router::OnTransportDataProducerMessageReceived(\n\t  RTC::Transport* /*transport*/,\n\t  RTC::DataProducer* dataProducer,\n\t  const uint8_t* msg,\n\t  size_t len,\n\t  uint32_t ppid,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);\n\n\t\tif (!dataConsumers.empty())\n\t\t{\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Activate liburing usage.\n\t\t\t\t// The effective sending could be synchronous, thus we would send those\n\t\t\t\t// messages within a single system call.\n\t\t\t\tDepLibUring::SetActive();\n\t\t\t}\n#endif\n\n\t\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t\t{\n\t\t\t\tdataConsumer->SendMessage(msg, len, ppid, subchannels, requiredSubchannel);\n\t\t\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Submit all prepared submission entries.\n\t\t\t\tDepLibUring::Submit();\n\t\t\t}\n#endif\n\t\t}\n\t}\n\n\tvoid Router::OnTransportDataProducerMessageReceived(\n\t  RTC::Transport* /*transport*/,\n\t  RTC::DataProducer* dataProducer,\n\t  RTC::SCTP::Message message,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);\n\n\t\tif (!dataConsumers.empty())\n\t\t{\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Activate liburing usage.\n\t\t\t\t// The effective sending could be synchronous, thus we would send those\n\t\t\t\t// messages within a single system call.\n\t\t\t\tDepLibUring::SetActive();\n\t\t\t}\n#endif\n\n\t\t\tconst auto numDataConsumers = dataConsumers.size();\n\n\t\t\tfor (auto* dataConsumer : dataConsumers)\n\t\t\t{\n\t\t\t\tconst uint16_t streamId =\n\t\t\t\t  (dataConsumer->GetType() == DataConsumer::Type::SCTP\n\t\t\t\t     ? dataConsumer->GetSctpStreamParameters().streamId\n\t\t\t\t     : 0);\n\n\t\t\t\tif (numDataConsumers == 1)\n\t\t\t\t{\n\t\t\t\t\t// We must update the Message`s `streamId` for each destination\n\t\t\t\t\t// DataConsumer.\n\t\t\t\t\t// NOTE: clang-tidy doesn't understand that we are only doing this\n\t\t\t\t\t// once in the original `message`.\n\t\t\t\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\t\t\t\tmessage.SetStreamId(streamId);\n\n\t\t\t\t\tdataConsumer->SendMessage(std::move(message), subchannels, requiredSubchannel);\n\t\t\t\t}\n\t\t\t\t// NOTE: Here we are cloning the Message before passing it to each\n\t\t\t\t// DataConsumer because each DataConsumer will pass std::move(message)\n\t\t\t\t// internally to its SCTP Association.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tauto clonedMessage = message.Clone();\n\n\t\t\t\t\t// We must update the Message`s `streamId` for each destination\n\t\t\t\t\t// DataConsumer.\n\t\t\t\t\tclonedMessage.SetStreamId(streamId);\n\n\t\t\t\t\tdataConsumer->SendMessage(std::move(clonedMessage), subchannels, requiredSubchannel);\n\t\t\t\t}\n\t\t\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\t\tif (DepLibUring::IsEnabled())\n\t\t\t{\n\t\t\t\t// Submit all prepared submission entries.\n\t\t\t\tDepLibUring::Submit();\n\t\t\t}\n#endif\n\t\t}\n\t}\n\n\tvoid Router::OnTransportNewDataConsumer(\n\t  RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer, std::string& dataProducerId)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapDataProducersIt = this->mapDataProducers.find(dataProducerId);\n\n\t\tif (mapDataProducersIt == this->mapDataProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"DataProducer not found [dataProducerId:%s]\", dataProducerId.c_str());\n\t\t}\n\n\t\tauto* dataProducer                  = mapDataProducersIt->second;\n\t\tauto mapDataProducerDataConsumersIt = this->mapDataProducerDataConsumers.find(dataProducer);\n\n\t\tMS_ASSERT(\n\t\t  mapDataProducerDataConsumersIt != this->mapDataProducerDataConsumers.end(),\n\t\t  \"DataProducer not present in mapDataProducerDataConsumers\");\n\t\tMS_ASSERT(\n\t\t  this->mapDataConsumerDataProducer.find(dataConsumer) == this->mapDataConsumerDataProducer.end(),\n\t\t  \"DataConsumer already present in mapDataConsumerDataProducer\");\n\n\t\t// Update the DataConsumer status based on the DataProducer status.\n\t\tif (dataProducer->IsPaused())\n\t\t{\n\t\t\tdataConsumer->DataProducerPaused();\n\t\t}\n\n\t\t// Insert the DataConsumer in the maps.\n\t\tauto& dataConsumers = mapDataProducerDataConsumersIt->second;\n\n\t\tdataConsumers.insert(dataConsumer);\n\t\tthis->mapDataConsumerDataProducer[dataConsumer] = dataProducer;\n\t}\n\n\tvoid Router::OnTransportDataConsumerClosed(RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE:\n\t\t// This callback is called when the DataConsumer has been closed but its DataProducer\n\t\t// remains alive, so the entry in mapDataProducerDataConsumers still exists and must\n\t\t// be removed.\n\n\t\tauto mapDataConsumerDataProducerIt = this->mapDataConsumerDataProducer.find(dataConsumer);\n\n\t\tMS_ASSERT(\n\t\t  mapDataConsumerDataProducerIt != this->mapDataConsumerDataProducer.end(),\n\t\t  \"DataConsumer not present in mapDataConsumerDataProducer\");\n\n\t\t// Get the associated DataProducer.\n\t\tauto* dataProducer = mapDataConsumerDataProducerIt->second;\n\n\t\tMS_ASSERT(\n\t\t  this->mapDataProducerDataConsumers.find(dataProducer) !=\n\t\t    this->mapDataProducerDataConsumers.end(),\n\t\t  \"DataProducer not present in mapDataProducerDataConsumers\");\n\n\t\t// Remove the DataConsumer from the set of DataConsumers of the DataProducer.\n\t\tauto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer);\n\n\t\tdataConsumers.erase(dataConsumer);\n\n\t\t// Remove the DataConsumer from the map.\n\t\tthis->mapDataConsumerDataProducer.erase(mapDataConsumerDataProducerIt);\n\t}\n\n\tvoid Router::OnTransportDataConsumerDataProducerClosed(\n\t  RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE:\n\t\t// This callback is called when the DataConsumer has been closed because its\n\t\t// DataProducer was closed, so the entry in mapDataProducerDataConsumers has already\n\t\t// been removed.\n\n\t\tauto mapDataConsumerDataProducerIt = this->mapDataConsumerDataProducer.find(dataConsumer);\n\n\t\tMS_ASSERT(\n\t\t  mapDataConsumerDataProducerIt != this->mapDataConsumerDataProducer.end(),\n\t\t  \"DataConsumer not present in mapDataConsumerDataProducer\");\n\n\t\t// Remove the DataConsumer from the map.\n\t\tthis->mapDataConsumerDataProducer.erase(mapDataConsumerDataProducerIt);\n\t}\n\n\tvoid Router::OnTransportListenServerClosed(RTC::Transport* transport)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->mapTransports.find(transport->id) != this->mapTransports.end(),\n\t\t  \"Transport not present in mapTransports\");\n\n\t\t// Tell the Transport to close all its Producers and Consumers so it will\n\t\t// notify us about their closures.\n\t\ttransport->CloseProducersAndConsumers();\n\n\t\t// Remove it from the map.\n\t\tthis->mapTransports.erase(transport->id);\n\n\t\t// Delete it.\n\t\tdelete transport;\n\t}\n\n\tvoid Router::OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer)\n\t{\n\t\t// Add to the map.\n\t\tthis->mapProducerRtpObservers[producer].insert(rtpObserver);\n\t}\n\n\tvoid Router::OnRtpObserverRemoveProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer)\n\t{\n\t\t// Remove from the map.\n\t\tthis->mapProducerRtpObservers[producer].erase(rtpObserver);\n\t}\n\n\tRTC::Producer* Router::RtpObserverGetProducer(\n\t  RTC::RtpObserver* /* rtpObserver */, const std::string& id)\n\t{\n\t\tauto it = this->mapProducers.find(id);\n\n\t\tif (it == this->mapProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Producer not found\");\n\t\t}\n\n\t\tRTC::Producer* producer = it->second;\n\n\t\treturn producer;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtcLogger.cpp",
    "content": "#define MS_CLASS \"RTC::RtcLogger\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RtcLogger.hpp\"\n#include \"Logger.hpp\"\n#include <sstream>\n\nnamespace RTC\n{\n\tnamespace RtcLogger\n\t{\n\t\t// clang-format off\n\t\tconst absl::flat_hash_map<RtpPacket::DiscardReason, std::string> RtpPacket::DiscardReason2String = {\n\t\t\t{ RtpPacket::DiscardReason::NONE,                                    \"None\"                               },\n\t\t\t{ RtpPacket::DiscardReason::PRODUCER_NOT_FOUND,                      \"ProducerNotFound\"                   },\n\t\t\t{ RtpPacket::DiscardReason::RECV_RTP_STREAM_NOT_FOUND,               \"RecvRtpStreamNotFound\"              },\n\t\t\t{ RtpPacket::DiscardReason::RECV_RTP_STREAM_DISCARDED,               \"RecvRtpStreamDiscarded\"             },\n\t\t\t{ RtpPacket::DiscardReason::RECV_RTP_RTX_STREAM_DISCARDED,           \"RecvRtpRtxStreamDiscarded\"          },\n\t\t\t{ RtpPacket::DiscardReason::CONSUMER_INACTIVE,                       \"ConsumerInactive\"                   },\n\t\t\t{ RtpPacket::DiscardReason::INVALID_TARGET_LAYER,                    \"InvalidTargetLayer\"                 },\n\t\t\t{ RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE,                \"UnsupportedPayloadType\"             },\n\t\t\t{ RtpPacket::DiscardReason::NOT_A_KEYFRAME,                          \"NotAKeyframe\"                       },\n\t\t\t{ RtpPacket::DiscardReason::EMPTY_PAYLOAD,                           \"EmptyPayload\"                       },\n\t\t\t{ RtpPacket::DiscardReason::SPATIAL_LAYER_MISMATCH,                  \"SpatialLayerMismatch\"               },\n\t\t\t{ RtpPacket::DiscardReason::PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH, \"PacketPreviousToSpatialLayerSwitch\" },\n\t\t\t{ RtpPacket::DiscardReason::DROPPED_BY_CODEC,                        \"DroppedByCodec\"                     },\n\t\t\t{ RtpPacket::DiscardReason::TOO_HIGH_TIMESTAMP_EXTRA_NEEDED,         \"TooHighTimestampExtraNeeded\"},\n\t\t\t{ RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED, \"SendRtpStreamDiscarded\"}\n\t\t};\n\t\t// clang-format on\n\n\t\tvoid RtpPacket::Sent()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->discarded = false;\n\n\t\t\tLog();\n\t\t\tClear();\n\t\t}\n\n\t\tvoid RtpPacket::Discarded(DiscardReason discardReason)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->discarded     = true;\n\t\t\tthis->discardReason = discardReason;\n\n\t\t\tLog();\n\t\t\tClear();\n\t\t}\n\n\t\tvoid RtpPacket::Log() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::stringstream ss;\n\n\t\t\tss << \"{\";\n\t\t\tss << \"\\\"timestamp\\\": \" << this->timestamp;\n\n\t\t\tif (!this->recvTransportId.empty())\n\t\t\t{\n\t\t\t\tss << R\"(, \"recvTransportId\": \")\" << this->recvTransportId << \"\\\"\";\n\t\t\t}\n\t\t\tif (!this->sendTransportId.empty())\n\t\t\t{\n\t\t\t\tss << R\"(, \"sendTransportId\": \")\" << this->sendTransportId << \"\\\"\";\n\t\t\t}\n\t\t\tif (!this->routerId.empty())\n\t\t\t{\n\t\t\t\tss << R\"(, \"routerId\": \")\" << this->routerId << \"\\\"\";\n\t\t\t}\n\t\t\tif (!this->producerId.empty())\n\t\t\t{\n\t\t\t\tss << R\"(, \"producerId\": \")\" << this->producerId << \"\\\"\";\n\t\t\t}\n\t\t\tif (!this->consumerId.empty())\n\t\t\t{\n\t\t\t\tss << R\"(, \"consumerId\": \")\" << this->consumerId << \"\\\"\";\n\t\t\t}\n\n\t\t\tss << \", \\\"recvRtpTimestamp\\\": \" << this->recvRtpTimestamp;\n\t\t\tss << \", \\\"sendRtpTimestamp\\\": \" << this->sendRtpTimestamp;\n\t\t\tss << \", \\\"recvSeqNumber\\\": \" << this->recvSeqNumber;\n\t\t\tss << \", \\\"sendSeqNumber\\\": \" << this->sendSeqNumber;\n\t\t\tss << \", \\\"discarded\\\": \" << (this->discarded ? \"true\" : \"false\");\n\t\t\tss << \", \\\"discardReason\\\": '\" << RtpPacket::DiscardReason2String.at(this->discardReason)\n\t\t\t   << \"'\";\n\t\t\tss << \"}\";\n\n\t\t\tMS_DUMP_CLEAN(0, \"%s\", ss.str().c_str());\n\t\t}\n\n\t\tvoid RtpPacket::Clear()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sendTransportId = {};\n\t\t\tthis->routerId        = {};\n\t\t\tthis->producerId      = {};\n\t\t\tthis->sendSeqNumber   = { 0 };\n\t\t\tthis->discarded       = { false };\n\t\t\tthis->discardReason   = { DiscardReason::NONE };\n\t\t}\n\t} // namespace RtcLogger\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/Parameters.cpp",
    "content": "#define MS_CLASS \"RTC::Parameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Parameters.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::Parameter>> Parameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::Parameter>> parameters;\n\n\t\tfor (const auto& kv : this->mapKeyValues)\n\t\t{\n\t\t\tconst auto& key   = kv.first;\n\t\t\tconst auto& value = kv.second;\n\n\t\t\tflatbuffers::Offset<FBS::RtpParameters::Parameter> parameter;\n\n\t\t\tswitch (value.type)\n\t\t\t{\n\t\t\t\tcase Value::Type::BOOLEAN:\n\t\t\t\t{\n\t\t\t\t\tauto valueOffset = FBS::RtpParameters::CreateBoolean(builder, value.booleanValue);\n\n\t\t\t\t\tparameter = FBS::RtpParameters::CreateParameterDirect(\n\t\t\t\t\t  builder, key.c_str(), FBS::RtpParameters::Value::Boolean, valueOffset.Union());\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Value::Type::INTEGER:\n\t\t\t\t{\n\t\t\t\t\tauto valueOffset = FBS::RtpParameters::CreateInteger32(builder, value.integerValue);\n\n\t\t\t\t\tparameters.emplace_back(\n\t\t\t\t\t  FBS::RtpParameters::CreateParameterDirect(\n\t\t\t\t\t    builder, key.c_str(), FBS::RtpParameters::Value::Integer32, valueOffset.Union()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Value::Type::DOUBLE:\n\t\t\t\t{\n\t\t\t\t\tauto valueOffset = FBS::RtpParameters::CreateDouble(builder, value.doubleValue);\n\n\t\t\t\t\tparameters.emplace_back(\n\t\t\t\t\t  FBS::RtpParameters::CreateParameterDirect(\n\t\t\t\t\t    builder, key.c_str(), FBS::RtpParameters::Value::Double, valueOffset.Union()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Value::Type::STRING:\n\t\t\t\t{\n\t\t\t\t\tauto valueOffset =\n\t\t\t\t\t  FBS::RtpParameters::CreateStringDirect(builder, value.stringValue.c_str());\n\n\t\t\t\t\tparameters.emplace_back(\n\t\t\t\t\t  FBS::RtpParameters::CreateParameterDirect(\n\t\t\t\t\t    builder, key.c_str(), FBS::RtpParameters::Value::String, valueOffset.Union()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Value::Type::ARRAY_OF_INTEGERS:\n\t\t\t\t{\n\t\t\t\t\tauto valueOffset =\n\t\t\t\t\t  FBS::RtpParameters::CreateInteger32ArrayDirect(builder, &value.arrayOfIntegers);\n\n\t\t\t\t\tparameters.emplace_back(\n\t\t\t\t\t  FBS::RtpParameters::CreateParameterDirect(\n\t\t\t\t\t    builder, key.c_str(), FBS::RtpParameters::Value::Integer32Array, valueOffset.Union()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn parameters;\n\t}\n\n\tvoid Parameters::Set(const flatbuffers::Vector<flatbuffers::Offset<FBS::RtpParameters::Parameter>>* data)\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (const auto* parameter : *data)\n\t\t{\n\t\t\tconst auto key = parameter->name()->str();\n\n\t\t\tswitch (parameter->value_type())\n\t\t\t{\n\t\t\t\tcase FBS::RtpParameters::Value::Boolean:\n\t\t\t\t{\n\t\t\t\t\tthis->mapKeyValues.emplace(key, Value(parameter->value_as_Boolean()->value() != 0));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FBS::RtpParameters::Value::Integer32:\n\t\t\t\t{\n\t\t\t\t\tthis->mapKeyValues.emplace(key, Value(parameter->value_as_Integer32()->value()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FBS::RtpParameters::Value::Double:\n\t\t\t\t{\n\t\t\t\t\tthis->mapKeyValues.emplace(key, Value(parameter->value_as_Double()->value()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FBS::RtpParameters::Value::String:\n\t\t\t\t{\n\t\t\t\t\tthis->mapKeyValues.emplace(key, Value(parameter->value_as_String()->value()->str()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase FBS::RtpParameters::Value::Integer32Array:\n\t\t\t\t{\n\t\t\t\t\tthis->mapKeyValues.emplace(key, Value(parameter->value_as_Integer32Array()->value()));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:; // Just ignore other value types.\n\t\t\t}\n\t\t}\n\t}\n\n\tbool Parameters::HasBoolean(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::BOOLEAN;\n\t}\n\n\tbool Parameters::HasInteger(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::INTEGER;\n\t}\n\n\tbool Parameters::HasPositiveInteger(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::INTEGER && value.integerValue >= 0;\n\t}\n\n\tbool Parameters::HasDouble(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::DOUBLE;\n\t}\n\n\tbool Parameters::HasString(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::STRING;\n\t}\n\n\tbool Parameters::HasArrayOfIntegers(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.type == Value::Type::ARRAY_OF_INTEGERS;\n\t}\n\n\tbool Parameters::IncludesInteger(const std::string& key, int32_t integer) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tif (it == this->mapKeyValues.end())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tconst auto& value = it->second;\n\t\tconst auto& array = value.arrayOfIntegers;\n\n\t\treturn std::find(array.begin(), array.end(), integer) != array.end();\n\t}\n\n\tbool Parameters::GetBoolean(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tMS_ASSERT(it != this->mapKeyValues.end(), \"key does not exist [key:%s]\", key.c_str());\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.booleanValue;\n\t}\n\n\tint32_t Parameters::GetInteger(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tMS_ASSERT(it != this->mapKeyValues.end(), \"key does not exist [key:%s]\", key.c_str());\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.integerValue;\n\t}\n\n\tdouble Parameters::GetDouble(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tMS_ASSERT(it != this->mapKeyValues.end(), \"key does not exist [key:%s]\", key.c_str());\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.doubleValue;\n\t}\n\n\tconst std::string& Parameters::GetString(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tMS_ASSERT(it != this->mapKeyValues.end(), \"key does not exist [key:%s]\", key.c_str());\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.stringValue;\n\t}\n\n\tconst std::vector<int32_t>& Parameters::GetArrayOfIntegers(const std::string& key) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapKeyValues.find(key);\n\n\t\tMS_ASSERT(it != this->mapKeyValues.end(), \"key does not exist [key:%s]\", key.c_str());\n\n\t\tconst auto& value = it->second;\n\n\t\treturn value.arrayOfIntegers;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtcpFeedback.cpp",
    "content": "#define MS_CLASS \"RTC::RtcpFeedback\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtcpFeedback::RtcpFeedback(const FBS::RtpParameters::RtcpFeedback* data)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->type = data->type()->str();\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtcpFeedback::VT_PARAMETER))\n\t\t{\n\t\t\tthis->parameter = data->parameter()->str();\n\t\t}\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtcpFeedback> RtcpFeedback::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::RtpParameters::CreateRtcpFeedbackDirect(\n\t\t  builder, this->type.c_str(), this->parameter.empty() ? nullptr : this->parameter.c_str());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtcpParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtcpParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtcpParameters::RtcpParameters(const FBS::RtpParameters::RtcpParameters* data)\n\t{\n\t\tMS_TRACE();\n\n\t\t// cname is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtcpParameters::VT_CNAME))\n\t\t{\n\t\t\tthis->cname = data->cname()->str();\n\t\t}\n\n\t\t// reducedSize is optional, default value is true.\n\t\tthis->reducedSize = data->reducedSize();\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtcpParameters> RtcpParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::RtpParameters::CreateRtcpParametersDirect(\n\t\t  builder, this->cname.c_str(), this->reducedSize);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpCodecMimeType.cpp",
    "content": "#define MS_CLASS \"RTC::RtpCodecMimeType\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Class variables. */\n\n\t// clang-format off\n\tconst absl::flat_hash_map<std::string, RtpCodecMimeType::Type> RtpCodecMimeType::String2Type =\n\t{\n\t\t{ \"audio\", RtpCodecMimeType::Type::AUDIO },\n\t\t{ \"video\", RtpCodecMimeType::Type::VIDEO }\n\t};\n\tconst absl::flat_hash_map<RtpCodecMimeType::Type, std::string> RtpCodecMimeType::Type2String =\n\t{\n\t\t{ RtpCodecMimeType::Type::AUDIO, \"audio\" },\n\t\t{ RtpCodecMimeType::Type::VIDEO, \"video\" }\n\t};\n\tconst absl::flat_hash_map<std::string, RtpCodecMimeType::Subtype> RtpCodecMimeType::String2Subtype =\n\t{\n\t\t// Audio codecs:\n\t\t{ \"opus\",            RtpCodecMimeType::Subtype::OPUS            },\n\t\t{ \"multiopus\",       RtpCodecMimeType::Subtype::MULTIOPUS       },\n\t\t{ \"pcma\",            RtpCodecMimeType::Subtype::PCMA            },\n\t\t{ \"pcmu\",            RtpCodecMimeType::Subtype::PCMU            },\n\t\t{ \"isac\",            RtpCodecMimeType::Subtype::ISAC            },\n\t\t{ \"g722\",            RtpCodecMimeType::Subtype::G722            },\n\t\t{ \"ilbc\",            RtpCodecMimeType::Subtype::ILBC            },\n\t\t{ \"silk\",            RtpCodecMimeType::Subtype::SILK            },\n\t\t// Video codecs:\n\t\t{ \"vp8\",             RtpCodecMimeType::Subtype::VP8             },\n\t\t{ \"vp9\",             RtpCodecMimeType::Subtype::VP9             },\n\t\t{ \"h264\",            RtpCodecMimeType::Subtype::H264            },\n\t\t{ \"av1\",             RtpCodecMimeType::Subtype::AV1             },\n\t\t// Complementary codecs:\n\t\t{ \"cn\",              RtpCodecMimeType::Subtype::CN              },\n\t\t{ \"telephone-event\", RtpCodecMimeType::Subtype::TELEPHONE_EVENT },\n\t\t// Feature codecs:\n\t\t{ \"rtx\",             RtpCodecMimeType::Subtype::RTX             },\n\t\t{ \"ulpfec\",          RtpCodecMimeType::Subtype::ULPFEC          },\n\t\t{ \"flexfec\",         RtpCodecMimeType::Subtype::FLEXFEC         },\n\t\t{ \"x-ulpfecuc\",      RtpCodecMimeType::Subtype::X_ULPFECUC      },\n\t\t{ \"red\",             RtpCodecMimeType::Subtype::RED             }\n\t};\n\tconst absl::flat_hash_map<RtpCodecMimeType::Subtype, std::string> RtpCodecMimeType::Subtype2String =\n\t{\n\t\t// Audio codecs:\n\t\t{ RtpCodecMimeType::Subtype::OPUS,            \"opus\"            },\n\t\t{ RtpCodecMimeType::Subtype::MULTIOPUS,       \"multiopus\"       },\n\t\t{ RtpCodecMimeType::Subtype::PCMA,            \"PCMA\"            },\n\t\t{ RtpCodecMimeType::Subtype::PCMU,            \"PCMU\"            },\n\t\t{ RtpCodecMimeType::Subtype::ISAC,            \"ISAC\"            },\n\t\t{ RtpCodecMimeType::Subtype::G722,            \"G722\"            },\n\t\t{ RtpCodecMimeType::Subtype::ILBC,            \"iLBC\"            },\n\t\t{ RtpCodecMimeType::Subtype::SILK,            \"SILK\"            },\n\t\t// Video codecs:\n\t\t{ RtpCodecMimeType::Subtype::VP8,             \"VP8\"             },\n\t\t{ RtpCodecMimeType::Subtype::VP9,             \"VP9\"             },\n\t\t{ RtpCodecMimeType::Subtype::H264,            \"H264\"            },\n\t\t{ RtpCodecMimeType::Subtype::AV1,             \"AV1\"             },\n\t\t// Complementary codecs:\n\t\t{ RtpCodecMimeType::Subtype::CN,              \"CN\"              },\n\t\t{ RtpCodecMimeType::Subtype::TELEPHONE_EVENT, \"telephone-event\" },\n\t\t// Feature codecs:\n\t\t{ RtpCodecMimeType::Subtype::RTX,             \"rtx\"             },\n\t\t{ RtpCodecMimeType::Subtype::ULPFEC,          \"ulpfec\"          },\n\t\t{ RtpCodecMimeType::Subtype::FLEXFEC,         \"flexfec\"         },\n\t\t{ RtpCodecMimeType::Subtype::X_ULPFECUC,      \"x-ulpfecuc\"      },\n\t\t{ RtpCodecMimeType::Subtype::RED,             \"red\"             }\n\t};\n\t// clang-format on\n\n\t/* Instance methods. */\n\n\tvoid RtpCodecMimeType::SetMimeType(const std::string& mimeType)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto slashPos = mimeType.find('/');\n\n\t\tif (slashPos == std::string::npos || slashPos == 0 || slashPos == mimeType.length() - 1)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"wrong codec MIME\");\n\t\t}\n\n\t\tstd::string type    = mimeType.substr(0, slashPos);\n\t\tstd::string subtype = mimeType.substr(slashPos + 1);\n\n\t\t// Force lowcase names.\n\t\tUtils::String::ToLowerCase(type);\n\t\tUtils::String::ToLowerCase(subtype);\n\n\t\t// Set MIME type.\n\t\t{\n\t\t\tauto it = RtpCodecMimeType::String2Type.find(type);\n\n\t\t\tif (it == RtpCodecMimeType::String2Type.end())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"unknown codec MIME type '%s'\", type.c_str());\n\t\t\t}\n\n\t\t\tthis->type = it->second;\n\t\t}\n\n\t\t// Set MIME subtype.\n\t\t{\n\t\t\tauto it = RtpCodecMimeType::String2Subtype.find(subtype);\n\n\t\t\tif (it == RtpCodecMimeType::String2Subtype.end())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"unknown codec MIME subtype '%s'\", subtype.c_str());\n\t\t\t}\n\n\t\t\tthis->subtype = it->second;\n\t\t}\n\n\t\t// Set mimeType.\n\t\tthis->mimeType = RtpCodecMimeType::Type2String.at(this->type) + \"/\" +\n\t\t                 RtpCodecMimeType::Subtype2String.at(this->subtype);\n\t}\n\n\tvoid RtpCodecMimeType::UpdateMimeType()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Set mimeType.\n\t\tthis->mimeType = RtpCodecMimeType::Type2String.at(this->type) + \"/\" +\n\t\t                 RtpCodecMimeType::Subtype2String.at(this->subtype);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpCodecParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtpCodecParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtpCodecParameters::RtpCodecParameters(const FBS::RtpParameters::RtpCodecParameters* data)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Set MIME field.\n\t\t// This may throw.\n\t\tthis->mimeType.SetMimeType(data->mimeType()->str());\n\n\t\t// payloadType.\n\t\tthis->payloadType = data->payloadType();\n\n\t\t// clockRate.\n\t\tthis->clockRate = data->clockRate();\n\n\t\t// channels is optional.\n\t\tif (auto channels = data->channels(); channels.has_value())\n\t\t{\n\t\t\tthis->channels = channels.value();\n\t\t}\n\n\t\t// parameters is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpCodecParameters::VT_PARAMETERS))\n\t\t{\n\t\t\tthis->parameters.Set(data->parameters());\n\t\t}\n\n\t\t// rtcpFeedback is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpCodecParameters::VT_RTCPFEEDBACK))\n\t\t{\n\t\t\tthis->rtcpFeedback.reserve(data->rtcpFeedback()->size());\n\n\t\t\tfor (const auto* entry : *data->rtcpFeedback())\n\t\t\t{\n\t\t\t\tthis->rtcpFeedback.emplace_back(entry);\n\t\t\t}\n\t\t}\n\n\t\t// Check codec.\n\t\tCheckCodec();\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtpCodecParameters> RtpCodecParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto parameters = this->parameters.FillBuffer(builder);\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtcpFeedback>> rtcpFeedback;\n\t\trtcpFeedback.reserve(this->rtcpFeedback.size());\n\n\t\tfor (const auto& fb : this->rtcpFeedback)\n\t\t{\n\t\t\trtcpFeedback.emplace_back(fb.FillBuffer(builder));\n\t\t}\n\n\t\treturn FBS::RtpParameters::CreateRtpCodecParametersDirect(\n\t\t  builder,\n\t\t  this->mimeType.ToString().c_str(),\n\t\t  this->payloadType,\n\t\t  this->clockRate,\n\t\t  this->channels > 1 ? flatbuffers::Optional<uint8_t>(this->channels) : flatbuffers::nullopt,\n\t\t  &parameters,\n\t\t  &rtcpFeedback);\n\t}\n\n\tinline void RtpCodecParameters::CheckCodec() const\n\t{\n\t\tMS_TRACE();\n\n\t\tstatic const std::string AptString{ \"apt\" };\n\n\t\t// Check per MIME parameters and set default values.\n\t\tswitch (this->mimeType.subtype)\n\t\t{\n\t\t\tcase RTC::RtpCodecMimeType::Subtype::RTX:\n\t\t\t{\n\t\t\t\t// A RTX codec must have 'apt' parameter.\n\t\t\t\tif (!this->parameters.HasPositiveInteger(AptString))\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"missing apt parameter in RTX codec\");\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpEncodingParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtpEncodingParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <exception>\n#include <regex>\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtpEncodingParameters::RtpEncodingParameters(const FBS::RtpParameters::RtpEncodingParameters* data)\n\t{\n\t\tMS_TRACE();\n\n\t\t// ssrc is optional.\n\t\tif (auto ssrc = data->ssrc(); ssrc.has_value())\n\t\t{\n\t\t\tthis->ssrc = ssrc.value();\n\t\t}\n\n\t\t// rid is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_RID))\n\t\t{\n\t\t\tthis->rid = data->rid()->str();\n\t\t}\n\n\t\t// codecPayloadType is optional.\n\t\tif (auto codecPayloadType = data->codecPayloadType(); codecPayloadType.has_value())\n\t\t{\n\t\t\tthis->codecPayloadType    = codecPayloadType.value();\n\t\t\tthis->hasCodecPayloadType = true;\n\t\t}\n\n\t\t// rtx is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_RTX))\n\t\t{\n\t\t\tthis->rtx    = RtpRtxParameters(data->rtx());\n\t\t\tthis->hasRtx = true;\n\t\t}\n\n\t\t// maxBitrate is optional.\n\t\tif (auto maxBitrate = data->maxBitrate(); maxBitrate.has_value())\n\t\t{\n\t\t\tthis->maxBitrate = maxBitrate.value();\n\t\t}\n\n\t\t// dtx is optional, default is false.\n\t\tthis->dtx = data->dtx();\n\n\t\t// scalabilityMode is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_SCALABILITYMODE))\n\t\t{\n\t\t\tconst std::string scalabilityMode = data->scalabilityMode()->str();\n\n\t\t\tstatic const std::regex ScalabilityModeRegex(\n\t\t\t  \"^[LS]([1-9]\\\\d{0,1})T([1-9]\\\\d{0,1})(_KEY)?.*\", std::regex_constants::ECMAScript);\n\n\t\t\tstd::smatch match;\n\n\t\t\tstd::regex_match(scalabilityMode, match, ScalabilityModeRegex);\n\n\t\t\tif (!match.empty())\n\t\t\t{\n\t\t\t\tthis->scalabilityMode = scalabilityMode;\n\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->spatialLayers  = std::stoul(match[1].str());\n\t\t\t\t\tthis->temporalLayers = std::stoul(match[2].str());\n\t\t\t\t\tthis->ksvc           = match.size() >= 4 && match[3].str() == \"_KEY\";\n\t\t\t\t}\n\t\t\t\tcatch (std::exception& e)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid scalabilityMode: %s\", e.what());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters> RtpEncodingParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::RtpParameters::CreateRtpEncodingParametersDirect(\n\t\t  builder,\n\t\t  this->ssrc != 0u ? flatbuffers::Optional<uint32_t>(this->ssrc) : flatbuffers::nullopt,\n\t\t  !this->rid.empty() ? this->rid.c_str() : nullptr,\n\t\t  this->hasCodecPayloadType ? flatbuffers::Optional<uint8_t>(this->codecPayloadType)\n\t\t                            : flatbuffers::nullopt,\n\t\t  this->hasRtx ? this->rtx.FillBuffer(builder) : 0u,\n\t\t  this->dtx,\n\t\t  this->scalabilityMode.c_str());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpHeaderExtensionParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtpHeaderExtensionParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtpHeaderExtensionParameters::RtpHeaderExtensionParameters(\n\t  const FBS::RtpParameters::RtpHeaderExtensionParameters* const data)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Get the type.\n\t\tthis->type = RTC::RtpHeaderExtensionUri::TypeFromFbs(data->uri());\n\n\t\tthis->id = data->id();\n\n\t\t// Don't allow id 0.\n\t\tif (this->id == 0u)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid id 0\");\n\t\t}\n\n\t\t// encrypt is false by default.\n\t\tthis->encrypt = data->encrypt();\n\n\t\t// parameters is optional.\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpHeaderExtensionParameters::VT_PARAMETERS))\n\t\t{\n\t\t\tthis->parameters.Set(data->parameters());\n\t\t}\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtpHeaderExtensionParameters> RtpHeaderExtensionParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto parameters = this->parameters.FillBuffer(builder);\n\n\t\treturn FBS::RtpParameters::CreateRtpHeaderExtensionParametersDirect(\n\t\t  builder, RTC::RtpHeaderExtensionUri::TypeToFbs(this->type), this->id, this->encrypt, &parameters);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp",
    "content": "#define MS_CLASS \"RTC::RtpHeaderExtensionUri\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Class methods. */\n\n\tRtpHeaderExtensionUri::Type RtpHeaderExtensionUri::TypeFromFbs(\n\t  FBS::RtpParameters::RtpHeaderExtensionUri uri)\n\t{\n\t\tswitch (uri)\n\t\t{\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::Mid:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::MID;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::RtpStreamId:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::RTP_STREAM_ID;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::RepairRtpStreamId:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::AbsSendTime:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::ABS_SEND_TIME;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::TransportWideCcDraft01:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::SsrcAudioLevel:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::VideoOrientation:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::TimeOffset:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::TIME_OFFSET;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::PlayoutDelay:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::PLAYOUT_DELAY;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::AbsCaptureTime:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::DependencyDescriptor:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR;\n\t\t\t}\n\n\t\t\tcase FBS::RtpParameters::RtpHeaderExtensionUri::MediasoupPacketId:\n\t\t\t{\n\t\t\t\treturn RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tFBS::RtpParameters::RtpHeaderExtensionUri RtpHeaderExtensionUri::TypeToFbs(\n\t  RtpHeaderExtensionUri::Type uri)\n\t{\n\t\tswitch (uri)\n\t\t{\n\t\t\tcase RtpHeaderExtensionUri::Type::MID:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::Mid;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::RTP_STREAM_ID:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::RtpStreamId;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::RepairRtpStreamId;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::ABS_SEND_TIME:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::AbsSendTime;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::TransportWideCcDraft01;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::SsrcAudioLevel;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::DependencyDescriptor;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::VideoOrientation;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::TIME_OFFSET:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::TimeOffset;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::AbsCaptureTime;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::PLAYOUT_DELAY:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::PlayoutDelay;\n\t\t\t}\n\n\t\t\tcase RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::RtpHeaderExtensionUri::MediasoupPacketId;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtpParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <absl/container/flat_hash_set.h>\n\nnamespace RTC\n{\n\t/* Class variables. */\n\n\t// clang-format off\n\tconst absl::flat_hash_map<RtpParameters::Type, std::string> RtpParameters::Type2String = {\n\t\t{ RtpParameters::Type::SIMPLE, \"simple\" },\n\t\t{ RtpParameters::Type::SIMULCAST, \"simulcast\" },\n\t\t{ RtpParameters::Type::SVC, \"svc\" },\n\t\t{ RtpParameters::Type::PIPE, \"pipe\" }\n\t};\n\t// clang-format on\n\n\t/* Class methods. */\n\n\tstd::optional<RtpParameters::Type> RtpParameters::GetType(const RtpParameters& rtpParameters)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::optional<RtpParameters::Type> type;\n\n\t\tif (rtpParameters.encodings.size() == 1)\n\t\t{\n\t\t\tconst auto& encoding = rtpParameters.encodings[0];\n\t\t\tconst auto* mediaCodec =\n\t\t\t  rtpParameters.GetCodecForEncoding(const_cast<RTC::RtpEncodingParameters&>(encoding));\n\n\t\t\tif (encoding.spatialLayers > 1 || encoding.temporalLayers > 1)\n\t\t\t{\n\t\t\t\tif (RTC::RTP::Codecs::Tools::IsValidTypeForCodec(RtpParameters::Type::SVC, mediaCodec->mimeType))\n\t\t\t\t{\n\t\t\t\t\ttype.emplace(RtpParameters::Type::SVC);\n\t\t\t\t}\n\t\t\t\telse if (\n\t\t\t\t  RTC::RTP::Codecs::Tools::IsValidTypeForCodec(\n\t\t\t\t    RtpParameters::Type::SIMULCAST, mediaCodec->mimeType))\n\t\t\t\t{\n\t\t\t\t\ttype.emplace(RtpParameters::Type::SIMULCAST);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ttype.emplace(RtpParameters::Type::SIMPLE);\n\t\t\t}\n\t\t}\n\t\telse if (rtpParameters.encodings.size() > 1)\n\t\t{\n\t\t\ttype.emplace(RtpParameters::Type::SIMULCAST);\n\t\t}\n\n\t\treturn type;\n\t}\n\n\tconst std::string& RtpParameters::GetTypeString(RtpParameters::Type type)\n\t{\n\t\tMS_TRACE();\n\n\t\treturn RtpParameters::Type2String.at(type);\n\t}\n\n\tFBS::RtpParameters::Type RtpParameters::TypeToFbs(RtpParameters::Type type)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (type)\n\t\t{\n\t\t\tcase Type::SIMPLE:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::Type::SIMPLE;\n\t\t\t}\n\n\t\t\tcase Type::SIMULCAST:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::Type::SIMULCAST;\n\t\t\t}\n\n\t\t\tcase Type::SVC:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::Type::SVC;\n\t\t\t}\n\n\t\t\tcase Type::PIPE:\n\t\t\t{\n\t\t\t\treturn FBS::RtpParameters::Type::PIPE;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\t/* Instance methods. */\n\n\tRtpParameters::RtpParameters(const FBS::RtpParameters::RtpParameters* data)\n\t{\n\t\tMS_TRACE();\n\n\t\t// mid is optional.\n\t\tif (data->mid())\n\t\t{\n\t\t\tthis->mid = data->mid()->str();\n\t\t}\n\n\t\tthis->codecs.reserve(data->codecs()->size());\n\n\t\tfor (const auto* entry : *data->codecs())\n\t\t{\n\t\t\t// This may throw due the constructor of RTC::RtpCodecParameters.\n\t\t\tthis->codecs.emplace_back(entry);\n\t\t}\n\n\t\tif (this->codecs.empty())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"empty codecs\");\n\t\t}\n\n\t\tthis->encodings.reserve(data->encodings()->size());\n\n\t\tfor (const auto* entry : *data->encodings())\n\t\t{\n\t\t\t// This may throw due the constructor of RTC::RtpEncodingParameters.\n\t\t\tthis->encodings.emplace_back(entry);\n\t\t}\n\n\t\tif (this->encodings.empty())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"empty encodings\");\n\t\t}\n\n\t\tthis->headerExtensions.reserve(data->headerExtensions()->size());\n\n\t\tfor (const auto* entry : *data->headerExtensions())\n\t\t{\n\t\t\t// This may throw due the constructor of RTC::RtpHeaderExtensionParameters.\n\t\t\tthis->headerExtensions.emplace_back(entry);\n\t\t}\n\n\t\t// This may throw.\n\t\tthis->rtcp = RTC::RtcpParameters(data->rtcp());\n\n\t\t// msid is optional.\n\t\tif (data->msid())\n\t\t{\n\t\t\tthis->msid = data->msid()->str();\n\t\t}\n\n\t\t// Validate RTP parameters.\n\t\tValidateCodecs();\n\t\tValidateEncodings();\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtpParameters> RtpParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add codecs.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtpCodecParameters>> codecs;\n\t\tcodecs.reserve(this->codecs.size());\n\n\t\tfor (const auto& codec : this->codecs)\n\t\t{\n\t\t\tcodecs.emplace_back(codec.FillBuffer(builder));\n\t\t}\n\n\t\t// Add encodings.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters>> encodings;\n\t\tencodings.reserve(this->encodings.size());\n\n\t\tfor (const auto& encoding : this->encodings)\n\t\t{\n\t\t\tencodings.emplace_back(encoding.FillBuffer(builder));\n\t\t}\n\n\t\t// Add headerExtensions.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtpHeaderExtensionParameters>> headerExtensions;\n\t\theaderExtensions.reserve(this->headerExtensions.size());\n\n\t\tfor (const auto& headerExtension : this->headerExtensions)\n\t\t{\n\t\t\theaderExtensions.emplace_back(headerExtension.FillBuffer(builder));\n\t\t}\n\n\t\t// Add rtcp.\n\t\tflatbuffers::Offset<FBS::RtpParameters::RtcpParameters> rtcp;\n\n\t\trtcp = this->rtcp.FillBuffer(builder);\n\n\t\treturn FBS::RtpParameters::CreateRtpParametersDirect(\n\t\t  builder, this->mid.c_str(), &codecs, &headerExtensions, &encodings, rtcp, this->msid.c_str());\n\t}\n\n\tconst RTC::RtpCodecParameters* RtpParameters::GetCodecForEncoding(RtpEncodingParameters& encoding) const\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint8_t payloadType = encoding.codecPayloadType;\n\t\tauto it                   = this->codecs.begin();\n\n\t\tfor (; it != this->codecs.end(); ++it)\n\t\t{\n\t\t\tconst auto& codec = *it;\n\n\t\t\tif (codec.payloadType == payloadType)\n\t\t\t{\n\t\t\t\treturn std::addressof(codec);\n\t\t\t}\n\t\t}\n\n\t\t// This should never happen.\n\t\tif (it == this->codecs.end())\n\t\t{\n\t\t\tMS_ABORT(\"no valid codec payload type for the given encoding\");\n\t\t}\n\n\t\treturn nullptr;\n\t}\n\n\tconst RTC::RtpCodecParameters* RtpParameters::GetRtxCodecForEncoding(RtpEncodingParameters& encoding) const\n\t{\n\t\tMS_TRACE();\n\n\t\tstatic const std::string AptString{ \"apt\" };\n\n\t\tconst uint8_t payloadType = encoding.codecPayloadType;\n\n\t\tfor (const auto& codec : this->codecs)\n\t\t{\n\t\t\tif (codec.mimeType.IsFeatureCodec() && codec.parameters.GetInteger(AptString) == payloadType)\n\t\t\t{\n\t\t\t\treturn std::addressof(codec);\n\t\t\t}\n\t\t}\n\n\t\treturn nullptr;\n\t}\n\n\tvoid RtpParameters::ValidateCodecs()\n\t{\n\t\tMS_TRACE();\n\n\t\tstatic const std::string AptString{ \"apt\" };\n\n\t\tabsl::flat_hash_set<uint8_t> payloadTypes;\n\n\t\tfor (auto& codec : this->codecs)\n\t\t{\n\t\t\tif (payloadTypes.find(codec.payloadType) != payloadTypes.end())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"duplicated payloadType\");\n\t\t\t}\n\n\t\t\tpayloadTypes.insert(codec.payloadType);\n\n\t\t\tswitch (codec.mimeType.subtype)\n\t\t\t{\n\t\t\t\t// A RTX codec must have 'apt' parameter pointing to a non RTX codec.\n\t\t\t\tcase RTC::RtpCodecMimeType::Subtype::RTX:\n\t\t\t\t{\n\t\t\t\t\t// NOTE: RtpCodecParameters already asserted that there is apt parameter.\n\t\t\t\t\tconst int32_t apt = codec.parameters.GetInteger(AptString);\n\t\t\t\t\tauto it           = this->codecs.begin();\n\n\t\t\t\t\tfor (; it != this->codecs.end(); ++it)\n\t\t\t\t\t{\n\t\t\t\t\t\tconst auto& codec = *it;\n\n\t\t\t\t\t\tif (static_cast<int32_t>(codec.payloadType) == apt)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::RTX)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"apt in RTX codec points to a RTX codec\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::ULPFEC)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"apt in RTX codec points to a ULPFEC codec\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::FLEXFEC)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"apt in RTX codec points to a FLEXFEC codec\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (it == this->codecs.end())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"apt in RTX codec points to a non existing codec\");\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid RtpParameters::ValidateEncodings()\n\t{\n\t\tuint8_t firstMediaPayloadType{ 0 };\n\n\t\t{\n\t\t\tauto it = this->codecs.begin();\n\n\t\t\tfor (; it != this->codecs.end(); ++it)\n\t\t\t{\n\t\t\t\tauto& codec = *it;\n\n\t\t\t\t// Must be a media codec.\n\t\t\t\tif (codec.mimeType.IsMediaCodec())\n\t\t\t\t{\n\t\t\t\t\tfirstMediaPayloadType = codec.payloadType;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (it == this->codecs.end())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"no media codecs found\");\n\t\t\t}\n\t\t}\n\n\t\t// Iterate all the encodings, set the first payloadType in all of them with\n\t\t// codecPayloadType unset, and check that others point to a media codec.\n\t\t//\n\t\t// Also, don't allow multiple SVC spatial layers into an encoding if there\n\t\t// are more than one encoding (simulcast).\n\t\tfor (auto& encoding : this->encodings)\n\t\t{\n\t\t\tif (encoding.spatialLayers > 1 && this->encodings.size() > 1)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t  \"cannot use both simulcast and encodings with multiple SVC spatial layers\");\n\t\t\t}\n\n\t\t\tif (!encoding.hasCodecPayloadType)\n\t\t\t{\n\t\t\t\tencoding.codecPayloadType    = firstMediaPayloadType;\n\t\t\t\tencoding.hasCodecPayloadType = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tauto it = this->codecs.begin();\n\n\t\t\t\tfor (; it != this->codecs.end(); ++it)\n\t\t\t\t{\n\t\t\t\t\tconst auto& codec = *it;\n\n\t\t\t\t\tif (codec.payloadType == encoding.codecPayloadType)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Must be a media codec.\n\t\t\t\t\t\tif (codec.mimeType.IsMediaCodec())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid codecPayloadType\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (it == this->codecs.end())\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"unknown codecPayloadType\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpDictionaries/RtpRtxParameters.cpp",
    "content": "#define MS_CLASS \"RTC::RtpRtxParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtpRtxParameters::RtpRtxParameters(const FBS::RtpParameters::Rtx* data)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->ssrc = data->ssrc();\n\t}\n\n\tflatbuffers::Offset<FBS::RtpParameters::Rtx> RtpRtxParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::RtpParameters::CreateRtx(builder, this->ssrc);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpListener.cpp",
    "content": "#define MS_CLASS \"RTC::RtpListener\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RtpListener.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/Producer.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tflatbuffers::Offset<FBS::Transport::RtpListener> RtpListener::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add ssrcTable.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::Uint32String>> ssrcTable;\n\n\t\tfor (const auto& kv : this->ssrcTable)\n\t\t{\n\t\t\tauto ssrc      = kv.first;\n\t\t\tauto* producer = kv.second;\n\n\t\t\tssrcTable.emplace_back(\n\t\t\t  FBS::Common::CreateUint32StringDirect(builder, ssrc, producer->id.c_str()));\n\t\t}\n\n\t\t// Add midTable.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringString>> midTable;\n\n\t\tfor (const auto& kv : this->midTable)\n\t\t{\n\t\t\tconst auto& mid = kv.first;\n\t\t\tauto* producer  = kv.second;\n\n\t\t\tmidTable.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringDirect(builder, mid.c_str(), producer->id.c_str()));\n\t\t}\n\n\t\t// Add ridTable.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::StringString>> ridTable;\n\n\t\tfor (const auto& kv : this->ridTable)\n\t\t{\n\t\t\tconst auto& rid = kv.first;\n\t\t\tauto* producer  = kv.second;\n\n\t\t\tridTable.emplace_back(\n\t\t\t  FBS::Common::CreateStringStringDirect(builder, rid.c_str(), producer->id.c_str()));\n\t\t}\n\n\t\treturn FBS::Transport::CreateRtpListenerDirect(builder, &ssrcTable, &midTable, &ridTable);\n\t}\n\n\tvoid RtpListener::AddProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto& rtpParameters = producer->GetRtpParameters();\n\n\t\t// Add entries into the ssrcTable.\n\t\tfor (const auto& encoding : rtpParameters.encodings)\n\t\t{\n\t\t\tuint32_t ssrc;\n\n\t\t\t// Check encoding.ssrc.\n\t\t\tssrc = encoding.ssrc;\n\n\t\t\tif (ssrc != 0u)\n\t\t\t{\n\t\t\t\tif (this->ssrcTable.find(ssrc) == this->ssrcTable.end())\n\t\t\t\t{\n\t\t\t\t\tthis->ssrcTable[ssrc] = producer;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tRemoveProducer(producer);\n\n\t\t\t\t\tMS_THROW_ERROR(\"ssrc already exists in RTP listener [ssrc:%\" PRIu32 \"]\", ssrc);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check encoding.rtx.ssrc.\n\t\t\tssrc = encoding.rtx.ssrc;\n\n\t\t\tif (ssrc != 0u)\n\t\t\t{\n\t\t\t\tif (this->ssrcTable.find(ssrc) == this->ssrcTable.end())\n\t\t\t\t{\n\t\t\t\t\tthis->ssrcTable[ssrc] = producer;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tRemoveProducer(producer);\n\n\t\t\t\t\tMS_THROW_ERROR(\"RTX ssrc already exists in RTP listener [ssrc:%\" PRIu32 \"]\", ssrc);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add entries into midTable.\n\t\tif (!rtpParameters.mid.empty())\n\t\t{\n\t\t\tconst auto& mid = rtpParameters.mid;\n\n\t\t\tif (this->midTable.find(mid) == this->midTable.end())\n\t\t\t{\n\t\t\t\tthis->midTable[mid] = producer;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tRemoveProducer(producer);\n\n\t\t\t\tMS_THROW_ERROR(\"MID already exists in RTP listener [mid:%s]\", mid.c_str());\n\t\t\t}\n\t\t}\n\n\t\t// Add entries into ridTable.\n\t\tfor (const auto& encoding : rtpParameters.encodings)\n\t\t{\n\t\t\tconst auto& rid = encoding.rid;\n\n\t\t\tif (rid.empty())\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (this->ridTable.find(rid) == this->ridTable.end())\n\t\t\t{\n\t\t\t\tthis->ridTable[rid] = producer;\n\t\t\t}\n\t\t\t// Just fail if no MID is given.\n\t\t\telse if (rtpParameters.mid.empty())\n\t\t\t{\n\t\t\t\tRemoveProducer(producer);\n\n\t\t\t\tMS_THROW_ERROR(\"RID already exists in RTP listener and no MID is given [rid:%s]\", rid.c_str());\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid RtpListener::RemoveProducer(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove from the listener tables all entries pointing to the Producer.\n\n\t\tfor (auto it = this->ssrcTable.begin(); it != this->ssrcTable.end();)\n\t\t{\n\t\t\tif (it->second == producer)\n\t\t\t{\n\t\t\t\tit = this->ssrcTable.erase(it);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t++it;\n\t\t\t}\n\t\t}\n\n\t\tfor (auto it = this->midTable.begin(); it != this->midTable.end();)\n\t\t{\n\t\t\tif (it->second == producer)\n\t\t\t{\n\t\t\t\tit = this->midTable.erase(it);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t++it;\n\t\t\t}\n\t\t}\n\n\t\tfor (auto it = this->ridTable.begin(); it != this->ridTable.end();)\n\t\t{\n\t\t\tif (it->second == producer)\n\t\t\t{\n\t\t\t\tit = this->ridTable.erase(it);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t++it;\n\t\t\t}\n\t\t}\n\t}\n\n\tRTC::Producer* RtpListener::GetProducer(const RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// First lookup into the SSRC table.\n\t\t{\n\t\t\tauto it = this->ssrcTable.find(packet->GetSsrc());\n\n\t\t\tif (it != this->ssrcTable.end())\n\t\t\t{\n\t\t\t\tauto* producer = it->second;\n\n\t\t\t\treturn producer;\n\t\t\t}\n\t\t}\n\n\t\tbool hasMid{ false };\n\t\tstd::string mid;\n\t\tstd::string rid;\n\n\t\t// Otherwise lookup into the MID table.\n\t\tif (packet->ReadMid(mid))\n\t\t{\n\t\t\thasMid = true;\n\n\t\t\tauto it = this->midTable.find(mid);\n\n\t\t\tif (it != this->midTable.end())\n\t\t\t{\n\t\t\t\tauto* producer = it->second;\n\n\t\t\t\t// Fill the ssrc table.\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"inserting entry in ssrcTable [mid:%s, ssrc:%\" PRIu32 \", producerId:%s]\",\n\t\t\t\t  mid.c_str(),\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  producer->id.c_str());\n\n\t\t\t\t// NOTE: Here we may override an existing key with same SSRC but it's ok.\n\t\t\t\tif (this->ssrcTable.find(packet->GetSsrc()) != this->ssrcTable.end())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"a Producer with ssrc %\" PRIu32\n\t\t\t\t\t  \" already exists in the ssrcTable, overriding it anyway [mid:%s]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  mid.c_str());\n\t\t\t\t}\n\n\t\t\t\tthis->ssrcTable[packet->GetSsrc()] = producer;\n\n\t\t\t\treturn producer;\n\t\t\t}\n\t\t}\n\n\t\t// Otherwise lookup into the RID table but only do it if the packet doesn't\n\t\t// have MID extension.\n\t\tif (!hasMid && packet->ReadRid(rid))\n\t\t{\n\t\t\tauto it = this->ridTable.find(rid);\n\n\t\t\tif (it != this->ridTable.end())\n\t\t\t{\n\t\t\t\tauto* producer = it->second;\n\n\t\t\t\t// Fill the ssrc table.\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"inserting entry in ssrcTable [rid:%s, ssrc:%\" PRIu32 \", producerId:%s]\",\n\t\t\t\t  rid.c_str(),\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  producer->id.c_str());\n\n\t\t\t\t// NOTE: Here we may override an existing key with same SSRC but it's ok.\n\t\t\t\tif (this->ssrcTable.find(packet->GetSsrc()) != this->ssrcTable.end())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  rtp,\n\t\t\t\t\t  \"a Producer with ssrc %\" PRIu32\n\t\t\t\t\t  \" already exists in the ssrcTable, overriding it anyway  [rid:%s]\",\n\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t  rid.c_str());\n\t\t\t\t}\n\n\t\t\t\tthis->ssrcTable[packet->GetSsrc()] = producer;\n\n\t\t\t\treturn producer;\n\t\t\t}\n\t\t}\n\n\t\treturn nullptr;\n\t}\n\n\tRTC::Producer* RtpListener::GetProducer(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Lookup into the SSRC table.\n\t\tauto it = this->ssrcTable.find(ssrc);\n\n\t\tif (it != this->ssrcTable.end())\n\t\t{\n\t\t\tauto* producer = it->second;\n\n\t\t\treturn producer;\n\t\t}\n\n\t\treturn nullptr;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/RtpObserver.cpp",
    "content": "#define MS_CLASS \"RTC::RtpObserver\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/RtpObserver.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tRtpObserver::RtpObserver(\n\t  SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener)\n\t  : id(id), shared(shared), listener(listener)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tRtpObserver::~RtpObserver()\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid RtpObserver::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::RTPOBSERVER_PAUSE:\n\t\t\t{\n\t\t\t\tthis->Pause();\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::RTPOBSERVER_RESUME:\n\t\t\t{\n\t\t\t\tthis->Resume();\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::RTPOBSERVER_ADD_PRODUCER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::RtpObserver::AddProducerRequest>();\n\t\t\t\tauto producerId  = body->producerId()->str();\n\n\t\t\t\tRTC::Producer* producer = this->listener->RtpObserverGetProducer(this, producerId);\n\n\t\t\t\tthis->AddProducer(producer);\n\n\t\t\t\tthis->listener->OnRtpObserverAddProducer(this, producer);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::RTPOBSERVER_REMOVE_PRODUCER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::RtpObserver::RemoveProducerRequest>();\n\t\t\t\tauto producerId  = body->producerId()->str();\n\n\t\t\t\tRTC::Producer* producer = this->listener->RtpObserverGetProducer(this, producerId);\n\n\t\t\t\tthis->RemoveProducer(producer);\n\n\t\t\t\t// Remove from the map.\n\t\t\t\tthis->listener->OnRtpObserverRemoveProducer(this, producer);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid RtpObserver::Pause()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->paused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->paused = true;\n\n\t\tPaused();\n\t}\n\n\tvoid RtpObserver::Resume()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->paused)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->paused = false;\n\n\t\tResumed();\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/Association.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Association\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/Association.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/StateCookieParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include <limits>  // std::numeric_limits()\n#include <sstream> // std::ostringstream\n#include <string>\n#include <type_traits> // std::is_same_v\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Static. */\n\n\t\talignas(4) static thread_local uint8_t PacketFactoryBuffer[65536];\n\t\t// @see https://tools.ietf.org/html/rfc9260#section-5.1\n\t\tconstexpr uint32_t MinVerificationTag{ 1 };\n\t\tconstexpr uint32_t MaxVerificationTag{ std::numeric_limits<uint32_t>::max() };\n\t\t// @see https://tools.ietf.org/html/rfc9260#section-3.3.2\n\t\tconstexpr uint32_t MinInitialTsn{ 0 };\n\t\tconstexpr uint32_t MaxInitialTsn{ std::numeric_limits<uint32_t>::max() };\n\t\tconstexpr uint64_t MaxTieTag{ std::numeric_limits<uint64_t>::max() };\n\n\t\t/* Instance methods. */\n\n\t\tAssociation::Association(\n\t\t  const SctpOptions& sctpOptions, AssociationListenerInterface* listener, SharedInterface* shared)\n\t\t  : sctpOptions(sctpOptions),\n\t\t    // Our `listener` member is a `AssociationListenerDeferrer` which takes\n\t\t    // `listener` argument as constructor argument.\n\t\t    associationListenerDeferrer(listener),\n\t\t    shared(shared),\n\t\t    packetSender(this, this->associationListenerDeferrer),\n\t\t    sendQueue(\n\t\t      this->associationListenerDeferrer,\n\t\t      sctpOptions.mtu,\n\t\t      sctpOptions.defaultStreamPriority,\n\t\t      sctpOptions.totalBufferedAmountLowThreshold),\n\t\t    t1InitTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-t1-init\",\n\t\t        .baseTimeoutMs       = sctpOptions.t1InitTimeoutMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t        .maxRestarts         = sctpOptions.maxInitRetransmissions,\n\t\t      })),\n\t\t    t1CookieTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-t1-cookie\",\n\t\t        .baseTimeoutMs       = sctpOptions.t1CookieTimeoutMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t        .maxRestarts         = sctpOptions.maxInitRetransmissions })),\n\t\t    t2ShutdownTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-t2-shutdown\",\n\t\t        .baseTimeoutMs       = sctpOptions.t2ShutdownTimeoutMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t        .maxRestarts         = sctpOptions.maxRetransmissions })),\n\t\t    maxPacketLength(Utils::Byte::PadDownTo4Bytes(this->sctpOptions.mtu))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tAssociation::~Association()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid Association::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto stateStringView = Association::StateToString(this->state);\n\t\t\tconst auto associationStateStringView = Types::AssociationStateToString(GetAssociationState());\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::Association>\");\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  association state: %.*s (internal state: %.*s)\",\n\t\t\t  static_cast<int>(associationStateStringView.size()),\n\t\t\t  associationStateStringView.data(),\n\t\t\t  static_cast<int>(stateStringView.size()),\n\t\t\t  stateStringView.data());\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tthis->tcb->Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tconst auto metrics = GetMetrics();\n\n\t\t\tif (metrics.has_value())\n\t\t\t{\n\t\t\t\tmetrics->Dump(indentation + 1);\n\t\t\t}\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::Association>\");\n\t\t}\n\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpParameters> Association::FillBuffer(\n\t\t  flatbuffers::FlatBufferBuilder& builder) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn FBS::SctpParameters::CreateSctpParameters(\n\t\t\t  builder,\n\t\t\t  // Add port.\n\t\t\t  this->sctpOptions.sourcePort,\n\t\t\t  // Add OS.\n\t\t\t  // TODO: SCTP: We should put here current value which may be different after\n\t\t\t  // negotiation with peer and reconfig.\n\t\t\t  this->sctpOptions.announcedMaxOutboundStreams,\n\t\t\t  // Add MIS.\n\t\t\t  // TODO: SCTP: We should put here current value which may be different after\n\t\t\t  // negotiation with peer and reconfig.\n\t\t\t  this->sctpOptions.announcedMaxInboundStreams,\n\t\t\t  // Add maxMessageSize.\n\t\t\t  this->sctpOptions.maxSendMessageSize,\n\t\t\t  // Add sendBufferSize.\n\t\t\t  this->sctpOptions.maxSendBufferSize,\n\t\t\t  // Add sctpBufferedAmountLowThreshold.\n\t\t\t  this->sctpOptions.totalBufferedAmountLowThreshold,\n\t\t\t  // Add isDataChannel.\n\t\t\t  // TODO: SCTP: Have a member for this.\n\t\t\t  // TODO: SCTP: So remove this hardcoded `true`.\n\t\t\t  /*isDataChannel*/ true);\n\t\t}\n\n\t\tTypes::AssociationState Association::GetAssociationState() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase State::NEW:\n\t\t\t\t{\n\t\t\t\t\treturn Types::AssociationState::NEW;\n\t\t\t\t}\n\n\t\t\t\tcase State::CLOSED:\n\t\t\t\t{\n\t\t\t\t\treturn Types::AssociationState::CLOSED;\n\t\t\t\t}\n\n\t\t\t\tcase State::COOKIE_WAIT:\n\t\t\t\tcase State::COOKIE_ECHOED:\n\t\t\t\t{\n\t\t\t\t\treturn Types::AssociationState::CONNECTING;\n\t\t\t\t}\n\n\t\t\t\tcase State::ESTABLISHED:\n\t\t\t\t{\n\t\t\t\t\treturn Types::AssociationState::CONNECTED;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_PENDING:\n\t\t\t\tcase State::SHUTDOWN_SENT:\n\t\t\t\tcase State::SHUTDOWN_RECEIVED:\n\t\t\t\tcase State::SHUTDOWN_ACK_SENT:\n\t\t\t\t{\n\t\t\t\t\treturn Types::AssociationState::SHUTTING_DOWN;\n\t\t\t\t}\n\n\t\t\t\t\tNO_DEFAULT_GCC();\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::MayConnect()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Just run the SCTP stack if our state is 'new'.\n\t\t\t// Notice that once MayConnect() is called (and the code below is executed),\n\t\t\t// SCTP state will no longer be \"NEW\".\n\t\t\tif (this->state != State::NEW)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"internal Association state is not NEW, ignoring\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If we haven't received any SCTP packet yet and the transport is not\n\t\t\t// ready for SCTP traffic, don't do anything.\n\t\t\tif (this->privateMetrics.rxPacketsCount == 0 && !this->associationListenerDeferrer.OnAssociationIsTransportReadyForSctp())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"no SCTP data has been received yet and transport is not ready for SCTP traffic, ignoring\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\"invoking Connect()\");\n\n\t\t\tConnect();\n\t\t}\n\n\t\tvoid Association::Connect()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: We only accept NEW state here so once the Association is closed\n\t\t\t// it cannot be reused. However there is no real technical reason for it.\n\t\t\tif (this->state != State::NEW)\n\t\t\t{\n\t\t\t\tconst auto stateStringView = Association::StateToString(this->state);\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"cannot initiate the Association since internal state is not NEW but %.*s\",\n\t\t\t\t  static_cast<int>(stateStringView.size()),\n\t\t\t\t  stateStringView.data());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tthis->preTcb.localVerificationTag =\n\t\t\t  Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);\n\t\t\tthis->preTcb.localInitialTsn =\n\t\t\t  Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);\n\n\t\t\tSendInitChunk();\n\n\t\t\tthis->t1InitTimer->Start();\n\n\t\t\tSetState(State::COOKIE_WAIT, \"Connect() called\");\n\n\t\t\tAssertIsConsistent();\n\n\t\t\tthis->associationListenerDeferrer.OnAssociationConnecting();\n\t\t}\n\n\t\tvoid Association::Shutdown()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state == State::NEW || this->state == State::CLOSED)\n\t\t\t{\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"Upon receipt of the SHUTDOWN primitive from its upper layer, the\n\t\t\t// endpoint enters the SHUTDOWN-PENDING state and remains there until all\n\t\t\t// outstanding data has been acknowledged by its peer.\"\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\t// TODO: dcsctp: Remove this check, as it just hides the problem that the\n\t\t\t\t// Association can transition from ShutdownSent to ShutdownPending, or\n\t\t\t\t// from ShutdownAckSent to ShutdownPending, which is illegal.\n\t\t\t\t//\n\t\t\t\t// @see https://issues.webrtc.org/issues/42222897\n\t\t\t\tif (\n\t\t\t\t  this->state != State::SHUTDOWN_SENT && this->state != State::SHUTDOWN_ACK_SENT &&\n\t\t\t\t  this->state != State::SHUTDOWN_RECEIVED && this->state != State::SHUTDOWN_PENDING)\n\t\t\t\t{\n\t\t\t\t\tthis->t1InitTimer->Stop();\n\t\t\t\t\tthis->t1CookieTimer->Stop();\n\n\t\t\t\t\t// NOTE: We need to set state before calling method below.\n\t\t\t\t\tSetState(State::SHUTDOWN_PENDING, \"Shutdown() called\");\n\t\t\t\t\tMaySendShutdownOrShutdownAckChunk();\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Association closed before even starting to connect, or during the\n\t\t\t// initial connection phase. There is no outstanding data, so the\n\t\t\t// Association can just be closed (stopping any timers, if any), as this\n\t\t\t// is the application's intention when calling Shutdown().\n\t\t\telse\n\t\t\t{\n\t\t\t\tInternalClose(Types::ErrorKind::SUCCESS, \"\");\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid Association::Close()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state == State::NEW || this->state == State::CLOSED)\n\t\t\t{\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tauto packet                 = this->tcb->CreatePacket();\n\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t// NOTE: Don't set bit T in the ABORT chunk since TCB knows the\n\t\t\t\t// Verification Tag expected by the remote.\n\n\t\t\t\tauto* userInitiatedAbortErrorCause =\n\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<UserInitiatedAbortErrorCause>();\n\n\t\t\t\tuserInitiatedAbortErrorCause->SetUpperLayerAbortReason(\"Close() called\");\n\n\t\t\t\tuserInitiatedAbortErrorCause->Consolidate();\n\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t\t}\n\n\t\t\tInternalClose(Types::ErrorKind::SUCCESS, \"\");\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tstd::optional<AssociationMetrics> Association::GetMetrics() const\n\t\t{\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\treturn std::nullopt;\n\t\t\t}\n\n\t\t\tconst size_t packetPayloadLength =\n\t\t\t  this->sctpOptions.mtu - Packet::CommonHeaderLength - DataChunk::DataChunkHeaderLength;\n\n\t\t\tAssociationMetrics metrics{\n\t\t\t\t.txPacketsCount  = this->privateMetrics.txPacketsCount,\n\t\t\t\t.txMessagesCount = this->privateMetrics.txMessagesCount,\n\t\t\t\t.rxPacketsCount  = this->privateMetrics.rxPacketsCount,\n\t\t\t\t.rxMessagesCount = this->privateMetrics.rxMessagesCount,\n\t\t\t\t.rtxPacketsCount = this->tcb->GetRetransmissionQueue().GetRtxPacketsCount(),\n\t\t\t\t.rtxBytesCount   = this->tcb->GetRetransmissionQueue().GetRtxBytesCount(),\n\t\t\t\t.cwndBytes       = this->tcb->GetCwnd(),\n\t\t\t\t.srttMs          = this->tcb->GetCurrentSrttMs(),\n\t\t\t\t.unackDataCount  = this->tcb->GetRetransmissionQueue().GetUnackedItems() +\n\t\t\t\t                   ((this->sendQueue.GetTotalBufferedAmount() + packetPayloadLength - 1) /\n\t\t\t\t                    packetPayloadLength),\n\t\t\t\t.peerRwndBytes   = static_cast<uint32_t>(this->tcb->GetRetransmissionQueue().GetRwnd()),\n\t\t\t\t.peerImplementation           = this->privateMetrics.peerImplementation,\n\t\t\t\t.negotiatedMaxOutboundStreams = this->privateMetrics.negotiatedMaxOutboundStreams,\n\t\t\t\t.negotiatedMaxInboundStreams  = this->privateMetrics.negotiatedMaxInboundStreams,\n\t\t\t\t.usesPartialReliability       = this->privateMetrics.usesPartialReliability,\n\t\t\t\t.usesMessageInterleaving      = this->privateMetrics.usesMessageInterleaving,\n\t\t\t\t.usesReConfig                 = this->privateMetrics.usesReConfig,\n\t\t\t\t.usesZeroChecksum             = this->privateMetrics.usesZeroChecksum,\n\n\t\t\t};\n\n\t\t\treturn metrics;\n\t\t}\n\n\t\tuint16_t Association::GetStreamPriority(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->sendQueue.GetStreamPriority(streamId);\n\t\t}\n\n\t\tvoid Association::SetStreamPriority(uint16_t streamId, uint16_t priority)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sendQueue.SetStreamPriority(streamId, priority);\n\t\t}\n\n\t\tvoid Association::SetMaxSendMessageSize(size_t maxMessageSize)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sctpOptions.maxSendMessageSize = maxMessageSize;\n\t\t}\n\n\t\tsize_t Association::GetStreamBufferedAmount(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->sendQueue.GetStreamBufferedAmount(streamId);\n\t\t}\n\n\t\tsize_t Association::GetStreamBufferedAmountLowThreshold(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->sendQueue.GetStreamBufferedAmountLowThreshold(streamId);\n\t\t}\n\n\t\tvoid Association::SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sendQueue.SetStreamBufferedAmountLowThreshold(streamId, bytes);\n\t\t}\n\n\t\tTypes::ResetStreamsStatus Association::ResetStreams(std::span<const uint16_t> outboundStreamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::WRONG_SEQUENCE,\n\t\t\t\t  \"cannot reset outbound streams as the association is not connected\");\n\n\t\t\t\treturn Types::ResetStreamsStatus::NOT_CONNECTED;\n\t\t\t}\n\n\t\t\tif (!this->tcb->GetNegotiatedCapabilities().reConfig)\n\t\t\t{\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::UNSUPPORTED_OPERATION,\n\t\t\t\t  \"cannot reset outbound streams as the remote doesn't support it\");\n\n\t\t\t\treturn Types::ResetStreamsStatus::NOT_SUPPORTED;\n\t\t\t}\n\n\t\t\tthis->tcb->GetStreamResetHandler().ResetStreams(outboundStreamIds);\n\n\t\t\tMaySendResetStreamsRequest();\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn Types::ResetStreamsStatus::PERFORMED;\n\t\t}\n\n\t\tTypes::SendMessageStatus Association::SendMessage(\n\t\t  Message message, const SendMessageOptions& sendMessageOptions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tconst auto status = InternalSendMessageCheck(message, sendMessageOptions);\n\n\t\t\tif (status != Types::SendMessageStatus::SUCCESS)\n\t\t\t{\n\t\t\t\treturn status;\n\t\t\t}\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->privateMetrics.txMessagesCount++;\n\n\t\t\tthis->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions);\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn Types::SendMessageStatus::SUCCESS;\n\t\t}\n\n\t\tstd::vector<Types::SendMessageStatus> Association::SendManyMessages(\n\t\t  std::span<Message> messages, const SendMessageOptions& sendMessageOptions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tstd::vector<Types::SendMessageStatus> statuses;\n\n\t\t\tstatuses.reserve(messages.size());\n\n\t\t\tfor (auto& message : messages)\n\t\t\t{\n\t\t\t\tconst auto status = InternalSendMessageCheck(message, sendMessageOptions);\n\n\t\t\t\tstatuses.push_back(status);\n\n\t\t\t\tif (status != Types::SendMessageStatus::SUCCESS)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis->privateMetrics.txMessagesCount++;\n\n\t\t\t\tthis->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions);\n\t\t\t}\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn statuses;\n\t\t}\n\n\t\tvoid Association::ReceiveSctpData(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// TODO: SCTP: For testing purposes. Must be removed.\n\t\t\t{\n\t\t\t\tMS_DUMP(\"<<< received SCTP packet:\");\n\n\t\t\t\tconst auto* packet = RTC::SCTP::Packet::Parse(data, len);\n\n\t\t\t\tif (packet)\n\t\t\t\t{\n\t\t\t\t\tpacket->Dump();\n\n\t\t\t\t\tdelete packet;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"RTC::SCTP::Packet::Parse() failed to parse received SCTP data\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->privateMetrics.rxPacketsCount++;\n\n\t\t\t// If we are received SCTP data from the remote peer it means that we may\n\t\t\t// initiate the SCTP association (if not already connected).\n\t\t\tMayConnect();\n\n\t\t\t// NOTE: It's important to create the deferrer here, otherwise it may\n\t\t\t// happen that MayConnect() ends calling to Connect() so we end with two\n\t\t\t// nested deferreds (and hence an assertion).\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tstd::unique_ptr<Packet> receivedPacket{ Packet::Parse(data, len) };\n\n\t\t\tif (!receivedPacket)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"failed to parse received SCTP packet\");\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"failed to parse received SCTP packet\");\n\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!ValidateReceivedPacket(receivedPacket.get()))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"Packet verification failed, discarded\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMaySendShutdownOnPacketReceived(receivedPacket.get());\n\n\t\t\tfor (auto it = receivedPacket->ChunksBegin(); it != receivedPacket->ChunksEnd(); ++it)\n\t\t\t{\n\t\t\t\tconst auto* receivedChunk = *it;\n\n\t\t\t\tif (!HandleReceivedChunk(receivedPacket.get(), receivedChunk))\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tthis->tcb->GetDataTracker().ObservePacketEnd();\n\t\t\t\tthis->tcb->MaySendSackChunk();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid Association::InternalClose(Types::ErrorKind errorKind, const std::string_view& message)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state != State::NEW && this->state != State::CLOSED)\n\t\t\t{\n\t\t\t\tthis->t1InitTimer->Stop();\n\t\t\t\tthis->t1CookieTimer->Stop();\n\t\t\t\tthis->t2ShutdownTimer->Stop();\n\n\t\t\t\tthis->tcb = nullptr;\n\t\t\t}\n\n\t\t\tconst auto prevState = this->state;\n\n\t\t\tSetState(State::CLOSED, message);\n\n\t\t\tif (prevState == State::COOKIE_WAIT || prevState == State::COOKIE_ECHOED)\n\t\t\t{\n\t\t\t\tif (errorKind == Types::ErrorKind::SUCCESS)\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationClosed(errorKind, message);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationFailed(errorKind, message);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationClosed(errorKind, message);\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::SetState(State state, const std::string_view& message)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto stateStringView = Association::StateToString(state);\n\n\t\t\tif (state == this->state)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"SCTP Association internal state is already %.*s (message:\\\"%.*s\\\")\",\n\t\t\t\t  static_cast<int>(stateStringView.size()),\n\t\t\t\t  stateStringView.data(),\n\t\t\t\t  static_cast<int>(message.size()),\n\t\t\t\t  message.data());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst auto previousStateStringView = Association::StateToString(this->state);\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"SCTP Association internal state changed from %.*s to %.*s (message:\\\"%.*s\\\")\",\n\t\t\t  static_cast<int>(previousStateStringView.size()),\n\t\t\t  previousStateStringView.data(),\n\t\t\t  static_cast<int>(stateStringView.size()),\n\t\t\t  stateStringView.data(),\n\t\t\t  static_cast<int>(message.size()),\n\t\t\t  message.data());\n\n\t\t\tthis->state = state;\n\t\t}\n\n\t\tvoid Association::AddCapabilitiesParametersToInitOrInitAckChunk(AnyInitChunk* chunk) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* supportedExtensionsParameter =\n\t\t\t  chunk->BuildParameterInPlace<SupportedExtensionsParameter>();\n\n\t\t\tsupportedExtensionsParameter->AddChunkType(Chunk::ChunkType::RE_CONFIG);\n\n\t\t\tif (this->sctpOptions.enablePartialReliability)\n\t\t\t{\n\t\t\t\tsupportedExtensionsParameter->AddChunkType(Chunk::ChunkType::FORWARD_TSN);\n\t\t\t}\n\n\t\t\tif (this->sctpOptions.enableMessageInterleaving)\n\t\t\t{\n\t\t\t\tsupportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_DATA);\n\t\t\t\tsupportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_FORWARD_TSN);\n\t\t\t}\n\n\t\t\tsupportedExtensionsParameter->Consolidate();\n\n\t\t\tif (this->sctpOptions.enablePartialReliability)\n\t\t\t{\n\t\t\t\tconst auto* forwardTsnSupportedParameter =\n\t\t\t\t  chunk->BuildParameterInPlace<ForwardTsnSupportedParameter>();\n\n\t\t\t\tforwardTsnSupportedParameter->Consolidate();\n\t\t\t}\n\n\t\t\tif (\n\t\t\t  this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod !=\n\t\t\t  ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE)\n\t\t\t{\n\t\t\t\tauto* zeroChecksumAcceptableParameter =\n\t\t\t\t  chunk->BuildParameterInPlace<ZeroChecksumAcceptableParameter>();\n\n\t\t\t\tzeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod(\n\t\t\t\t  this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod);\n\t\t\t\tzeroChecksumAcceptableParameter->Consolidate();\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::CreateTransmissionControlBlock(\n\t\t  uint32_t localVerificationTag,\n\t\t  uint32_t remoteVerificationTag,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  uint64_t tieTag,\n\t\t  const NegotiatedCapabilities& negotiatedCapabilities)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->tcb = std::make_unique<TransmissionControlBlock>(\n\t\t\t  this->associationListenerDeferrer,\n\t\t\t  this->sctpOptions,\n\t\t\t  this->shared,\n\t\t\t  this->sendQueue,\n\t\t\t  this->packetSender,\n\t\t\t  localVerificationTag,\n\t\t\t  remoteVerificationTag,\n\t\t\t  localInitialTsn,\n\t\t\t  remoteInitialTsn,\n\t\t\t  remoteAdvertisedReceiverWindowCredit,\n\t\t\t  tieTag,\n\t\t\t  negotiatedCapabilities,\n\t\t\t  this->maxPacketLength,\n\t\t\t  [this]()\n\t\t\t  {\n\t\t\t\t  return this->state == State::ESTABLISHED;\n\t\t\t  });\n\n\t\t\tthis->privateMetrics.negotiatedMaxOutboundStreams =\n\t\t\t  negotiatedCapabilities.negotiatedMaxOutboundStreams;\n\t\t\tthis->privateMetrics.negotiatedMaxInboundStreams =\n\t\t\t  negotiatedCapabilities.negotiatedMaxInboundStreams;\n\t\t\tthis->privateMetrics.usesPartialReliability  = negotiatedCapabilities.partialReliability;\n\t\t\tthis->privateMetrics.usesMessageInterleaving = negotiatedCapabilities.messageInterleaving;\n\t\t\tthis->privateMetrics.usesReConfig            = negotiatedCapabilities.reConfig;\n\t\t\tthis->privateMetrics.usesZeroChecksum        = negotiatedCapabilities.zeroChecksum;\n\t\t}\n\n\t\tstd::unique_ptr<Packet> Association::CreatePacket() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn CreatePacketWithVerificationTag(0);\n\t\t}\n\n\t\tstd::unique_ptr<Packet> Association::CreatePacketWithVerificationTag(uint32_t verificationTag) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto packet =\n\t\t\t  std::unique_ptr<Packet>{ Packet::Factory(PacketFactoryBuffer, this->maxPacketLength) };\n\n\t\t\tpacket->SetSourcePort(this->sctpOptions.sourcePort);\n\t\t\tpacket->SetDestinationPort(this->sctpOptions.destinationPort);\n\t\t\tpacket->SetVerificationTag(verificationTag);\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tvoid Association::SendInitChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto packet = CreatePacket();\n\n\t\t\t// Insert an INIT Chunk in the Packet.\n\t\t\tauto* initChunk = packet->BuildChunkInPlace<InitChunk>();\n\n\t\t\tinitChunk->SetInitiateTag(this->preTcb.localVerificationTag);\n\t\t\tinitChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize);\n\t\t\tinitChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams);\n\t\t\tinitChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams);\n\t\t\tinitChunk->SetInitialTsn(this->preTcb.localInitialTsn);\n\n\t\t\t// Insert capabilities related Parameters in the INIT Chunk.\n\t\t\tAddCapabilitiesParametersToInitOrInitAckChunk(initChunk);\n\n\t\t\tinitChunk->Consolidate();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9653#section-5.2\n\t\t\t//\n\t\t\t// \"When a sender sends a packet containing an INIT chunk, it MUST include\n\t\t\t// a correct CRC32c checksum in the packet containing the INIT chunk.\"\n\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t}\n\n\t\tvoid Association::SendShutdownChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertHasTcb();\n\n\t\t\tauto packet         = this->tcb->CreatePacket();\n\t\t\tauto* shutdownChunk = packet->BuildChunkInPlace<ShutdownChunk>();\n\n\t\t\tshutdownChunk->SetCumulativeTsnAck(this->tcb->GetDataTracker().GetLastCumulativeAckedTsn());\n\t\t\tshutdownChunk->Consolidate();\n\n\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t}\n\n\t\tvoid Association::SendShutdownAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertHasTcb();\n\n\t\t\tauto packet                  = this->tcb->CreatePacket();\n\t\t\tconst auto* shutdownAckChunk = packet->BuildChunkInPlace<ShutdownAckChunk>();\n\n\t\t\tshutdownAckChunk->Consolidate();\n\n\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\tthis->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());\n\t\t\tthis->t2ShutdownTimer->Start();\n\t\t}\n\n\t\tvoid Association::MaySendShutdownOrShutdownAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertHasTcb();\n\n\t\t\tif (this->tcb->GetRetransmissionQueue().GetUnackedItems() != 0)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"Once all its outstanding data has been acknowledged, the endpoint\n\t\t\t// sends a SHUTDOWN chunk to its peer, including in the Cumulative TSN Ack\n\t\t\t// field the last sequential TSN it has received from the peer. It SHOULD\n\t\t\t// then start the T2-shutdown timer and enter the SHUTDOWN-SENT state.\"\n\t\t\tif (this->state == State::SHUTDOWN_PENDING)\n\t\t\t{\n\t\t\t\tSendShutdownChunk();\n\n\t\t\t\tthis->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());\n\t\t\t\tthis->t2ShutdownTimer->Start();\n\n\t\t\t\tSetState(State::SHUTDOWN_SENT, \"no more outstanding data\");\n\t\t\t}\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"If the receiver of the SHUTDOWN chunk has no more outstanding DATA\n\t\t\t// chunks, the SHUTDOWN chunk receiver MUST send a SHUTDOWN ACK chunk and\n\t\t\t// start a T2-shutdown timer of its own, entering the SHUTDOWN-ACK-SENT\n\t\t\t// state. If the timer expires, the endpoint MUST resend the SHUTDOWN ACK\n\t\t\t// chunk.\"\n\t\t\telse if (this->state == State::SHUTDOWN_RECEIVED)\n\t\t\t{\n\t\t\t\tSendShutdownAckChunk();\n\t\t\t\tSetState(State::SHUTDOWN_ACK_SENT, \"no more outstanding data\");\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::MaySendShutdownOnPacketReceived(const Packet* receivedPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state != State::SHUTDOWN_SENT)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tAssertHasTcb();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"While in the SHUTDOWN-SENT state, the SHUTDOWN chunk sender MUST\n\t\t\t// immediately respond to each received packet containing one or more\n\t\t\t// DATA chunks with a SHUTDOWN chunk and restart the T2-shutdown timer.\"\n\t\t\t//\n\t\t\t// @remarks\n\t\t\t// - This also applies to I-DATA chunks.\n\t\t\tconst bool hasDataChunk = std::find_if(\n\t\t\t                            receivedPacket->ChunksBegin(),\n\t\t\t                            receivedPacket->ChunksEnd(),\n\t\t\t                            [](const Chunk* chunk)\n\t\t\t                            {\n\t\t\t\t                            return chunk->GetType() == Chunk::ChunkType::DATA ||\n\t\t\t\t                                   chunk->GetType() == Chunk::ChunkType::I_DATA;\n\t\t\t                            }) != receivedPacket->ChunksEnd();\n\n\t\t\tif (hasDataChunk)\n\t\t\t{\n\t\t\t\tSendShutdownChunk();\n\n\t\t\t\tthis->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs());\n\t\t\t\tthis->t2ShutdownTimer->Start();\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::MaySendResetStreamsRequest()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertHasTcb();\n\n\t\t\tif (this->tcb->GetStreamResetHandler().ShouldSendStreamResetRequest())\n\t\t\t{\n\t\t\t\tauto packet = this->tcb->CreatePacket();\n\n\t\t\t\tthis->tcb->GetStreamResetHandler().AddStreamResetRequest(packet.get());\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::MayDeliverMessages()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertHasTcb();\n\n\t\t\twhile (std::optional<Message> message = this->tcb->GetReassemblyQueue().GetNextMessage())\n\t\t\t{\n\t\t\t\tthis->privateMetrics.rxMessagesCount++;\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationMessageReceived(*std::move(message));\n\t\t\t}\n\t\t}\n\n\t\tTypes::SendMessageStatus Association::InternalSendMessageCheck(\n\t\t  const Message& message, const SendMessageOptions& sendMessageOptions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto lifecycleId = sendMessageOptions.lifecycleId;\n\n\t\t\tif (message.GetPayloadLength() == 0)\n\t\t\t{\n\t\t\t\tif (lifecycleId.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());\n\t\t\t\t}\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PROTOCOL_VIOLATION, \"cannot send empty message\");\n\n\t\t\t\treturn Types::SendMessageStatus::ERROR_MESSAGE_EMPTY;\n\t\t\t}\n\t\t\telse if (message.GetPayloadLength() > this->sctpOptions.maxSendMessageSize)\n\t\t\t{\n\t\t\t\tif (lifecycleId.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());\n\t\t\t\t}\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PROTOCOL_VIOLATION, \"cannot send too large message\");\n\n\t\t\t\treturn Types::SendMessageStatus::ERROR_MESSAGE_TOO_LARGE;\n\t\t\t}\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"An endpoint SHOULD reject any new data request from its upper layer\n\t\t\t// if it is in the SHUTDOWN-PENDING, SHUTDOWN-SENT, SHUTDOWN-RECEIVED, or\n\t\t\t// SHUTDOWN-ACK-SENT state.\"\n\t\t\telse if (\n\t\t\t  this->state == State::SHUTDOWN_PENDING || this->state == State::SHUTDOWN_SENT ||\n\t\t\t  this->state == State::SHUTDOWN_RECEIVED || this->state == State::SHUTDOWN_ACK_SENT)\n\t\t\t{\n\t\t\t\tif (lifecycleId.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());\n\t\t\t\t}\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::WRONG_SEQUENCE,\n\t\t\t\t  \"cannot send message as the association is shutting down\");\n\n\t\t\t\treturn Types::SendMessageStatus::ERROR_SHUTTING_DOWN;\n\t\t\t}\n\t\t\telse if (\n\t\t\t  this->sendQueue.GetTotalBufferedAmount() >= this->sctpOptions.maxSendBufferSize ||\n\t\t\t  this->sendQueue.GetStreamBufferedAmount(message.GetStreamId()) >=\n\t\t\t    this->sctpOptions.perStreamSendQueueLimit)\n\t\t\t{\n\t\t\t\tif (lifecycleId.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value());\n\t\t\t\t}\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::RESOURCE_EXHAUSTION, \"cannot send message as the send queue is full\");\n\n\t\t\t\treturn Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION;\n\t\t\t}\n\n\t\t\treturn Types::SendMessageStatus::SUCCESS;\n\t\t}\n\n\t\tbool Association::ValidateReceivedPacket(const Packet* receivedPacket)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint32_t localVerificationTag = this->tcb ? this->tcb->GetLocalVerificationTag() : 0;\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1\n\t\t\t//\n\t\t\t// \"When an endpoint receives an SCTP packet with the Verification Tag\n\t\t\t// set to 0, it SHOULD verify that the packet contains only an INIT\n\t\t\t// chunk. Otherwise, the receiver MUST silently discard the packet.\"\n\t\t\tif (receivedPacket->GetVerificationTag() == 0)\n\t\t\t{\n\t\t\t\tif (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"Packet with Verification Tag 0 must have a single Chunk and it must be an INIT Chunk, packet discarded\");\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::PARSE_FAILED,\n\t\t\t\t\t  \"packet with Verification Tag 0 must have a single chunk and it must be an INIT chunk\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1\n\t\t\t//\n\t\t\t// \"The receiver of an ABORT chunk MUST accept the packet if the\n\t\t\t// Verification Tag field of the packet matches its own tag and the T bit\n\t\t\t// is not set OR if it is set to its Peer's Tag and the T bit is set in\n\t\t\t// the Chunk Flags. Otherwise, the receiver MUST silently discard the\n\t\t\t// packet and take no further action.\"\n\t\t\tif (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::ABORT)\n\t\t\t{\n\t\t\t\tconst auto* abortAssociationChunk =\n\t\t\t\t  static_cast<const AbortAssociationChunk*>(receivedPacket->GetChunkAt(0));\n\n\t\t\t\t// We cannot verify the Verification Tag so assume it's okey.\n\t\t\t\tif (abortAssociationChunk->GetT() && !this->tcb)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse if (\n\t\t\t\t  (!abortAssociationChunk->GetT() &&\n\t\t\t\t   receivedPacket->GetVerificationTag() == localVerificationTag) ||\n\t\t\t\t  (abortAssociationChunk->GetT() &&\n\t\t\t\t   receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag()))\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"ABORT Chunk Verification Tag %\" PRIu32 \" is wrong, packet discarded\",\n\t\t\t\t\t  receivedPacket->GetVerificationTag());\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"packet with ABORT chunk has invalid Verification Tag\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT_ACK)\n\t\t\t{\n\t\t\t\tif (receivedPacket->GetVerificationTag() == this->preTcb.localVerificationTag)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"INIT_ACK Chunk Verification Tag %\" PRIu32 \" (should be %\" PRIu32 \")\",\n\t\t\t\t\t  receivedPacket->GetVerificationTag(),\n\t\t\t\t\t  this->preTcb.localVerificationTag);\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::PARSE_FAILED,\n\t\t\t\t\t  \"packet with INIT_ACK chunk has invalid Verification Tag\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.4\n\t\t\t//\n\t\t\t// This is handled in HandleReceivedCookieEchoChunk().\n\t\t\tif (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::COOKIE_ECHO)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1\n\t\t\t//\n\t\t\t// \"The receiver of a SHUTDOWN COMPLETE shall accept the packet if the\n\t\t\t// Verification Tag field of the packet matches its own tag and the T bit is\n\t\t\t// not set OR if it is set to its peer's tag and the T bit is set in the\n\t\t\t// Chunk Flags.  Otherwise, the receiver MUST silently discard the packet\n\t\t\t// and take no further action.\"\n\t\t\tif (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::SHUTDOWN_COMPLETE)\n\t\t\t{\n\t\t\t\tconst auto* shutdownCompleteChunk =\n\t\t\t\t  static_cast<const ShutdownCompleteChunk*>(receivedPacket->GetChunkAt(0));\n\n\t\t\t\t// We cannot verify the Verification Tag so assume it's okey.\n\t\t\t\tif (shutdownCompleteChunk->GetT() && !this->tcb)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse if (\n\t\t\t\t  (!shutdownCompleteChunk->GetT() &&\n\t\t\t\t   receivedPacket->GetVerificationTag() == localVerificationTag) ||\n\t\t\t\t  (shutdownCompleteChunk->GetT() &&\n\t\t\t\t   receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag()))\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"SHUTDOWN_COMPLETE Chunk Verification Tag %\" PRIu32 \" is wrong, packet discarded\",\n\t\t\t\t\t  receivedPacket->GetVerificationTag());\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::PARSE_FAILED,\n\t\t\t\t\t  \"packet with SHUTDOWN_COMPLETE chunk has invalid Verification Tag\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.5\n\t\t\t//\n\t\t\t// \"When receiving an SCTP packet, the endpoint MUST ensure that the\n\t\t\t// value in the Verification Tag field of the received SCTP packet\n\t\t\t// matches its own tag. If the received Verification Tag value does not\n\t\t\t// match the receiver's own tag value, the receiver MUST silently discard\n\t\t\t// the packet and MUST NOT process it any further, except for those cases\n\t\t\t// listed in Section 8.5.1 below.\"\n\t\t\tif (receivedPacket->GetVerificationTag() == localVerificationTag)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"invalid Verification Tag %\" PRIu32 \" (should be %\" PRIu32 \")\",\n\t\t\t\t  receivedPacket->GetVerificationTag(),\n\t\t\t\t  localVerificationTag);\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"packet has invalid Verification Tag\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tbool Association::HandleReceivedChunk(const Packet* receivedPacket, const Chunk* receivedChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (receivedChunk->GetType())\n\t\t\t{\n\t\t\t\tcase Chunk::ChunkType::INIT:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedInitChunk(receivedPacket, static_cast<const InitChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::INIT_ACK:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedInitAckChunk(receivedPacket, static_cast<const InitAckChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::COOKIE_ECHO:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedCookieEchoChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const CookieEchoChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::COOKIE_ACK:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedCookieAckChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const CookieAckChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::SHUTDOWN:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedShutdownChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const ShutdownChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::SHUTDOWN_ACK:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedShutdownAckChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const ShutdownAckChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::SHUTDOWN_COMPLETE:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedShutdownCompleteChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const ShutdownCompleteChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::OPERATION_ERROR:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedOperationErrorChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const OperationErrorChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::ABORT:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedAbortAssociationChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const AbortAssociationChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::HEARTBEAT_REQUEST:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedHeartbeatRequestChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const HeartbeatRequestChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::HEARTBEAT_ACK:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedHeartbeatAckChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const HeartbeatAckChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::RE_CONFIG:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedReConfigChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const ReConfigChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::FORWARD_TSN:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedForwardTsnChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const ForwardTsnChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::I_FORWARD_TSN:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedIForwardTsnChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const IForwardTsnChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::DATA:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedDataChunk(receivedPacket, static_cast<const DataChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::I_DATA:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedIDataChunk(receivedPacket, static_cast<const IDataChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Chunk::ChunkType::SACK:\n\t\t\t\t{\n\t\t\t\t\tHandleReceivedSackChunk(receivedPacket, static_cast<const SackChunk*>(receivedChunk));\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\treturn HandleReceivedUnknownChunk(\n\t\t\t\t\t  receivedPacket, static_cast<const UnknownChunk*>(receivedChunk));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid Association::HandleReceivedInitChunk(\n\t\t  const Packet* /*receivedPacket*/, const InitChunk* receivedInitChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-3.3.2\n\t\t\t//\n\t\t\t// \"If the value of the Initiate Tag in a received INIT chunk is found to\n\t\t\t// be 0, the receiver MUST silently discard the packet.\"\n\t\t\tif (receivedInitChunk->GetInitiateTag() == 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"invalid value 0 in Initiate Tagin received INIT Chunk, discarded\");\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-3.3.2\n\t\t\t//\n\t\t\t// \"A receiver of an INIT chunk with the OS value set to 0 MUST discard\n\t\t\t// the packet, SHOULD send a packet in response containing an ABORT chunk\n\t\t\t// and using the Initiate Tag as the Verification Tag.\"\n\t\t\t//\n\t\t\t// \"A receiver of an INIT chunk with the MIS value set to 0 MUST discard\n\t\t\t// the packet, SHOULD send a packet in response containing an ABORT chunk\n\t\t\t// and using the Initiate Tag as the Verification Tag.\"\n\t\t\telse if (\n\t\t\t  receivedInitChunk->GetNumberOfOutboundStreams() == 0 or\n\t\t\t  receivedInitChunk->GetNumberOfInboundStreams() == 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"invalidNumber of Outbound Streams or Number of Inbound Streams in received INIT Chunk, aborting Association\");\n\n\t\t\t\tauto packet                 = CreatePacketWithVerificationTag(0);\n\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t// NOTE: We are not setting the Verification Tag expected by the peer\n\t\t\t\t// so must set be T to 1.\n\t\t\t\tabortAssociationChunk->SetT(true);\n\n\t\t\t\tauto* protocolViolationErrorCause =\n\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();\n\n\t\t\t\tprotocolViolationErrorCause->SetAdditionalInformation(\n\t\t\t\t  \"invalid value 0 in Number of Outbound Streams or Number of Inbound Streams in received INIT chunk\");\n\n\t\t\t\tprotocolViolationErrorCause->Consolidate();\n\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\tInternalClose(Types::ErrorKind::PROTOCOL_VIOLATION, \"received invalid INIT chunk\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"If an endpoint is in the SHUTDOWN-ACK-SENT state and receives an INIT\n\t\t\t// chunk (e.g., if the SHUTDOWN COMPLETE chunk was lost) with source and\n\t\t\t// destination transport addresses (either in the IP addresses or in the\n\t\t\t// INIT chunk) that belong to this association, it SHOULD discard the\n\t\t\t// INIT chunk and retransmit the SHUTDOWN ACK chunk.\"\n\t\t\tif (this->state == State::SHUTDOWN_ACK_SENT)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp, \"INIT Chunk received in SHUTDOWN_ACK_SENT state, retransmitting SHUTDOWN_ACK Chunk\");\n\n\t\t\t\tSendShutdownAckChunk();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tuint64_t tieTag{ 0 };\n\t\t\tuint32_t localVerificationTag;\n\t\t\tuint32_t localInitialTsn;\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase State::NEW:\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(sctp, \"INIT Chunk received in NEW state (normal scenario)\");\n\n\t\t\t\t\tlocalVerificationTag =\n\t\t\t\t\t  Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);\n\t\t\t\t\tlocalInitialTsn = Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"ignoring INIT Chunk received in CLOSED state)\");\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.1\n\t\t\t\t//\n\t\t\t\t// \"This usually indicates an initialization collision, i.e., each\n\t\t\t\t// endpoint is attempting, at about the same time, to establish an\n\t\t\t\t// association with the other endpoint. Upon receipt of an INIT chunk\n\t\t\t\t// in the COOKIE-WAIT state, an endpoint MUST respond with an INIT ACK\n\t\t\t\t// chunk using the same parameters it sent in its original INIT chunk\n\t\t\t\t// (including its Initiate Tag, unchanged).\"\n\t\t\t\tcase State::COOKIE_WAIT:\n\t\t\t\tcase State::COOKIE_ECHOED:\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(sctp, \"INIT Chunk received after sending INIT Chunk (collision, no problem)\");\n\n\t\t\t\t\tlocalVerificationTag = this->preTcb.localVerificationTag;\n\t\t\t\t\tlocalInitialTsn      = this->preTcb.localInitialTsn;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.2\n\t\t\t\t//\n\t\t\t\t// \"The outbound SCTP packet containing this INIT ACK chunk MUST carry\n\t\t\t\t// a Verification Tag value equal to the Initiate Tag found in the\n\t\t\t\t// unexpected INIT chunk. And the INIT ACK chunk MUST contain a new\n\t\t\t\t// Initiate Tag (randomly generated; see Section 5.3.1). Other\n\t\t\t\t// parameters for the endpoint SHOULD be copied from the existing\n\t\t\t\t// parameters of the association (e.g., number of outbound streams)\n\t\t\t\t// into the INIT ACK chunk and cookie.\"\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tAssertHasTcb();\n\n\t\t\t\t\tMS_DEBUG_TAG(sctp, \"INIT Chunk received (probably peer restarted)\");\n\n\t\t\t\t\tlocalVerificationTag =\n\t\t\t\t\t  Utils::Crypto::GetRandomUInt<uint32_t>(MinVerificationTag, MaxVerificationTag);\n\n\t\t\t\t\tlocalInitialTsn = Utils::Crypto::GetRandomUInt<uint32_t>(MinInitialTsn, MaxInitialTsn);\n\t\t\t\t\ttieTag          = this->tcb->GetTieTag();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"initiating Association [localVerificationTag:%\" PRIu32 \", localInitialTsn:%\" PRIu32\n\t\t\t  \", remoteVerificationTag:%\" PRIu32 \", remoteInitialTsn:%\" PRIu32 \"]\",\n\t\t\t  localVerificationTag,\n\t\t\t  localInitialTsn,\n\t\t\t  receivedInitChunk->GetInitiateTag(),\n\t\t\t  receivedInitChunk->GetInitialTsn());\n\n\t\t\t/* Send a Packet with an INIT_ACK Chunk. */\n\n\t\t\tauto packet = CreatePacketWithVerificationTag(receivedInitChunk->GetInitiateTag());\n\n\t\t\t// Insert an INIT_ACK Chunk in the Packet.\n\t\t\tauto* initAckChunk = packet->BuildChunkInPlace<InitAckChunk>();\n\n\t\t\tinitAckChunk->SetInitiateTag(localVerificationTag);\n\t\t\tinitAckChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize);\n\t\t\tinitAckChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams);\n\t\t\tinitAckChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams);\n\t\t\tinitAckChunk->SetInitialTsn(localInitialTsn);\n\n\t\t\t// Insert a StateCookieParameter in the INIT_ACK Chunk.\n\t\t\tauto* stateCookieParameter = initAckChunk->BuildParameterInPlace<StateCookieParameter>();\n\n\t\t\tconst auto negotiatedCapabilities =\n\t\t\t  NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitChunk);\n\n\t\t\t// Write the StateCookie in place in the Parameter.\n\t\t\tstateCookieParameter->WriteStateCookieInPlace(\n\t\t\t  localVerificationTag,\n\t\t\t  receivedInitChunk->GetInitiateTag(),\n\t\t\t  localInitialTsn,\n\t\t\t  receivedInitChunk->GetInitialTsn(),\n\t\t\t  receivedInitChunk->GetAdvertisedReceiverWindowCredit(),\n\t\t\t  tieTag,\n\t\t\t  negotiatedCapabilities);\n\n\t\t\tstateCookieParameter->Consolidate();\n\n\t\t\t// Insert capabilities related Parameters in the INIT_ACK Chunk.\n\t\t\tAddCapabilitiesParametersToInitOrInitAckChunk(initAckChunk);\n\n\t\t\tinitAckChunk->Consolidate();\n\n\t\t\t// If the peer has signaled that it supports Zero Checksum, INIT-ACK can\n\t\t\t// then have its checksum as zero.\n\t\t\tthis->packetSender.SendPacket(\n\t\t\t  packet.get(), /*writeChecksum*/ !negotiatedCapabilities.zeroChecksum);\n\t\t}\n\n\t\tvoid Association::HandleReceivedInitAckChunk(\n\t\t  const Packet* /*receivedPacket*/, const InitAckChunk* receivedInitAckChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.3\n\t\t\t//\n\t\t\t// \"If an INIT ACK chunk is received by an endpoint in any state other\n\t\t\t// than the COOKIE-WAIT or CLOSED state, the endpoint SHOULD discard the\n\t\t\t// INIT ACK chunk.\"\n\t\t\tif (this->state != State::COOKIE_WAIT)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(sctp, \"ignoring received INIT_ACK Chunk when not in COOKIE_WAIT state\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst auto* stateCookieParameter =\n\t\t\t  receivedInitAckChunk->GetFirstParameterOfType<StateCookieParameter>();\n\n\t\t\tif (!stateCookieParameter || !stateCookieParameter->GetCookie())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"ignoring received INIT_ACK Chunk without StateCookieParameter or without Cookie\");\n\n\t\t\t\tauto packet = CreatePacketWithVerificationTag(this->preTcb.localVerificationTag);\n\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t// NOTE: We are not setting the Verification Tag expected by the peer\n\t\t\t\t// so must set be T to 1.\n\t\t\t\tabortAssociationChunk->SetT(true);\n\n\t\t\t\tauto* protocolViolationErrorCause =\n\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();\n\n\t\t\t\tprotocolViolationErrorCause->SetAdditionalInformation(\n\t\t\t\t  \"INIT_ACK without State Cookie Parameter or without Cookie\");\n\n\t\t\t\tprotocolViolationErrorCause->Consolidate();\n\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\tInternalClose(\n\t\t\t\t  Types::ErrorKind::PROTOCOL_VIOLATION, \"received INIT_ACK chunk doesn't contain a Cookie\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->privateMetrics.peerImplementation = StateCookie::DetermineSctpImplementation(\n\t\t\t  stateCookieParameter->GetCookie(), stateCookieParameter->GetCookieLength());\n\n\t\t\tthis->t1InitTimer->Stop();\n\n\t\t\tconst auto negotiatedCapabilities =\n\t\t\t  NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitAckChunk);\n\n\t\t\t// If the Association is re-established (peer restarted, but re-used old\n\t\t\t// Association), make sure that all message identifiers are reset and any\n\t\t\t// partly sent message is re-sent in full. The same is true when the\n\t\t\t// Association is closed and later re-opened, which never happens in\n\t\t\t// WebRTC, but is a valid operation on the SCTP level.\n\t\t\tthis->sendQueue.Reset();\n\n\t\t\tCreateTransmissionControlBlock(\n\t\t\t  this->preTcb.localVerificationTag,\n\t\t\t  receivedInitAckChunk->GetInitiateTag(),\n\t\t\t  this->preTcb.localInitialTsn,\n\t\t\t  receivedInitAckChunk->GetInitialTsn(),\n\t\t\t  receivedInitAckChunk->GetAdvertisedReceiverWindowCredit(),\n\t\t\t  /*tieTag*/ Utils::Crypto::GetRandomUInt<uint64_t>(0, MaxTieTag),\n\t\t\t  negotiatedCapabilities);\n\n\t\t\tSetState(State::COOKIE_ECHOED, \"INIT_ACK received\");\n\n\t\t\t// The Association isn't fully established just yet. Store the stat\n\t\t\t// cookie in the TCB.\n\t\t\tstd::vector<uint8_t> remoteStateCookie(\n\t\t\t  stateCookieParameter->GetCookie(),\n\t\t\t  stateCookieParameter->GetCookie() + stateCookieParameter->GetCookieLength());\n\n\t\t\tthis->tcb->SetRemoteStateCookie(std::move(remoteStateCookie));\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\tthis->t1CookieTimer->Start();\n\t\t\tthis->associationListenerDeferrer.OnAssociationConnecting();\n\t\t}\n\n\t\tvoid Association::HandleReceivedCookieEchoChunk(\n\t\t  const Packet* receivedPacket, const CookieEchoChunk* receivedCookieEchoChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!receivedCookieEchoChunk->HasCookie())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"ignoring received COOKIE_ECHO Chunk without Cookie\");\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"received COOKIE_ECHO Chunk without Cookie\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tstd::unique_ptr<StateCookie> cookie{ StateCookie::Parse(\n\t\t\t\treceivedCookieEchoChunk->GetCookie(), receivedCookieEchoChunk->GetCookieLength()) };\n\n\t\t\tif (!cookie)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"failed to parse Cookie in received COOKIE_ECHO Chunk\");\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"received COOKIE_ECHO Chunk with invalid Cookie\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\tif (!HandleReceivedCookieEchoChunkWithTcb(receivedPacket, cookie.get()))\n\t\t\t\t{\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (receivedPacket->GetVerificationTag() != cookie->GetLocalVerificationTag())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"received COOKIE_ECHO Chunk with invalid Verification Tag\");\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::PARSE_FAILED,\n\t\t\t\t\t  \"received COOKIE_ECHO Chunk with invalid Verification Tag\");\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->t1InitTimer->Stop();\n\t\t\tthis->t1CookieTimer->Stop();\n\n\t\t\tif (this->state != State::ESTABLISHED)\n\t\t\t{\n\t\t\t\tif (this->tcb)\n\t\t\t\t{\n\t\t\t\t\tthis->tcb->ClearRemoteStateCookie();\n\t\t\t\t}\n\n\t\t\t\tSetState(State::ESTABLISHED, \"COOKIE_ECHO received\");\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationConnected();\n\t\t\t}\n\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\t// If the Association is re-established (peer restarted, but re-used old\n\t\t\t\t// Association), make sure that all message identifiers are reset and any\n\t\t\t\t// partly sent message is re-sent in full. The same is true when the\n\t\t\t\t// Association is closed and later re-opened, which never happens in\n\t\t\t\t// WebRTC, but is a valid operation on the SCTP level.\n\t\t\t\tthis->sendQueue.Reset();\n\n\t\t\t\tCreateTransmissionControlBlock(\n\t\t\t\t  cookie->GetLocalVerificationTag(),\n\t\t\t\t  cookie->GetRemoteVerificationTag(),\n\t\t\t\t  cookie->GetLocalInitialTsn(),\n\t\t\t\t  cookie->GetRemoteInitialTsn(),\n\t\t\t\t  cookie->GetRemoteAdvertisedReceiverWindowCredit(),\n\t\t\t\t  /*tieTag*/ Utils::Crypto::GetRandomUInt<uint64_t>(0, MaxTieTag),\n\t\t\t\t  cookie->GetNegotiatedCapabilities());\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.1\n\t\t\t//\n\t\t\t// \"A COOKIE ACK chunk MAY be bundled with any pending DATA chunks (and/or\n\t\t\t// SACK chunks), but the COOKIE ACK chunk MUST be the first chunk in the\n\t\t\t// packet.\"\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->tcb->SendBufferedPackets(nowMs, /*addCookieAckChunk*/ true);\n\t\t}\n\n\t\tbool Association::HandleReceivedCookieEchoChunkWithTcb(\n\t\t  const Packet* receivedPacket, const StateCookie* cookie)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_DEV(\"handling COOKIE_ECHO with TCB\");\n\n\t\t\tAssertHasTcb();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.4\n\t\t\t//\n\t\t\t// \"Handle a COOKIE ECHO Chunk When a TCB Exists\"\n\t\t\t//\n\t\t\t// \"A) In this case, the peer might have restarted.\"\n\t\t\tif (\n\t\t\t  receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&\n\t\t\t  cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag() &&\n\t\t\t  cookie->GetTieTag() == this->tcb->GetTieTag())\n\t\t\t{\n\t\t\t\t// \"If the endpoint is in the SHUTDOWN-ACK-SENT state and recognizes\n\t\t\t\t// that the peer has restarted (Action A), it MUST NOT set up a new\n\t\t\t\t// association but instead resend the SHUTDOWN ACK chunk and send an\n\t\t\t\t// ERROR chunk with a \"Cookie Received While Shutting Down\" error cause\n\t\t\t\t// to its peer.\"\n\t\t\t\tif (this->state == State::SHUTDOWN_ACK_SENT)\n\t\t\t\t{\n\t\t\t\t\tauto packet = CreatePacketWithVerificationTag(cookie->GetRemoteVerificationTag());\n\t\t\t\t\tconst auto* shutdownAckChunk = packet->BuildChunkInPlace<ShutdownAckChunk>();\n\n\t\t\t\t\tshutdownAckChunk->Consolidate();\n\n\t\t\t\t\tauto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();\n\t\t\t\t\tconst auto* cookieReceivedWhileShuttingDownErrorCause =\n\t\t\t\t\t  operationErrorChunk->BuildErrorCauseInPlace<CookieReceivedWhileShuttingDownErrorCause>();\n\n\t\t\t\t\tcookieReceivedWhileShuttingDownErrorCause->Consolidate();\n\t\t\t\t\toperationErrorChunk->Consolidate();\n\n\t\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t\t  Types::ErrorKind::WRONG_SEQUENCE, \"received COOKIE_ECHO while shutting down\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\"received COOKIE_ECHO indicating a restarted peer\");\n\n\t\t\t\tthis->tcb = nullptr;\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationRestarted();\n\t\t\t}\n\t\t\t// \"B) In this case, both sides might be attempting to start an association\n\t\t\t// at about the same time, but the peer endpoint sent its INIT chunk after\n\t\t\t// responding to the local endpoint's INIT chunk.\"\n\t\t\telse if (\n\t\t\t  receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() &&\n\t\t\t  cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag())\n\t\t\t{\n\t\t\t\t// TODO: dcsctp: Handle the case in which remote Verification Tag is 0?\n\n\t\t\t\tMS_DEBUG_DEV(\"received COOKIE_ECHO indicating simultaneous associations\");\n\n\t\t\t\tthis->tcb = nullptr;\n\t\t\t}\n\t\t\t// \"C) In this case, the local endpoint's cookie has arrived late. Before\n\t\t\t// it arrived, the local endpoint sent an INIT chunk and received an INIT\n\t\t\t// ACK chunk and finally sent a COOKIE ECHO chunk with the peer's same tag\n\t\t\t// but a new tag of its own. The cookie SHOULD be silently discarded. The\n\t\t\t// endpoint SHOULD NOT change states and SHOULD leave any timers running.\"\n\t\t\telse if (\n\t\t\t  receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() &&\n\t\t\t  cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag() &&\n\t\t\t  cookie->GetTieTag() == this->tcb->GetTieTag())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"received COOKIE_ECHO indicating a late COOKIE_ECHO, discarding\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// \"D) When both local and remote tags match, the endpoint SHOULD enter\n\t\t\t// the ESTABLISHED state if it is in the COOKIE_ECHOED state. It SHOULD\n\t\t\t// stop any T1-cookie timer that is running and send a COOKIE ACK chunk.\"\n\t\t\telse if (\n\t\t\t  receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() &&\n\t\t\t  cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"received duplicate COOKIE_ECHO, probably because of peer not receiving COOKIE_ACK and retransmitting COOKIE_ECHO\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid Association::HandleReceivedCookieAckChunk(\n\t\t  const Packet* /*receivedPacket*/, const CookieAckChunk* /*receivedCookieAckChunk*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.5\n\t\t\t//\n\t\t\t// \"At any state other than COOKIE_ECHOED, an endpoint SHOULD silently\n\t\t\t// discard a received COOKIE ACK chunk.\"\n\t\t\tif (this->state != State::COOKIE_ECHOED)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"received COOKIE_ACK not in COOKIE_ECHOED state, discarding\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tAssertHasTcb();\n\n\t\t\tthis->t1CookieTimer->Stop();\n\t\t\tthis->tcb->ClearRemoteStateCookie();\n\n\t\t\tSetState(State::ESTABLISHED, \"COOKIE_ACK received\");\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\tthis->associationListenerDeferrer.OnAssociationConnected();\n\t\t}\n\n\t\tvoid Association::HandleReceivedShutdownChunk(\n\t\t  const Packet* /*receivedPacket*/, const ShutdownChunk* /*receivedShutdownChunk*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase State::NEW:\n\t\t\t\tcase State::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t\t//\n\t\t\t\t// \"If a SHUTDOWN chunk is received in the COOKIE-WAIT or COOKIE ECHOED\n\t\t\t\t// state, the SHUTDOWN chunk SHOULD be silently discarded.\"\n\t\t\t\tcase State::COOKIE_WAIT:\n\t\t\t\tcase State::COOKIE_ECHOED:\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t\t//\n\t\t\t\t// \"If an endpoint is in the SHUTDOWN-SENT state and receives a SHUTDOWN\n\t\t\t\t// chunk from its peer, the endpoint SHOULD respond immediately with a\n\t\t\t\t// SHUTDOWN ACK chunk to its peer and move into the SHUTDOWN-ACK-SENT\n\t\t\t\t// state, restarting its T2-shutdown timer.\n\t\t\t\tcase State::SHUTDOWN_SENT:\n\t\t\t\t{\n\t\t\t\t\tSendShutdownAckChunk();\n\t\t\t\t\tSetState(State::SHUTDOWN_ACK_SENT, \"SHUTDOWN received\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_ACK_SENT:\n\t\t\t\t{\n\t\t\t\t\tSendShutdownAckChunk();\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_RECEIVED:\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t\t//\n\t\t\t\t// \"Upon reception of the SHUTDOWN chunk, the peer endpoint does the\n\t\t\t\t// following:\n\t\t\t\t// - enter the SHUTDOWN-RECEIVED state,\n\t\t\t\t// - stop accepting new data from its SCTP user, and\n\t\t\t\t// - verify, by checking the Cumulative TSN Ack field of the chunk,\n\t\t\t\t//   that all its outstanding DATA chunks have been received by the\n\t\t\t\t//   SHUTDOWN chunk sender.\"\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"received SHUTDOWN, shutting down the Association\");\n\n\t\t\t\t\tSetState(State::SHUTDOWN_RECEIVED, \"SHUTDOWN received\");\n\t\t\t\t\tMaySendShutdownOrShutdownAckChunk();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::HandleReceivedShutdownAckChunk(\n\t\t  const Packet* receivedPacket, const ShutdownAckChunk* /*receivedShutdownAckChunk*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t\t//\n\t\t\t\t// \"Upon the receipt of the SHUTDOWN ACK chunk, the sender of the\n\t\t\t\t// SHUTDOWN chunk MUST stop the T2-shutdown timer, send a SHUTDOWN\n\t\t\t\t// COMPLETE chunk to its peer, and remove all record of the\n\t\t\t\t// association.\"\n\t\t\t\tcase State::SHUTDOWN_SENT:\n\t\t\t\tcase State::SHUTDOWN_ACK_SENT:\n\t\t\t\t{\n\t\t\t\t\tauto packet                       = this->tcb->CreatePacket();\n\t\t\t\t\tconst auto* shutdownCompleteChunk = packet->BuildChunkInPlace<ShutdownCompleteChunk>();\n\n\t\t\t\t\t// NOTE: Don't set bit T in the SHUTDOWN_COMPLETE chunk since TCB\n\t\t\t\t\t// knows the Verification Tag expected by the remote.\n\n\t\t\t\t\tshutdownCompleteChunk->Consolidate();\n\n\t\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\t\tInternalClose(Types::ErrorKind::SUCCESS, \"\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1\n\t\t\t\t//\n\t\t\t\t// \"If the receiver is in COOKIE-ECHOED or COOKIE-WAIT state, the\n\t\t\t\t// procedures in Section 8.4 SHOULD be followed; in other words, it is\n\t\t\t\t// treated as an OOTB packet.\"\n\t\t\t\t//\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.4\n\t\t\t\t//\n\t\t\t\t// \"If the packet contains a SHUTDOWN ACK chunk, the receiver SHOULD\n\t\t\t\t// respond to the sender of the OOTB packet with a SHUTDOWN COMPLETE\n\t\t\t\t// chunk. When sending the SHUTDOWN COMPLETE chunk, the receiver of the\n\t\t\t\t// OOTB packet MUST fill in the Verification Tag field of the outbound\n\t\t\t\t// packet with the Verification Tag received in the SHUTDOWN ACK chunk\n\t\t\t\t// and set the T bit in the Chunk Flags to indicate that the\n\t\t\t\t// Verification Tag is reflected.\"\n\t\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tauto packet = this->CreatePacketWithVerificationTag(receivedPacket->GetVerificationTag());\n\t\t\t\t\tauto* shutdownCompleteChunk = packet->BuildChunkInPlace<ShutdownCompleteChunk>();\n\n\t\t\t\t\tshutdownCompleteChunk->SetT(true);\n\t\t\t\t\tshutdownCompleteChunk->Consolidate();\n\n\t\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::HandleReceivedShutdownCompleteChunk(\n\t\t  const Packet* /*receivedPacket*/, const ShutdownCompleteChunk* /*receivedShutdownCompleteChunk*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->state != State::SHUTDOWN_ACK_SENT)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"Upon reception of the SHUTDOWN COMPLETE chunk, the endpoint verifies\n\t\t\t// that it is in the SHUTDOWN-ACK-SENT state; if it is not, the chunk\n\t\t\t// SHOULD be discarded. If the endpoint is in the SHUTDOWN-ACK-SENT state,\n\t\t\t// the endpoint SHOULD stop the T2-shutdown timer and remove all knowledge\n\t\t\t// of the association (and thus the association enters the CLOSED state).\"\n\t\t\tInternalClose(Types::ErrorKind::SUCCESS, \"\");\n\t\t}\n\n\t\tvoid Association::HandleReceivedOperationErrorChunk(\n\t\t  const Packet* /*receivedPacket*/, const OperationErrorChunk* receivedOperationErrorChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::string errorCausesStr;\n\n\t\t\terrorCausesStr.reserve(50);\n\n\t\t\tfor (auto it = receivedOperationErrorChunk->ErrorCausesBegin();\n\t\t\t     it != receivedOperationErrorChunk->ErrorCausesEnd();\n\t\t\t     ++it)\n\t\t\t{\n\t\t\t\tconst auto* errorCause = *it;\n\n\t\t\t\tif (!errorCausesStr.empty())\n\t\t\t\t{\n\t\t\t\t\terrorCausesStr.append(\", \");\n\t\t\t\t}\n\n\t\t\t\terrorCausesStr.append(errorCause->ToString());\n\t\t\t}\n\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"received OPERATION_ERROR Chunk on a Association with no TCB, ignoring: %s\",\n\t\t\t\t  errorCausesStr.c_str());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_WARN_TAG(sctp, \"received OPERATION_ERROR Chunk: %s\", errorCausesStr.c_str());\n\n\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t  Types::ErrorKind::PEER_REPORTED, errorCausesStr);\n\t\t}\n\n\t\tvoid Association::HandleReceivedAbortAssociationChunk(\n\t\t  const Packet* /*receivedPacket*/, const AbortAssociationChunk* receivedAbortAssociationChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::string errorCausesStr;\n\n\t\t\terrorCausesStr.reserve(50);\n\n\t\t\tfor (auto it = receivedAbortAssociationChunk->ErrorCausesBegin();\n\t\t\t     it != receivedAbortAssociationChunk->ErrorCausesEnd();\n\t\t\t     ++it)\n\t\t\t{\n\t\t\t\tconst auto* errorCause = *it;\n\n\t\t\t\tif (!errorCausesStr.empty())\n\t\t\t\t{\n\t\t\t\t\terrorCausesStr.append(\", \");\n\t\t\t\t}\n\n\t\t\t\terrorCausesStr.append(errorCause->ToString());\n\t\t\t}\n\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"received ABORT Chunk on a Association with no TCB, ignoring: %s\",\n\t\t\t\t  errorCausesStr.c_str());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_WARN_TAG(sctp, \"received ABORT Chunk, closing Association: %s\", errorCausesStr.c_str());\n\n\t\t\tInternalClose(Types::ErrorKind::PEER_REPORTED, errorCausesStr);\n\t\t}\n\n\t\tvoid Association::HandleReceivedHeartbeatRequestChunk(\n\t\t  const Packet* /*receivedPacket*/, const HeartbeatRequestChunk* receivedHeartbeatRequestChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatRequestChunk(\n\t\t\t  receivedHeartbeatRequestChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedHeartbeatAckChunk(\n\t\t  const Packet* /*receivedPacket*/, const HeartbeatAckChunk* receivedHeartbeatAckChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedReConfigChunk(\n\t\t  const Packet* /*receivedPacket*/, const ReConfigChunk* receivedReConfigChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->tcb->GetStreamResetHandler().HandleReceivedReConfigChunk(receivedReConfigChunk);\n\n\t\t\t// Handling this response may result in outgoing stream resets finishing\n\t\t\t// (either successfully or with failure). If there still are pending\n\t\t\t// streams that were waiting for this request to finish, continue\n\t\t\t// resetting them.\n\t\t\tMaySendResetStreamsRequest();\n\n\t\t\t// If a response was processed, pending to-be-reset streams may now have\n\t\t\t// become unpaused. Try to send more DATA/I_DATA chunks.\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\n\t\t\t// If it leaves \"deferred reset processing\", there may be chunks to\n\t\t\t// deliver that were queued while waiting for the stream to reset.\n\t\t\tMayDeliverMessages();\n\t\t}\n\n\t\tvoid Association::HandleReceivedForwardTsnChunk(\n\t\t  const Packet* receivedPacket, const ForwardTsnChunk* receivedForwardTsnChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tHandleReceivedAnyForwardTsnChunk(receivedPacket, receivedForwardTsnChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedIForwardTsnChunk(\n\t\t  const Packet* receivedPacket, const IForwardTsnChunk* receivedIForwardTsnChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tHandleReceivedAnyForwardTsnChunk(receivedPacket, receivedIForwardTsnChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedAnyForwardTsnChunk(\n\t\t  const Packet* /*receivedPacket*/, const AnyForwardTsnChunk* receivedAnyForwardTsnChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!this->tcb->GetNegotiatedCapabilities().partialReliability)\n\t\t\t{\n\t\t\t\tauto packet                 = this->tcb->CreatePacket();\n\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t// NOTE: Don't set bit T in the ABORT chunk since TCB knows the\n\t\t\t\t// Verification Tag expected by the remote.\n\n\t\t\t\tauto* protocolViolationErrorCause =\n\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<ProtocolViolationErrorCause>();\n\n\t\t\t\tprotocolViolationErrorCause->SetAdditionalInformation(\n\t\t\t\t  \"FORWARD-TSN or I_FORWARD-TSN chunk received but partial reliability is not negotiated\");\n\n\t\t\t\tprotocolViolationErrorCause->Consolidate();\n\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PROTOCOL_VIOLATION,\n\t\t\t\t  \"received FORWARD-TSN or I-FORWARD-TSN chunk but partial reliability is not negotiated\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this->tcb->GetDataTracker().HandleForwardTsn(receivedAnyForwardTsnChunk->GetNewCumulativeTsn()))\n\t\t\t{\n\t\t\t\tthis->tcb->GetReassemblyQueue().HandleForwardTsn(\n\t\t\t\t  receivedAnyForwardTsnChunk->GetNewCumulativeTsn(),\n\t\t\t\t  receivedAnyForwardTsnChunk->GetSkippedStreams());\n\t\t\t}\n\n\t\t\t// A forward TSN (for ordered streams) may allow messages to be delivered.\n\t\t\tMayDeliverMessages();\n\t\t}\n\n\t\tvoid Association::HandleReceivedDataChunk(\n\t\t  const Packet* receivedPacket, const DataChunk* receivedDataChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tHandleReceivedAnyDataChunk(receivedPacket, receivedDataChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedIDataChunk(\n\t\t  const Packet* receivedPacket, const IDataChunk* receivedIDataChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tHandleReceivedAnyDataChunk(receivedPacket, receivedIDataChunk);\n\t\t}\n\n\t\tvoid Association::HandleReceivedAnyDataChunk(\n\t\t  const Packet* /*receivedPacket*/, const AnyDataChunk* receivedAnyDataChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst uint32_t tsn      = receivedAnyDataChunk->GetTsn();\n\t\t\tconst bool immediateAck = receivedAnyDataChunk->GetI();\n\n\t\t\tif (receivedAnyDataChunk->GetUserDataPayloadLength() == 0)\n\t\t\t{\n\t\t\t\tauto packet               = this->tcb->CreatePacket();\n\t\t\t\tauto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();\n\t\t\t\tauto* noUserDataErrorCause =\n\t\t\t\t  operationErrorChunk->BuildErrorCauseInPlace<NoUserDataErrorCause>();\n\n\t\t\t\tnoUserDataErrorCause->SetTsn(tsn);\n\t\t\t\tnoUserDataErrorCause->Consolidate();\n\t\t\t\toperationErrorChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PROTOCOL_VIOLATION, \"received DATA or I-DATA chunk with no user data\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"data received [data length:%\" PRIu16 \", queue size:%zu, watermark:%zu, full:%s, above:%s]\",\n\t\t\t  receivedAnyDataChunk->GetUserDataPayloadLength(),\n\t\t\t  this->tcb->GetReassemblyQueue().GetQueuedBytes(),\n\t\t\t  this->tcb->GetReassemblyQueue().GetWatermarkBytes(),\n\t\t\t  this->tcb->GetReassemblyQueue().IsFull() ? \"yes\" : \"no\",\n\t\t\t  this->tcb->GetReassemblyQueue().IsAboveWatermark() ? \"yes\" : \"no\");\n\n\t\t\tif (this->tcb->GetReassemblyQueue().IsFull())\n\t\t\t{\n\t\t\t\t// If the reassembly queue is full but there are assembled messages\n\t\t\t\t// waiting to be pulled, we can't do anything with this data except drop\n\t\t\t\t// it, and hope the upper layer drains the accumulated messages soon.\n\t\t\t\tif (this->tcb->GetReassemblyQueue().HasMessages())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"received data rejected because reassembly queue is full\");\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// If the reassembly queue is full and there's no messages waiting,\n\t\t\t\t// there is nothing that can be done. The specification only allows\n\t\t\t\t// dropping gap-ack-blocks, and that's not likely to help as the\n\t\t\t\t// Association has been trying to fill gaps since the watermark was\n\t\t\t\t// reached.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tauto packet                 = this->tcb->CreatePacket();\n\t\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t\t// NOTE: Don't set bit T in the ABORT chunk since TCB knows the\n\t\t\t\t\t// Verification Tag expected by the remote.\n\n\t\t\t\t\tauto* outOfResourceErrorCause =\n\t\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<OutOfResourceErrorCause>();\n\n\t\t\t\t\toutOfResourceErrorCause->Consolidate();\n\t\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\t\tInternalClose(Types::ErrorKind::RESOURCE_EXHAUSTION, \"reassembly queue is exhausted\");\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If the reassembly queue is above its high watermark, only accept data\n\t\t\t// chunks that increase its cumulative ack tsn in an attempt to fill gaps\n\t\t\t// to deliver messages.\n\t\t\tif (this->tcb->GetReassemblyQueue().IsAboveWatermark())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"reassembly queue is above watermark\");\n\n\t\t\t\tif (!this->tcb->GetDataTracker().WillIncreaseCumAckTsn(tsn))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"reassembly queue is above watermark\");\n\n\t\t\t\t\tthis->tcb->GetDataTracker().ForceImmediateSack();\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this->tcb->GetDataTracker().IsTsnValid(tsn))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"data rejected because of failing TSN validity\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this->tcb->GetDataTracker().Observe(tsn, immediateAck))\n\t\t\t{\n\t\t\t\t// NOTE: Here we are passing an UserData r-value created and returned by\n\t\t\t\t// receivedAnyDataChunk->MakeUserData() so there is only one copy here.\n\t\t\t\t// And ReassemblyQueue::AddData() will std::move() it internally.\n\t\t\t\tthis->tcb->GetReassemblyQueue().AddData(tsn, receivedAnyDataChunk->MakeUserData());\n\n\t\t\t\tMayDeliverMessages();\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::HandleReceivedSackChunk(\n\t\t  const Packet* /*receivedPacket*/, const SackChunk* receivedSackChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateHasTcb())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tif (this->tcb->GetRetransmissionQueue().HandleReceivedSackChunk(nowMs, receivedSackChunk))\n\t\t\t{\n\t\t\t\tMaySendShutdownOrShutdownAckChunk();\n\n\t\t\t\t// Receiving an ACK may make the Association go into fast recovery mode.\n\t\t\t\t//\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t\t//\n\t\t\t\t// \"If not in Fast Recovery, determine how many of the earliest (i.e.,\n\t\t\t\t// lowest TSN) DATA chunks marked for retransmission will fit into a\n\t\t\t\t// single packet, subject to constraint of the PMTU of the destination\n\t\t\t\t// transport address to which the packet is being sent. Call this value\n\t\t\t\t// K. Retransmit those K DATA chunks in a single packet. When a Fast\n\t\t\t\t// Retransmit is being performed, the sender SHOULD ignore the value of\n\t\t\t\t// cwnd and SHOULD NOT delay retransmission for this single packet.\"\n\t\t\t\tthis->tcb->MaySendFastRetransmit();\n\n\t\t\t\t// Receiving an ACK will decrease outstanding bytes (maybe now below\n\t\t\t\t// cwnd?) or indicate packet loss that may result in sending FORWARD-TSN.\n\t\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"dropping received out-of-order SACK [TSN:%\" PRIu32 \"]\",\n\t\t\t\t  receivedSackChunk->GetCumulativeTsnAck());\n\t\t\t}\n\t\t}\n\n\t\tbool Association::HandleReceivedUnknownChunk(\n\t\t  const Packet* /*receivedPacket*/, const UnknownChunk* receivedUnknownChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto action         = receivedUnknownChunk->GetActionForUnknownChunkType();\n\t\t\tconst auto skipProcessing = action == Chunk::ActionForUnknownChunkType::SKIP ||\n\t\t\t                            action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT;\n\t\t\tconst auto reportError    = action == Chunk::ActionForUnknownChunkType::STOP_AND_REPORT ||\n\t\t\t                            action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT;\n\n\t\t\tif (skipProcessing)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"Chunk with unknown type %\" PRIu8\n\t\t\t\t  \" received, skipping further processing of Chunks in the Packet\",\n\t\t\t\t  static_cast<uint8_t>(receivedUnknownChunk->GetType()));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"ignoring received Chunk with unknown type %\" PRIu8,\n\t\t\t\t  static_cast<uint8_t>(receivedUnknownChunk->GetType()));\n\t\t\t}\n\n\t\t\tif (reportError)\n\t\t\t{\n\t\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"unknown chunk with type indicating it should be reported\");\n\n\t\t\t\t// If there is TCB (we need correct remote verification tag) send an\n\t\t\t\t// OPERATION_ERROR Chunk with a Unrecognized Chunk Type Error Cause.\n\t\t\t\tif (this->tcb)\n\t\t\t\t{\n\t\t\t\t\tauto packet               = this->tcb->CreatePacket();\n\t\t\t\t\tauto* operationErrorChunk = packet->BuildChunkInPlace<OperationErrorChunk>();\n\t\t\t\t\tauto* unrecognizedChunkTypeErrorCause =\n\t\t\t\t\t  operationErrorChunk->BuildErrorCauseInPlace<UnrecognizedChunkTypeErrorCause>();\n\n\t\t\t\t\tunrecognizedChunkTypeErrorCause->SetUnrecognizedChunk(\n\t\t\t\t\t  receivedUnknownChunk->GetBuffer(), receivedUnknownChunk->GetLength());\n\n\t\t\t\t\tunrecognizedChunkTypeErrorCause->Consolidate();\n\t\t\t\t\toperationErrorChunk->Consolidate();\n\n\t\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn !skipProcessing;\n\t\t}\n\n\t\tvoid Association::OnT1InitTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tAssertState(State::COOKIE_WAIT);\n\n\t\t\tif (this->t1InitTimer->IsRunning())\n\t\t\t{\n\t\t\t\tSendInitChunk();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tInternalClose(Types::ErrorKind::TOO_MANY_RETRIES, \"no INIT_ACK chunk received\");\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid Association::OnT1CookieTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\tAssertState(State::COOKIE_ECHOED);\n\n\t\t\tif (this->t1CookieTimer->IsRunning())\n\t\t\t{\n\t\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\t\tthis->tcb->SendBufferedPackets(nowMs);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tInternalClose(Types::ErrorKind::TOO_MANY_RETRIES, \"no COOKIE_ACK chunk received\");\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid Association::OnT2ShutdownTimer(uint64_t& baseTimeoutMs, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertState(State::SHUTDOWN_SENT, State::SHUTDOWN_ACK_SENT);\n\t\t\tAssertHasTcb();\n\n\t\t\tconst AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer);\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"An endpoint SHOULD limit the number of retransmissions of the\n\t\t\t// SHUTDOWN chunk to the protocol parameter 'Association.Max.Retrans'. If\n\t\t\t// this threshold is exceeded, the endpoint SHOULD destroy the TCB and\n\t\t\t// SHOULD report the peer endpoint unreachable to the upper layer (and\n\t\t\t// thus the association enters the CLOSED state).\"\n\t\t\tif (!this->t2ShutdownTimer->IsRunning())\n\t\t\t{\n\t\t\t\tauto packet                 = this->tcb->CreatePacket();\n\t\t\t\tauto* abortAssociationChunk = packet->BuildChunkInPlace<AbortAssociationChunk>();\n\n\t\t\t\t// NOTE: Don't set bit T in the ABORT chunk since TCB knows the\n\t\t\t\t// Verification Tag expected by the remote.\n\n\t\t\t\tauto* userInitiatedAbortErrorCause =\n\t\t\t\t  abortAssociationChunk->BuildErrorCauseInPlace<UserInitiatedAbortErrorCause>();\n\n\t\t\t\tuserInitiatedAbortErrorCause->SetUpperLayerAbortReason(\n\t\t\t\t  \"too many retransmissions of SHUTDOWN chunk\");\n\n\t\t\t\tuserInitiatedAbortErrorCause->Consolidate();\n\t\t\t\tabortAssociationChunk->Consolidate();\n\n\t\t\t\tthis->packetSender.SendPacket(packet.get());\n\n\t\t\t\tInternalClose(Types::ErrorKind::TOO_MANY_RETRIES, \"no SHUTDOWN_ACK chunk received\");\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"the SHUTDOWN chunk receiver MUST send a SHUTDOWN ACK chunk and start\n\t\t\t// a T2-shutdown timer of its own, entering the SHUTDOWN-ACK-SENT state.\n\t\t\t// If the timer expires, the endpoint MUST resend the SHUTDOWN ACK chunk.\"\n\t\t\tif (this->state == State::SHUTDOWN_ACK_SENT)\n\t\t\t{\n\t\t\t\tSendShutdownAckChunk();\n\t\t\t}\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-9.2\n\t\t\t//\n\t\t\t// \"It SHOULD then start the T2-shutdown timer and enter the SHUTDOWN-SENT\n\t\t\t// state. If the timer expires, the endpoint MUST resend the SHUTDOWN\n\t\t\t// chunk with the updated last sequential TSN received from its peer.\"\n\t\t\telse\n\t\t\t{\n\t\t\t\tSendShutdownChunk();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\tbaseTimeoutMs = this->tcb->GetCurrentRtoMs();\n\t\t}\n\n\t\ttemplate<typename... States>\n\t\tvoid Association::AssertState(States... expectedStates) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic_assert((std::is_same_v<States, State> && ...), \"all arguments must be of type State\");\n\n\t\t\t// NOTE: Using fold expression operator.\n\t\t\tif ((... || (this->state == expectedStates)))\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst auto currentStateStringView = Association::StateToString(this->state);\n\t\t\tstd::ostringstream expectedStatesOss;\n\t\t\tbool firstExpectedState = true;\n\n\t\t\t// NOTE: Using fold expression operator.\n\t\t\t((expectedStatesOss << (firstExpectedState ? \"\" : \", \")\n\t\t\t                    << Association::StateToString(expectedStates),\n\t\t\t  firstExpectedState = false),\n\t\t\t ...);\n\n\t\t\tauto expectedStatesString = expectedStatesOss.str();\n\n\t\t\tMS_ABORT(\n\t\t\t  \"current internal state %.*s does not match any of the given expected states (%s)\",\n\t\t\t  static_cast<int>(currentStateStringView.size()),\n\t\t\t  currentStateStringView.data(),\n\t\t\t  expectedStatesString.c_str());\n\t\t}\n\n\t\ttemplate<typename... States>\n\t\tvoid Association::AssertNotState(States... unexpectedStates) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic_assert((std::is_same_v<States, State> && ...), \"all arguments must be of type State\");\n\n\t\t\t// NOTE: Using fold expression operator.\n\t\t\tif ((... || (this->state == unexpectedStates)))\n\t\t\t{\n\t\t\t\tconst auto currentStateStringView = Association::StateToString(this->state);\n\t\t\t\tstd::ostringstream unexpectedStatesOss;\n\t\t\t\tbool firstUnexpectedState = true;\n\n\t\t\t\t// NOTE: Using fold expression operator.\n\t\t\t\t((unexpectedStatesOss << (firstUnexpectedState ? \"\" : \", \")\n\t\t\t\t                      << Association::StateToString(unexpectedStates),\n\t\t\t\t  firstUnexpectedState = false),\n\t\t\t\t ...);\n\n\t\t\t\tconst auto unexpectedStatesString = unexpectedStatesOss.str();\n\n\t\t\t\tMS_ABORT(\n\t\t\t\t  \"current internal state %.*s matches one of the given unexpected states (%s)\",\n\t\t\t\t  static_cast<int>(currentStateStringView.size()),\n\t\t\t\t  currentStateStringView.data(),\n\t\t\t\t  unexpectedStatesString.c_str());\n\t\t\t}\n\t\t}\n\n\t\tbool Association::ValidateHasTcb()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->tcb)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tthis->associationListenerDeferrer.OnAssociationError(\n\t\t\t  Types::ErrorKind::NOT_CONNECTED,\n\t\t\t  \"received unexpected commands on association that is not connected\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tvoid Association::AssertHasTcb() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->tcb)\n\t\t\t{\n\t\t\t\tMS_ABORT(\"TCB doesn't exist\");\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  !(this->tcb && this->tcb->GetReassemblyQueue().HasMessages()),\n\t\t\t  \"this->tcb && this->tcb->GetReassemblyQueue().HasMessages()\");\n\n\t\t\tswitch (this->state)\n\t\t\t{\n\t\t\t\tcase State::NEW:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(!this->tcb, \"internal state is NEW but there is TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(), \"internal state is NEW but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is NEW but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is NEW but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(!this->tcb, \"internal state is CLOSED but there is TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(), \"internal state is CLOSED but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is CLOSED but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is CLOSED but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::COOKIE_WAIT:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(!this->tcb, \"internal state is COOKIE_WAIT but there is TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_WAIT but T1 Init timer is not running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_WAIT but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_WAIT but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::COOKIE_ECHOED:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is COOKIE_ECHOED but there is no TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_ECHOED but T1 Init timer is not running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_ECHOED but T1 Cookie timer is not running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is COOKIE_ECHOED but T2 Shutdown timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  this->tcb->HasRemoteStateCookie(),\n\t\t\t\t\t  \"internal state is COOKIE_ECHOED but TCB does't have remote state cookie\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::ESTABLISHED:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is ESTABLISHED but there is not TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is ESTABLISHED but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is ESTABLISHED but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is ESTABLISHED but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_PENDING:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is SHUTDOWN_PENDING but there is not TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_PENDING but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_PENDING but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_PENDING but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_SENT:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is SHUTDOWN_SENT but there is not TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_SENT but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_SENT but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_SENT but T2 Shutdown timer is not running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_RECEIVED:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is SHUTDOWN_RECEIVED but there is not TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_RECEIVED but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_RECEIVED but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_RECEIVED but T2 Shutdown timer is running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase State::SHUTDOWN_ACK_SENT:\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(this->tcb, \"internal state is SHUTDOWN_ACK_SENT but there is not TCB\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1InitTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_ACK_SENT but T1 Init timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->t1CookieTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_ACK_SENT but T1 Cookie timer is running\");\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  this->t2ShutdownTimer->IsRunning(),\n\t\t\t\t\t  \"internal state is SHUTDOWN_ACK_SENT but T2 Shutdown timer is not running\");\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::OnPacketSenderPacketSent(\n\t\t  PacketSender* /*packetSender*/, const Packet* /*packet*/, bool sent)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (sent)\n\t\t\t{\n\t\t\t\tthis->privateMetrics.txPacketsCount++;\n\t\t\t}\n\t\t}\n\n\t\tvoid Association::OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto maxRestarts = backoffTimer->GetMaxRestarts();\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"%s timer has expired [expìrations:%zu/%s]\",\n\t\t\t  backoffTimer->GetLabel().c_str(),\n\t\t\t  backoffTimer->GetExpirationCount(),\n\t\t\t  maxRestarts ? std::to_string(maxRestarts.value()).c_str() : \"Infinite\");\n\n\t\t\tif (backoffTimer == this->t1InitTimer.get())\n\t\t\t{\n\t\t\t\tOnT1InitTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t\telse if (backoffTimer == this->t1CookieTimer.get())\n\t\t\t{\n\t\t\t\tOnT1CookieTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t\telse if (backoffTimer == this->t2ShutdownTimer.get())\n\t\t\t{\n\t\t\t\tOnT2ShutdownTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/AssociationListenerDeferrer.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::AssociationListenerDeferrer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/AssociationListenerDeferrer.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tAssociationListenerDeferrer::ScopedDeferrer::ScopedDeferrer(\n\t\t  AssociationListenerDeferrer& listenerDeferrer)\n\t\t  : listenerDeferrer(listenerDeferrer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->listenerDeferrer.SetReady();\n\t\t}\n\n\t\t// NOLINTNEXTLINE(bugprone-exception-escape)\n\t\tAssociationListenerDeferrer::ScopedDeferrer::~ScopedDeferrer()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->listenerDeferrer.TriggerDeferredCallbacks();\n\t\t}\n\n\t\tAssociationListenerDeferrer::AssociationListenerDeferrer(AssociationListenerInterface* innerListener)\n\t\t  : innerListener(innerListener)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->deferredCallbacks.reserve(8);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::SetReady()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(!this->ready, \"already ready\");\n\n\t\t\tthis->ready = true;\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::TriggerDeferredCallbacks()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->ready = false;\n\n\t\t\tif (this->deferredCallbacks.empty())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Need to swap stored callbacks here. The caller may call into the library\n\t\t\t// from within a callback, and that might result in adding new callbacks to\n\t\t\t// this instance, and the vector can't be modified while iterated on.\n\n\t\t\tstd::vector<std::pair<Callback, CallbackData>> localDeferredCallbacks;\n\n\t\t\t// Reserve a small buffer to prevent too much reallocation on growth.\n\t\t\tlocalDeferredCallbacks.reserve(8);\n\n\t\t\tlocalDeferredCallbacks.swap(this->deferredCallbacks);\n\n\t\t\tfor (auto& [callback, data] : localDeferredCallbacks)\n\t\t\t{\n\t\t\t\tcallback(std::move(data), this->innerListener);\n\t\t\t}\n\t\t}\n\n\t\tbool AssociationListenerDeferrer::OnAssociationSendData(const uint8_t* data, size_t len)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\treturn this->innerListener->OnAssociationSendData(data, len);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationConnecting()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData /*data*/, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationConnecting();\n\t\t\t  },\n\t\t\t  std::monostate{});\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationConnected()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData /*data*/, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationConnected();\n\t\t\t  },\n\t\t\t  std::monostate{});\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationFailed(\n\t\t  Types::ErrorKind errorKind, std::string_view errorMessage)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  const Error error = std::get<Error>(std::move(data));\n\t\t\t\t  listener->OnAssociationFailed(error.errorKind, error.message);\n\t\t\t  },\n\t\t\t  Error{ .errorKind = errorKind, .message = std::string(errorMessage) });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationClosed(\n\t\t  Types::ErrorKind errorKind, std::string_view errorMessage)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  const Error error = std::get<Error>(std::move(data));\n\t\t\t\t  listener->OnAssociationClosed(error.errorKind, error.message);\n\t\t\t  },\n\t\t\t  Error{ .errorKind = errorKind, .message = std::string(errorMessage) });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationRestarted()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData /*data*/, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationRestarted();\n\t\t\t  },\n\t\t\t  std::monostate{});\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationError(\n\t\t  Types::ErrorKind errorKind, std::string_view errorMessage)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  const Error error = std::get<Error>(std::move(data));\n\t\t\t\t  listener->OnAssociationError(error.errorKind, error.message);\n\t\t\t  },\n\t\t\t  Error{ .errorKind = errorKind, .message = std::string(errorMessage) });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationMessageReceived(Message message)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationMessageReceived(std::get<Message>(std::move(data)));\n\t\t\t  },\n\t\t\t  std::move(message));\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationStreamsResetPerformed(\n\t\t  std::span<const uint16_t> outboundStreamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  StreamReset streamReset = std::get<StreamReset>(std::move(data));\n\t\t\t\t  listener->OnAssociationStreamsResetPerformed(streamReset.streamIds);\n      },\n\t\t\t  StreamReset{ .streamIds = { outboundStreamIds.begin(), outboundStreamIds.end() } });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationStreamsResetFailed(\n\t\t  std::span<const uint16_t> outboundStreamIds, std::string_view errorMessage)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  StreamReset streamReset = std::get<StreamReset>(std::move(data));\n\t\t\t\t  listener->OnAssociationStreamsResetFailed(streamReset.streamIds, streamReset.errorMessage);\n      },\n\t\t\t  StreamReset{ .streamIds    = { outboundStreamIds.begin(), outboundStreamIds.end() },\n\t\t\t               .errorMessage = std::string(errorMessage) });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationInboundStreamsReset(\n\t\t  std::span<const uint16_t> inboundStreamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  StreamReset streamReset = std::get<StreamReset>(std::move(data));\n\t\t\t\t  listener->OnAssociationInboundStreamsReset(streamReset.streamIds);\n      },\n\t\t\t  StreamReset{ .streamIds = { inboundStreamIds.begin(), inboundStreamIds.end() } });\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationStreamBufferedAmountLow(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t\t;\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData data, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationStreamBufferedAmountLow(std::get<uint16_t>(std::move(data)));\n\t\t\t  },\n\t\t\t  streamId);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationTotalBufferedAmountLow()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->ready, \"not ready\");\n\n\t\t\tthis->deferredCallbacks.emplace_back(\n\t\t\t  [](CallbackData /*data*/, AssociationListenerInterface* listener)\n\t\t\t  {\n\t\t\t\t  listener->OnAssociationTotalBufferedAmountLow();\n\t\t\t  },\n\t\t\t  std::monostate{});\n\t\t}\n\n\t\tbool AssociationListenerDeferrer::OnAssociationIsTransportReadyForSctp()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t\t;\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\treturn this->innerListener->OnAssociationIsTransportReadyForSctp();\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\tthis->innerListener->OnAssociationLifecycleMessageFullySent(lifecycleId);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationLifecycleMessageExpired(\n\t\t  uint64_t lifecycleId, bool maybeDelivered)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\tthis->innerListener->OnAssociationLifecycleMessageExpired(lifecycleId, maybeDelivered);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\tthis->innerListener->OnAssociationLifecycleMessageDelivered(lifecycleId);\n\t\t}\n\n\t\tvoid AssociationListenerDeferrer::OnAssociationLifecycleMessageEnd(uint64_t lifecycleId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Will not be deferred but called directly.\n\t\t\tthis->innerListener->OnAssociationLifecycleMessageEnd(lifecycleId);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/HeartbeatHandler.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::HeartbeatHandler\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/HeartbeatHandler.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Static. */\n\n\t\tstatic constexpr int HeartbeatInfoLength{ 8 };\n\n\t\t/* Instance methods. */\n\n\t\tHeartbeatHandler::HeartbeatHandler(\n\t\t  AssociationListenerInterface& associationListener,\n\t\t  const SctpOptions& sctpOptions,\n\t\t  SharedInterface* shared,\n\t\t  TransmissionControlBlockContextInterface* tcbContext)\n\t\t  : associationListener(associationListener),\n\t\t    sctpOptions(sctpOptions),\n\t\t    shared(shared),\n\t\t    tcbContext(tcbContext),\n\t\t    intervalDurationMs(sctpOptions.heartbeatIntervalMs),\n\t\t    intervalDurationShouldIncludeRtt(sctpOptions.heartbeatIntervalIncludeRtt),\n\t\t    intervalTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-heartbeat-interval\",\n\t\t        .baseTimeoutMs       = sctpOptions.initialRtoMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t        .maxRestarts         = std::nullopt })),\n\t\t    timeoutTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-heartbeat-timeout\",\n\t\t        .baseTimeoutMs       = sctpOptions.initialRtoMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::FIXED,\n\t\t        .maxBackoffTimeoutMs = std::nullopt,\n\t\t        .maxRestarts         = 0 }))\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// The interval timer must always be running as long as the association\n\t\t\t// is up (so when the TCB is created, which is the one that creates the\n\t\t\t// HeartbeatHandler.\n\t\t\tRestartTimer();\n\t\t}\n\n\t\tHeartbeatHandler::~HeartbeatHandler()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid HeartbeatHandler::RestartTimer()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Heartbeating has been disabled.\n\t\t\tif (this->intervalDurationMs == 0)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (intervalDurationShouldIncludeRtt)\n\t\t\t{\n\t\t\t\tthis->intervalTimer->SetBaseTimeoutMs(\n\t\t\t\t  this->intervalDurationMs + this->tcbContext->GetCurrentRtoMs());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->intervalTimer->SetBaseTimeoutMs(this->intervalDurationMs);\n\t\t\t}\n\n\t\t\tthis->intervalTimer->Start();\n\t\t}\n\n\t\tvoid HeartbeatHandler::HandleReceivedHeartbeatRequestChunk(\n\t\t  const HeartbeatRequestChunk* receivedHeartbeatRequestChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.3\n\t\t\t//\n\t\t\t// \"The receiver of the HEARTBEAT chunk SHOULD immediately respond with a\n\t\t\t// HEARTBEAT ACK chunk that contains the Heartbeat Information TLV,\n\t\t\t// together with any other received TLVs, copied unchanged from the\n\t\t\t// received HEARTBEAT chunk.\"\n\t\t\tauto packet             = this->tcbContext->CreatePacket();\n\t\t\tauto* heartbeatAckChunk = packet->BuildChunkInPlace<HeartbeatAckChunk>();\n\n\t\t\t// Here we have to extract all Parameters from receivedHeartbeatRequestChunk\n\t\t\t// and add them into heartbeatAckChunk.\n\t\t\tfor (auto it = receivedHeartbeatRequestChunk->ParametersBegin();\n\t\t\t     it != receivedHeartbeatRequestChunk->ParametersEnd();\n\t\t\t     ++it)\n\t\t\t{\n\t\t\t\tconst auto* parameter = *it;\n\n\t\t\t\theartbeatAckChunk->AddParameter(parameter);\n\t\t\t}\n\n\t\t\theartbeatAckChunk->Consolidate();\n\n\t\t\tthis->tcbContext->SendPacket(packet.get());\n\t\t}\n\n\t\tvoid HeartbeatHandler::HandleReceivedHeartbeatAckChunk(\n\t\t  const HeartbeatAckChunk* receivedHeartbeatAckChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->timeoutTimer->Stop();\n\n\t\t\tconst auto* heartbeatInfoParameter =\n\t\t\t  receivedHeartbeatAckChunk->GetFirstParameterOfType<HeartbeatInfoParameter>();\n\n\t\t\tif (!heartbeatInfoParameter)\n\t\t\t{\n\t\t\t\tthis->associationListener.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED,\n\t\t\t\t  \"ignoring HEARTBEAT_ACK chunk without Heartbeat Info parameter\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst auto* info       = heartbeatInfoParameter->GetInfo();\n\t\t\tconst uint16_t infoLen = heartbeatInfoParameter->GetInfoLength();\n\n\t\t\tif (!info)\n\t\t\t{\n\t\t\t\tthis->associationListener.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"ignoring Heartbeat Info parameter without info field\");\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if (infoLen != HeartbeatInfoLength)\n\t\t\t{\n\t\t\t\tthis->associationListener.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"ignoring Heartbeat Info parameter with wrong length\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst uint64_t createdAtMs = Utils::Byte::Get8Bytes(info, 0);\n\t\t\tconst uint64_t nowMs       = this->shared->GetTimeMs();\n\n\t\t\tif (createdAtMs > 0 && createdAtMs <= nowMs)\n\t\t\t{\n\t\t\t\tconst uint64_t rttMs = nowMs - createdAtMs;\n\n\t\t\t\tMS_DEBUG_DEV(\"valid HEARTBEAT_ACK Chunk received, calling ObserveRttMs(%\" PRIu64 \")\", rttMs);\n\n\t\t\t\tthis->tcbContext->ObserveRttMs(rttMs);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"ignoring received HEARTBEAT_ACK Chunk with invalid info content [createdAtMs:%\" PRIu64\n\t\t\t\t  \", nowMs:%\" PRIu64 \"]\",\n\t\t\t\t  createdAtMs,\n\t\t\t\t  nowMs);\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.1\n\t\t\t//\n\t\t\t// \"When a HEARTBEAT ACK chunk is received from the peer endpoint, the\n\t\t\t// counter SHOULD also be reset.\"\n\t\t\tthis->tcbContext->ClearTxErrorCounter();\n\t\t}\n\n\t\tvoid HeartbeatHandler::OnIntervalTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->tcbContext->IsAssociationEstablished())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"won't send HEARTBEAT_REQUEST when SCTP Association is not established\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->timeoutTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs());\n\t\t\tthis->timeoutTimer->Start();\n\n\t\t\talignas(8) uint8_t info[HeartbeatInfoLength];\n\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tUtils::Byte::Set8Bytes(info, 0, nowMs);\n\n\t\t\tauto packet                 = this->tcbContext->CreatePacket();\n\t\t\tauto* heartbeatRequestChunk = packet->BuildChunkInPlace<HeartbeatRequestChunk>();\n\t\t\tauto* heartbeatInfoParameter =\n\t\t\t  heartbeatRequestChunk->BuildParameterInPlace<HeartbeatInfoParameter>();\n\n\t\t\theartbeatInfoParameter->SetInfo(info, HeartbeatInfoLength);\n\t\t\theartbeatInfoParameter->Consolidate();\n\t\t\theartbeatRequestChunk->Consolidate();\n\n\t\t\tMS_DEBUG_DEV(\"sending HEARTBEAT_REQUEST Chunk with info content [nowMs:%\" PRIu64 \"]\", nowMs);\n\n\t\t\tthis->tcbContext->SendPacket(packet.get());\n\t\t}\n\n\t\tvoid HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Note that the timeout timer is not restarted. It will be started again when\n\t\t\t// the interval timer expires.\n\t\t\tMS_ASSERT(!this->timeoutTimer->IsRunning(), \"timeout timer shouldn't be running\");\n\n\t\t\tthis->tcbContext->IncrementTxErrorCounter(\"hearbeat timeout\");\n\t\t}\n\n\t\tvoid HeartbeatHandler::OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto maxRestarts = backoffTimer->GetMaxRestarts();\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"%s timer has expired [expìrations:%zu/%s]\",\n\t\t\t  backoffTimer->GetLabel().c_str(),\n\t\t\t  backoffTimer->GetExpirationCount(),\n\t\t\t  maxRestarts ? std::to_string(maxRestarts.value()).c_str() : \"Infinite\");\n\n\t\t\tif (backoffTimer == this->intervalTimer.get())\n\t\t\t{\n\t\t\t\tOnIntervalTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t\telse if (backoffTimer == this->timeoutTimer.get())\n\t\t\t{\n\t\t\t\tOnTimeoutTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/NegotiatedCapabilities.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::NegotiatedCapabilities\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tNegotiatedCapabilities NegotiatedCapabilities::Factory(\n\t\t  const SctpOptions& sctpOptions, const AnyInitChunk* remoteChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tNegotiatedCapabilities negotiatedCapabilities{};\n\n\t\t\tconst auto* remoteSupportedExtensionsParameter =\n\t\t\t  remoteChunk->template GetFirstParameterOfType<SupportedExtensionsParameter>();\n\t\t\tconst auto* remoteForwardTsnSupportedParameter =\n\t\t\t  remoteChunk->template GetFirstParameterOfType<ForwardTsnSupportedParameter>();\n\t\t\tconst auto* remoteZeroChecksumAcceptableParameter =\n\t\t\t  remoteChunk->template GetFirstParameterOfType<ZeroChecksumAcceptableParameter>();\n\n\t\t\tnegotiatedCapabilities.negotiatedMaxOutboundStreams =\n\t\t\t  std::min(sctpOptions.announcedMaxOutboundStreams, remoteChunk->GetNumberOfInboundStreams());\n\n\t\t\tnegotiatedCapabilities.negotiatedMaxInboundStreams =\n\t\t\t  std::min(sctpOptions.announcedMaxInboundStreams, remoteChunk->GetNumberOfOutboundStreams());\n\n\t\t\t// Partial Reliability Extension is negotiated if we desire it and\n\t\t\t// peer announces support via Forward-TSN-Supported Parameter or via\n\t\t\t// Supported Extensions Parameter.\n\t\t\tnegotiatedCapabilities.partialReliability =\n\t\t\t  sctpOptions.enablePartialReliability &&\n\t\t\t  (remoteForwardTsnSupportedParameter ||\n\t\t\t   (remoteSupportedExtensionsParameter &&\n\t\t\t    remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::FORWARD_TSN)));\n\n\t\t\t// Message Interleaving is negotiated if we desire it and peer\n\t\t\t// announces support via Supported Extensions Parameter.\n\t\t\tnegotiatedCapabilities.messageInterleaving =\n\t\t\t  sctpOptions.enableMessageInterleaving && remoteSupportedExtensionsParameter &&\n\t\t\t  remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::I_DATA) &&\n\t\t\t  remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::I_FORWARD_TSN);\n\n\t\t\t// Stream Re-Configuration is negotiated if peer announces support via\n\t\t\t// Supported Extensions Parameter.\n\t\t\tnegotiatedCapabilities.reConfig =\n\t\t\t  remoteSupportedExtensionsParameter &&\n\t\t\t  remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::RE_CONFIG);\n\n\t\t\t// Alternate Error Detection Method for Zero Checksum is negotiated\n\t\t\t// if we desire it and peer announces the same non-none alternate\n\t\t\t// error detection method.\n\t\t\tnegotiatedCapabilities.zeroChecksum =\n\t\t\t  sctpOptions.zeroChecksumAlternateErrorDetectionMethod !=\n\t\t\t    ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE &&\n\t\t\t  remoteZeroChecksumAcceptableParameter &&\n\t\t\t  remoteZeroChecksumAcceptableParameter->GetAlternateErrorDetectionMethod() ==\n\t\t\t    sctpOptions.zeroChecksumAlternateErrorDetectionMethod;\n\n\t\t\treturn negotiatedCapabilities;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tvoid NegotiatedCapabilities::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::NegotiatedCapabilities>\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  negotiated max outbound streams: %\" PRIu16, this->negotiatedMaxOutboundStreams);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  negotiated max inbound streams: %\" PRIu16, this->negotiatedMaxInboundStreams);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  partial reliability: %s\", this->partialReliability ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  message interleaving: %s\", this->messageInterleaving ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  re-config: %s\", this->reConfig ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  zero checksum: %s\", this->zeroChecksum ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::NegotiatedCapabilities>\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/PacketSender.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::PacketSender\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/PacketSender.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tPacketSender::PacketSender(Listener* listener, AssociationListenerInterface& associationListener)\n\t\t  : listener(listener), associationListener(associationListener)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tPacketSender::~PacketSender()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tbool PacketSender::SendPacket(Packet* packet, bool writeChecksum)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (packet->GetChunksCount() == 0)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (writeChecksum)\n\t\t\t{\n\t\t\t\tpacket->WriteCRC32cChecksum();\n\t\t\t}\n\n\t\t\t// TODO: SCTP: For testing purposes. Must be removed.\n\t\t\t{\n\t\t\t\tMS_DUMP(\">>> sending SCTP packet:\");\n\n\t\t\t\tpacket->Dump();\n\t\t\t}\n\n\t\t\tMS_ASSERT(!packet->NeedsConsolidation(), \"cannot send a SCTP packet that needs consolidation\");\n\n\t\t\tconst bool sent =\n\t\t\t  this->associationListener.OnAssociationSendData(packet->GetBuffer(), packet->GetLength());\n\n\t\t\tthis->listener->OnPacketSenderPacketSent(this, packet, sent);\n\n\t\t\tif (!sent)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"couldn't send SCTP Packet\");\n\t\t\t}\n\n\t\t\treturn sent;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/StateCookie.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Packet\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tbool StateCookie::IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength != StateCookie::StateCookieLength)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (Utils::Byte::Get8Bytes(buffer, 0) != StateCookie::Magic1)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tauto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(\n\t\t\t  const_cast<uint8_t*>(buffer) + StateCookie::NegotiatedCapabilitiesOffset);\n\n\t\t\tif (ntohs(negotiatedCapabilitiesField->magic2) != StateCookie::Magic2)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tStateCookie* StateCookie::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!StateCookie::IsMediasoupStateCookie(buffer, bufferLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"not a StateCookie generated by mediasoup\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* stateCookie = new StateCookie(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn stateCookie;\n\t\t}\n\n\t\tStateCookie* StateCookie::Factory(\n\t\t  uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  uint32_t localVerificationTag,\n\t\t  uint32_t remoteVerificationTag,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  uint64_t tieTag,\n\t\t  const NegotiatedCapabilities& negotiatedCapabilities)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// This may throw.\n\t\t\tStateCookie::Write(\n\t\t\t  buffer,\n\t\t\t  bufferLength,\n\t\t\t  localVerificationTag,\n\t\t\t  remoteVerificationTag,\n\t\t\t  localInitialTsn,\n\t\t\t  remoteInitialTsn,\n\t\t\t  remoteAdvertisedReceiverWindowCredit,\n\t\t\t  tieTag,\n\t\t\t  negotiatedCapabilities);\n\n\t\t\treturn new StateCookie(buffer, StateCookie::StateCookieLength);\n\t\t}\n\n\t\tvoid StateCookie::Write(\n\t\t  uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  uint32_t localVerificationTag,\n\t\t  uint32_t remoteVerificationTag,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  uint64_t tieTag,\n\t\t  const NegotiatedCapabilities& negotiatedCapabilities)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < StateCookie::StateCookieLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tUtils::Byte::Set8Bytes(buffer, 0, StateCookie::Magic1);\n\t\t\tUtils::Byte::Set4Bytes(buffer, 8, localVerificationTag);\n\t\t\tUtils::Byte::Set4Bytes(buffer, 12, remoteVerificationTag);\n\t\t\tUtils::Byte::Set4Bytes(buffer, 16, localInitialTsn);\n\t\t\tUtils::Byte::Set4Bytes(buffer, 20, remoteInitialTsn);\n\t\t\tUtils::Byte::Set4Bytes(buffer, 24, remoteAdvertisedReceiverWindowCredit);\n\t\t\tUtils::Byte::Set8Bytes(buffer, 28, tieTag);\n\n\t\t\tauto* negotiatedCapabilitiesField = reinterpret_cast<NegotiatedCapabilitiesField*>(\n\t\t\t  buffer + StateCookie::NegotiatedCapabilitiesOffset);\n\n\t\t\tnegotiatedCapabilitiesField->reserved = 0;\n\t\t\tnegotiatedCapabilitiesField->bitA     = negotiatedCapabilities.partialReliability;\n\t\t\tnegotiatedCapabilitiesField->bitB     = negotiatedCapabilities.messageInterleaving;\n\t\t\tnegotiatedCapabilitiesField->bitC     = negotiatedCapabilities.reConfig;\n\t\t\tnegotiatedCapabilitiesField->bitD     = negotiatedCapabilities.zeroChecksum;\n\t\t\tnegotiatedCapabilitiesField->magic2   = htons(StateCookie::Magic2);\n\t\t\tnegotiatedCapabilitiesField->negotiatedMaxOutboundStreams =\n\t\t\t  htons(negotiatedCapabilities.negotiatedMaxOutboundStreams);\n\t\t\tnegotiatedCapabilitiesField->negotiatedMaxInboundStreams =\n\t\t\t  htons(negotiatedCapabilities.negotiatedMaxInboundStreams);\n\t\t}\n\n\t\tTypes::SctpImplementation StateCookie::DetermineSctpImplementation(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < StateCookie::Magic1Length)\n\t\t\t{\n\t\t\t\treturn Types::SctpImplementation::UNKNOWN;\n\t\t\t}\n\n\t\t\tconst std::string_view magic1(reinterpret_cast<const char*>(buffer), StateCookie::Magic1Length);\n\n\t\t\tif (magic1 == \"msworker\")\n\t\t\t{\n\t\t\t\treturn Types::SctpImplementation::MEDIASOUP;\n\t\t\t}\n\t\t\telse if (magic1 == \"dcSCTP00\")\n\t\t\t{\n\t\t\t\treturn Types::SctpImplementation::DCSCTP;\n\t\t\t}\n\t\t\telse if (magic1 == \"KAME-BSD\")\n\t\t\t{\n\t\t\t\treturn Types::SctpImplementation::USRSCTP;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn Types::SctpImplementation::UNKNOWN;\n\t\t\t}\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tStateCookie::StateCookie(uint8_t* buffer, size_t bufferLength)\n\t\t  : Serializable(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(StateCookie::StateCookieLength);\n\t\t}\n\n\t\tStateCookie::~StateCookie()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid StateCookie::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto negotiatedCapabilities = GetNegotiatedCapabilities();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::StateCookie>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %zu (buffer length: %zu)\", GetLength(), GetBufferLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  local verification tag: %\" PRIu32, GetLocalVerificationTag());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  remote verification tag: %\" PRIu32, GetRemoteVerificationTag());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  local initial tsn: %\" PRIu32, GetLocalInitialTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  remote initial tsn: %\" PRIu32, GetRemoteInitialTsn());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  remote advertised receiver window credit: %\" PRIu32,\n\t\t\t  GetRemoteAdvertisedReceiverWindowCredit());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tie-tag: %\" PRIu64, GetTieTag());\n\t\t\tnegotiatedCapabilities.Dump(indentation + 1);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::StateCookie>\");\n\t\t}\n\n\t\tStateCookie* StateCookie::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedStateCookie = new StateCookie(buffer, bufferLength);\n\n\t\t\tSerializable::CloneInto(clonedStateCookie);\n\n\t\t\treturn clonedStateCookie;\n\t\t}\n\n\t\tNegotiatedCapabilities StateCookie::GetNegotiatedCapabilities() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* negotiatedCapabilitiesField = GetNegotiatedCapabilitiesField();\n\n\t\t\tNegotiatedCapabilities negotiatedCapabilities;\n\n\t\t\tnegotiatedCapabilities.negotiatedMaxOutboundStreams =\n\t\t\t  ntohs(negotiatedCapabilitiesField->negotiatedMaxOutboundStreams);\n\t\t\tnegotiatedCapabilities.negotiatedMaxInboundStreams =\n\t\t\t  ntohs(negotiatedCapabilitiesField->negotiatedMaxInboundStreams);\n\t\t\tnegotiatedCapabilities.partialReliability  = negotiatedCapabilitiesField->bitA;\n\t\t\tnegotiatedCapabilities.messageInterleaving = negotiatedCapabilitiesField->bitB;\n\t\t\tnegotiatedCapabilities.reConfig            = negotiatedCapabilitiesField->bitC;\n\t\t\tnegotiatedCapabilities.zeroChecksum        = negotiatedCapabilitiesField->bitD;\n\n\t\t\t// NOTE: No need to std::move(). Copy elision (RVO) is used for free in GCC\n\t\t\t// and clang in C++17 or higher.\n\t\t\treturn negotiatedCapabilities;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/StreamResetHandler.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::StreamResetHandler\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/StreamResetHandler.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tStreamResetHandler::StreamResetHandler(\n\t\t  AssociationListenerInterface& associationListener,\n\t\t  SharedInterface* shared,\n\t\t  TransmissionControlBlockContextInterface* tcbContext,\n\t\t  DataTracker* dataTracker,\n\t\t  ReassemblyQueue* reassemblyQueue,\n\t\t  RetransmissionQueue* retransmissionQueue)\n\t\t  : associationListener(associationListener),\n\t\t    shared(shared),\n\t\t    tcbContext(tcbContext),\n\t\t    dataTracker(dataTracker),\n\t\t    reassemblyQueue(reassemblyQueue),\n\t\t    retransmissionQueue(retransmissionQueue),\n\t\t    reConfigTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-re-config\",\n\t\t        .baseTimeoutMs       = 0,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = std::nullopt,\n\t\t        .maxRestarts         = std::nullopt,\n\t\t      })),\n\t\t    nextOutgoingReqSeqNbr(tcbContext->GetLocalInitialTsn()),\n\t\t    lastProcessedReqSeqNbr(\n\t\t      this->incomingReConfigRequestSnUnwrapper.Unwrap(tcbContext->GetRemoteInitialTsn() - 1)),\n\t\t    lastProcessedReqResult(ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tStreamResetHandler::~StreamResetHandler()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid StreamResetHandler::ResetStreams(std::span<const uint16_t> outgoingStreamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (const auto streamId : outgoingStreamIds)\n\t\t\t{\n\t\t\t\tthis->retransmissionQueue->PrepareResetStream(streamId);\n\t\t\t}\n\t\t}\n\n\t\tbool StreamResetHandler::ShouldSendStreamResetRequest() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Only send stream resets if there are streams to reset and no current\n\t\t\t// ongoing request (there can only be one at a time).\n\t\t\treturn !this->currentRequest.has_value() &&\n\t\t\t       this->retransmissionQueue->HasStreamsReadyToBeReset();\n\t\t}\n\n\t\tvoid StreamResetHandler::AddStreamResetRequest(Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(ShouldSendStreamResetRequest(), \"should not send a stream reset request\");\n\n\t\t\tthis->currentRequest.emplace(\n\t\t\t  this->retransmissionQueue->GetLastAssignedTsn(),\n\t\t\t  this->retransmissionQueue->BeginResetStreams());\n\n\t\t\tthis->reConfigTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs());\n\t\t\tthis->reConfigTimer->Start();\n\n\t\t\tAddReConfigChunk(packet);\n\t\t}\n\n\t\tvoid StreamResetHandler::HandleReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!ValidateReceivedReConfigChunk(receivedReConfigChunk))\n\t\t\t{\n\t\t\t\tthis->associationListener.OnAssociationError(\n\t\t\t\t  Types::ErrorKind::PARSE_FAILED, \"invalid RE-CONFIG command received\");\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto packet         = this->tcbContext->CreatePacket();\n\t\t\tauto* reConfigChunk = packet->BuildChunkInPlace<ReConfigChunk>();\n\n\t\t\tfor (auto it = receivedReConfigChunk->ParametersBegin();\n\t\t\t     it != receivedReConfigChunk->ParametersEnd();\n\t\t\t     ++it)\n\t\t\t{\n\t\t\t\tconst auto* parameter = *it;\n\n\t\t\t\tswitch (parameter->GetType())\n\t\t\t\t{\n\t\t\t\t\tcase Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tHandleReceivedOutgoingSsnResetRequestParameter(\n\t\t\t\t\t\t  reinterpret_cast<const OutgoingSsnResetRequestParameter*>(parameter), reConfigChunk);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tHandleReceivedIncomingSsnResetRequestParameter(\n\t\t\t\t\t\t  reinterpret_cast<const IncomingSsnResetRequestParameter*>(parameter), reConfigChunk);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::RECONFIGURATION_RESPONSE:\n\t\t\t\t\t{\n\t\t\t\t\t\tHandleReceivedReconfigurationResponseParameter(\n\t\t\t\t\t\t  reinterpret_cast<const ReconfigurationResponseParameter*>(parameter));\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treConfigChunk->Consolidate();\n\n\t\t\tif (reConfigChunk->GetParametersCount() > 0)\n\t\t\t{\n\t\t\t\tthis->tcbContext->SendPacket(packet.get());\n\t\t\t}\n\t\t}\n\n\t\tbool StreamResetHandler::ValidateReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (receivedReConfigChunk->GetParametersCount() == 1)\n\t\t\t{\n\t\t\t\tconst auto* firstParameter = receivedReConfigChunk->GetParameterAt(0);\n\n\t\t\t\tif (\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST ||\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST ||\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::SSN_TSN_RESET_REQUEST ||\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST ||\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST ||\n\t\t\t\t  firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE)\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (receivedReConfigChunk->GetParametersCount() == 2)\n\t\t\t{\n\t\t\t\tconst auto* firstParameter  = receivedReConfigChunk->GetParameterAt(0);\n\t\t\t\tconst auto* secondParameter = receivedReConfigChunk->GetParameterAt(1);\n\n\t\t\t\tif (\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE) ||\n\t\t\t\t  (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE &&\n\t\t\t\t   secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE))\n\t\t\t\t{\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMS_WARN_TAG(sctp, \"invalid set of RE-CONFIG Parameters\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tvoid StreamResetHandler::AddReConfigChunk(Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// The `reqSeqNbr` will be empty if the request has never been sent before,\n\t\t\t// or if it was sent, but the sender responded \"in progress\", and then the\n\t\t\t// `reqSeqNbr` will be cleared to re-send with a new number. But if the\n\t\t\t// request is re-sent due to timeout (re-config timer expiring), the same\n\t\t\t// `reqSeqNbr` will be used.\n\t\t\tMS_ASSERT(this->currentRequest.has_value(), \"currentRequest optional must have value\");\n\n\t\t\tif (this->currentRequest->HasBeenSent())\n\t\t\t{\n\t\t\t\tthis->currentRequest->PrepareToSend(this->nextOutgoingReqSeqNbr);\n\t\t\t\tthis->nextOutgoingReqSeqNbr = uint32_t{ this->nextOutgoingReqSeqNbr + 1 };\n\t\t\t}\n\n\t\t\tauto* reConfigChunk = packet->BuildChunkInPlace<ReConfigChunk>();\n\t\t\tauto* outgoingSsnResetRequestParameter =\n\t\t\t  reConfigChunk->BuildParameterInPlace<OutgoingSsnResetRequestParameter>();\n\n\t\t\toutgoingSsnResetRequestParameter->SetReconfigurationRequestSequenceNumber(\n\t\t\t  this->currentRequest->GetReqSeqNbr());\n\t\t\toutgoingSsnResetRequestParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t  this->currentRequest->GetReqSeqNbr());\n\t\t\toutgoingSsnResetRequestParameter->SetSenderLastAssignedTsn(\n\t\t\t  this->currentRequest->GetSenderLastAssignedTsn());\n\n\t\t\tfor (const auto& streamId : this->currentRequest->GetStreamIds())\n\t\t\t{\n\t\t\t\toutgoingSsnResetRequestParameter->AddStreamId(streamId);\n\t\t\t}\n\n\t\t\toutgoingSsnResetRequestParameter->Consolidate();\n\t\t\treConfigChunk->Consolidate();\n\t\t}\n\n\t\tStreamResetHandler::ReqSeqNbrValidationResult StreamResetHandler::ValidateReqSeqNbr(\n\t\t  StreamResetHandler::UnwrappedReConfigRequestSn reqSeqNbr)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (reqSeqNbr == this->lastProcessedReqSeqNbr)\n\t\t\t{\n\t\t\t\treturn ReqSeqNbrValidationResult::RETRANSMISSION;\n\t\t\t}\n\t\t\telse if (reqSeqNbr != this->lastProcessedReqSeqNbr.GetNextValue())\n\t\t\t{\n\t\t\t\t// Too old, too new, from wrong Association, etc.\n\t\t\t\tMS_WARN_TAG(sctp, \"bad reqSeqNbr: %\" PRIu32, reqSeqNbr.Wrap());\n\n\t\t\t\treturn ReqSeqNbrValidationResult::BAD_SEQUENCE_NUMBER;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn ReqSeqNbrValidationResult::VALID;\n\t\t\t}\n\t\t}\n\n\t\tvoid StreamResetHandler::HandleReceivedOutgoingSsnResetRequestParameter(\n\t\t  const OutgoingSsnResetRequestParameter* receivedOutgoingSsnResetRequestParameter,\n\t\t  ReConfigChunk* reConfigChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst UnwrappedReConfigRequestSn requestSn = this->incomingReConfigRequestSnUnwrapper.Unwrap(\n\t\t\t  receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\tconst ReqSeqNbrValidationResult validationResult = ValidateReqSeqNbr(requestSn);\n\n\t\t\tif (validationResult == ReqSeqNbrValidationResult::BAD_SEQUENCE_NUMBER)\n\t\t\t{\n\t\t\t\tauto* reconfigurationResponseParameter =\n\t\t\t\t  reConfigChunk->BuildParameterInPlace<ReconfigurationResponseParameter>();\n\n\t\t\t\treconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\t\treconfigurationResponseParameter->SetResult(\n\t\t\t\t  ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER);\n\n\t\t\t\treconfigurationResponseParameter->Consolidate();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If this is a retransmission of a request that has already been finalized\n\t\t\t// (i.e., not \"In Progress\"), just send the previous final response.\n\t\t\tif (\n\t\t\t  validationResult == ReqSeqNbrValidationResult::RETRANSMISSION &&\n\t\t\t  this->lastProcessedReqResult != ReconfigurationResponseParameter::Result::IN_PROGRESS)\n\t\t\t{\n\t\t\t\tauto* reconfigurationResponseParameter =\n\t\t\t\t  reConfigChunk->BuildParameterInPlace<ReconfigurationResponseParameter>();\n\n\t\t\t\treconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\t\treconfigurationResponseParameter->SetResult(this->lastProcessedReqResult);\n\n\t\t\t\treconfigurationResponseParameter->Consolidate();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// At this point, the request is either brand new, a buggy client sending\n\t\t\t// a new SN after \"In Progress\", or a compliant client retransmitting an\n\t\t\t// \"In Progress\" request. In all cases, re-evaluate the state.\n\t\t\tthis->lastProcessedReqSeqNbr = requestSn;\n\n\t\t\t// TODO: SCTP: Remove (it's just to avoid \"private field 'reassemblyQueue'\n\t\t\t// is not used\" wargning).\n\t\t\t(void)this->reassemblyQueue;\n\n\t\t\tif (\n\t\t\t  this->dataTracker->IsLaterThanCumulativeAckedTsn(\n\t\t\t    receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn()))\n\t\t\t{\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2\n\t\t\t\t//\n\t\t\t\t// E2) \"If the Sender's Last Assigned TSN is greater than the cumulative\n\t\t\t\t// acknowledgment point, then the endpoint MUST enter 'deferred reset\n\t\t\t\t// processing'.\"\n\t\t\t\tthis->reassemblyQueue->EnterDeferredReset(\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn(),\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetStreamIds());\n\n\t\t\t\t// \"If the endpoint enters 'deferred reset processing', it MUST put a\n\t\t\t\t// Re-configuration Response Parameter into a RE-CONFIG chunk indicating\n\t\t\t\t// 'In progress' and MUST send the RE-CONFIG chunk.\n\t\t\t\tthis->lastProcessedReqResult = ReconfigurationResponseParameter::Result::IN_PROGRESS;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"reset outgoing in progress, sender last assigned tsn %\" PRIu32 \" not yet reached\",\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2\n\t\t\t\t//\n\t\t\t\t// E3) If no stream numbers are listed in the parameter, then all incoming\n\t\t\t\t// streams MUST be reset to 0 as the next expected SSN. If specific stream\n\t\t\t\t// numbers are listed, then only these specific streams MUST be reset to\n\t\t\t\t// 0, and all other non-listed SSNs remain unchanged. E4: Any queued TSNs\n\t\t\t\t// (queued at step E2) MUST now be released and processed normally.\n\t\t\t\tthis->reassemblyQueue->ResetStreamsAndLeaveDeferredReset(\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetStreamIds());\n\n\t\t\t\tthis->associationListener.OnAssociationInboundStreamsReset(\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetStreamIds());\n\n\t\t\t\tthis->lastProcessedReqResult = ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED;\n\n\t\t\t\tMS_DEBUG_DEV(\"reset outgoing performed\");\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"reset outgoing performed, sender last assigned tsn %\" PRIu32 \" reached\",\n\t\t\t\t  receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn());\n\t\t\t}\n\n\t\t\tauto* reconfigurationResponseParameter =\n\t\t\t  reConfigChunk->BuildParameterInPlace<ReconfigurationResponseParameter>();\n\n\t\t\treconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t  receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\treconfigurationResponseParameter->SetResult(this->lastProcessedReqResult);\n\n\t\t\treconfigurationResponseParameter->Consolidate();\n\t\t}\n\n\t\tvoid StreamResetHandler::HandleReceivedIncomingSsnResetRequestParameter(\n\t\t  const IncomingSsnResetRequestParameter* receivedIncomingSsnResetRequestParameter,\n\t\t  ReConfigChunk* reConfigChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst UnwrappedReConfigRequestSn requestSn = this->incomingReConfigRequestSnUnwrapper.Unwrap(\n\t\t\t  receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\tconst ReqSeqNbrValidationResult validationResult = ValidateReqSeqNbr(requestSn);\n\n\t\t\tif (validationResult == ReqSeqNbrValidationResult::VALID || validationResult == ReqSeqNbrValidationResult::RETRANSMISSION)\n\t\t\t{\n\t\t\t\tauto* reconfigurationResponseParameter =\n\t\t\t\t  reConfigChunk->BuildParameterInPlace<ReconfigurationResponseParameter>();\n\n\t\t\t\treconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t\t  receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\t\treconfigurationResponseParameter->SetResult(\n\t\t\t\t  ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO);\n\n\t\t\t\treconfigurationResponseParameter->Consolidate();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tauto* reconfigurationResponseParameter =\n\t\t\t\t  reConfigChunk->BuildParameterInPlace<ReconfigurationResponseParameter>();\n\n\t\t\t\treconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber(\n\t\t\t\t  receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber());\n\t\t\t\treconfigurationResponseParameter->SetResult(\n\t\t\t\t  ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER);\n\n\t\t\t\treconfigurationResponseParameter->Consolidate();\n\t\t\t}\n\t\t}\n\n\t\tvoid StreamResetHandler::HandleReceivedReconfigurationResponseParameter(\n\t\t  const ReconfigurationResponseParameter* receivedReconfigurationResponseParameter)\n\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (\n\t\t\t  this->currentRequest.has_value() && this->currentRequest->HasBeenSent() &&\n\t\t\t  receivedReconfigurationResponseParameter->GetReconfigurationResponseSequenceNumber() ==\n\t\t\t    this->currentRequest->GetReqSeqNbr())\n\t\t\t{\n\t\t\t\tthis->reConfigTimer->Stop();\n\n\t\t\t\tswitch (receivedReconfigurationResponseParameter->GetResult())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO:\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"reset stream success [reqSeqNbr:%\" PRIu32 \"]\", this->currentRequest->GetReqSeqNbr());\n\n\t\t\t\t\t\tthis->associationListener.OnAssociationStreamsResetPerformed(\n\t\t\t\t\t\t  this->currentRequest->GetStreamIds());\n\n\t\t\t\t\t\tthis->currentRequest = std::nullopt;\n\n\t\t\t\t\t\tthis->retransmissionQueue->CommitResetStreams();\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"reset stream still pending [reqSeqNbr:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  this->currentRequest->GetReqSeqNbr());\n\n\t\t\t\t\t\t// Force this request to be sent again, but with the same `reqSeqNbr`.\n\t\t\t\t\t\tthis->currentRequest->SetDeferred(true);\n\n\t\t\t\t\t\tthis->reConfigTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs());\n\t\t\t\t\t\tthis->reConfigTimer->Start();\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS:\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::DENIED:\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_WRONG_SSN:\n\t\t\t\t\tcase RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  sctp,\n\t\t\t\t\t\t  \"reset stream error [reqSeqNbr:%\" PRIu32 \", result:%s]\",\n\t\t\t\t\t\t  this->currentRequest->GetReqSeqNbr(),\n\t\t\t\t\t\t  ReconfigurationResponseParameter::ResultToString(\n\t\t\t\t\t\t    receivedReconfigurationResponseParameter->GetResult())\n\t\t\t\t\t\t    .c_str());\n\n\t\t\t\t\t\tthis->associationListener.OnAssociationStreamsResetFailed(\n\t\t\t\t\t\t  this->currentRequest->GetStreamIds(),\n\t\t\t\t\t\t  ReconfigurationResponseParameter::ResultToString(\n\t\t\t\t\t\t    receivedReconfigurationResponseParameter->GetResult()));\n\n\t\t\t\t\t\tthis->currentRequest = std::nullopt;\n\n\t\t\t\t\t\tthis->retransmissionQueue->RollbackResetStreams();\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->currentRequest && this->currentRequest->HasBeenSent())\n\t\t\t{\n\t\t\t\t// The request was deferred (received \"In Progress\"). This is not a\n\t\t\t\t// timeout, but just time to retry.\n\t\t\t\tif (this->currentRequest->IsDeferred())\n\t\t\t\t{\n\t\t\t\t\tthis->currentRequest->SetDeferred(false);\n\t\t\t\t}\n\t\t\t\t// There is an outstanding request, which timed out while waiting for a\n\t\t\t\t// response.\n\t\t\t\telse if (!this->tcbContext->IncrementTxErrorCounter(\"RECONFIG timeout\"))\n\t\t\t\t{\n\t\t\t\t\t// Timed out. The connection will close after processing the timers.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// There is no outstanding request, but there is a prepared one. This means\n\t\t\t\t// that the receiver has previously responded \"in progress\", which resulted\n\t\t\t\t// in retrying the request (but with a new `reqSeqNbr`) after a while.\n\t\t\t}\n\n\t\t\tauto packet = this->tcbContext->CreatePacket();\n\n\t\t\tAddReConfigChunk(packet.get());\n\n\t\t\tthis->tcbContext->SendPacket(packet.get());\n\n\t\t\tbaseTimeoutMs = this->tcbContext->GetCurrentRtoMs();\n\t\t}\n\n\t\tvoid StreamResetHandler::OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto maxRestarts = backoffTimer->GetMaxRestarts();\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"%s timer has expired [expìrations:%zu/%s]\",\n\t\t\t  backoffTimer->GetLabel().c_str(),\n\t\t\t  backoffTimer->GetExpirationCount(),\n\t\t\t  maxRestarts ? std::to_string(maxRestarts.value()).c_str() : \"Infinite\");\n\n\t\t\tif (backoffTimer == this->reConfigTimer.get())\n\t\t\t{\n\t\t\t\tOnReConfigTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/association/TransmissionControlBlock.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::TransmissionControlBlock\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/association/TransmissionControlBlock.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieEchoChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Static. */\n\n\t\talignas(4) static thread_local uint8_t PacketFactoryBuffer[65536];\n\n\t\t/* Instance methods. */\n\n\t\tTransmissionControlBlock::TransmissionControlBlock(\n\t\t  AssociationListenerInterface& associationListener,\n\t\t  const SctpOptions& sctpOptions,\n\t\t  SharedInterface* shared,\n\t\t  SendQueueInterface& sendQueue,\n\t\t  PacketSender& packetSender,\n\t\t  uint32_t localVerificationTag,\n\t\t  uint32_t remoteVerificationTag,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  uint64_t tieTag,\n\t\t  const NegotiatedCapabilities& negotiatedCapabilities,\n\t\t  size_t maxPacketLength,\n\t\t  std::function<bool()> isAssociationEstablished)\n\t\t  : associationListener(associationListener),\n\t\t    sctpOptions(sctpOptions),\n\t\t    shared(shared),\n\t\t    packetSender(packetSender),\n\t\t    localVerificationTag(localVerificationTag),\n\t\t    remoteVerificationTag(remoteVerificationTag),\n\t\t    localInitialTsn(localInitialTsn),\n\t\t    remoteInitialTsn(remoteInitialTsn),\n\t\t    remoteAdvertisedReceiverWindowCredit(remoteAdvertisedReceiverWindowCredit),\n\t\t    tieTag(tieTag),\n\t\t    negotiatedCapabilities(negotiatedCapabilities),\n\t\t    maxPacketLength(maxPacketLength),\n\t\t    isAssociationEstablished(std::move(isAssociationEstablished)),\n\t\t    t3RtxTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-t3-rtx\",\n\t\t        .baseTimeoutMs       = sctpOptions.initialRtoMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t        .maxRestarts         = std::nullopt })),\n\t\t    delayedAckTimer(this->shared->CreateBackoffTimer(\n\t\t      BackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t        .listener            = this,\n\t\t        .label               = \"sctp-delayed-ack\",\n\t\t        .baseTimeoutMs       = sctpOptions.delayedAckMaxTimeoutMs,\n\t\t        .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t        .maxBackoffTimeoutMs = std::nullopt,\n\t\t        .maxRestarts         = 0 })),\n\t\t    rto(sctpOptions),\n\t\t    txErrorCounter(sctpOptions),\n\t\t    dataTracker(this->delayedAckTimer.get(), remoteInitialTsn),\n\t\t    reassemblyQueue(\n\t\t      sctpOptions.maxReceiverWindowBufferSize, negotiatedCapabilities.messageInterleaving),\n\t\t    retransmissionQueue(\n\t\t      this,\n\t\t      this->associationListener,\n\t\t      localInitialTsn,\n\t\t      remoteAdvertisedReceiverWindowCredit,\n\t\t      sendQueue,\n\t\t      this->t3RtxTimer.get(),\n\t\t      sctpOptions,\n\t\t      negotiatedCapabilities.partialReliability,\n\t\t      negotiatedCapabilities.messageInterleaving),\n\t\t    streamResetHandler(\n\t\t      this->associationListener,\n\t\t      this->shared,\n\t\t      this,\n\t\t      std::addressof(this->dataTracker),\n\t\t      std::addressof(this->reassemblyQueue),\n\t\t      std::addressof(this->retransmissionQueue)),\n\t\t    heartbeatHandler(this->associationListener, sctpOptions, this->shared, this)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsendQueue.EnableMessageInterleaving(this->negotiatedCapabilities.messageInterleaving);\n\t\t}\n\n\t\tTransmissionControlBlock::~TransmissionControlBlock()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid TransmissionControlBlock::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::TransmissionControlBlock>\");\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"  local verification tag: %\" PRIu32, this->localVerificationTag);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  remote verification tag: %\" PRIu32, this->remoteVerificationTag);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  local initial tsn: %\" PRIu32, this->localInitialTsn);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  remote initial tsn: %\" PRIu32, this->remoteInitialTsn);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  remote advertised receiver window credit: %\" PRIu32,\n\t\t\t  this->remoteAdvertisedReceiverWindowCredit);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tie-tag: %\" PRIu64, this->tieTag);\n\n\t\t\tthis->negotiatedCapabilities.Dump(indentation + 1);\n\n\t\t\tthis->rto.Dump(indentation + 1);\n\n\t\t\tthis->txErrorCounter.Dump(indentation + 1);\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::TransmissionControlBlock>\");\n\t\t}\n\n\t\tvoid TransmissionControlBlock::ObserveRttMs(uint64_t rttMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tconst auto prevRtoMs = this->rto.GetRtoMs();\n#endif\n\n\t\t\tthis->rto.ObserveRttMs(rttMs);\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"new rtt:%\" PRIu64 \", previous rto:%\" PRIu64 \", new rto:%\" PRIu64 \", srtt:%\" PRIu64,\n\t\t\t  rttMs,\n\t\t\t  prevRtoMs,\n\t\t\t  this->rto.GetRtoMs(),\n\t\t\t  this->rto.GetSrttMs());\n\n\t\t\tthis->t3RtxTimer->SetBaseTimeoutMs(this->rto.GetRtoMs());\n\n\t\t\tconst uint64_t delayedAckTimeoutMs = std::min(\n\t\t\t  static_cast<uint64_t>(this->rto.GetRtoMs() * 0.5), this->sctpOptions.delayedAckMaxTimeoutMs);\n\n\t\t\tthis->delayedAckTimer->SetBaseTimeoutMs(delayedAckTimeoutMs);\n\t\t}\n\n\t\tstd::unique_ptr<Packet> TransmissionControlBlock::CreatePacket() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn CreatePacketWithVerificationTag(this->remoteVerificationTag);\n\t\t}\n\n\t\tstd::unique_ptr<Packet> TransmissionControlBlock::CreatePacketWithVerificationTag(\n\t\t  uint32_t verificationTag) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto packet =\n\t\t\t  std::unique_ptr<Packet>{ Packet::Factory(PacketFactoryBuffer, this->maxPacketLength) };\n\n\t\t\tpacket->SetSourcePort(this->sctpOptions.sourcePort);\n\t\t\tpacket->SetDestinationPort(this->sctpOptions.destinationPort);\n\t\t\tpacket->SetVerificationTag(verificationTag);\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tbool TransmissionControlBlock::SendPacket(Packet* packet)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->packetSender.SendPacket(\n\t\t\t  packet,\n\t\t\t  /*writeChecksum*/ !this->negotiatedCapabilities.zeroChecksum);\n\t\t}\n\n\t\tvoid TransmissionControlBlock::SetRemoteStateCookie(std::vector<uint8_t> remoteStateCookie)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->remoteStateCookie = std::move(remoteStateCookie);\n\t\t}\n\n\t\tvoid TransmissionControlBlock::ClearRemoteStateCookie()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->remoteStateCookie.reset();\n\t\t}\n\n\t\tvoid TransmissionControlBlock::MaySendSackChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->dataTracker.ShouldSendAck(/*alsoIfDelayed*/ false))\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst auto packet = CreatePacket();\n\n\t\t\tthis->dataTracker.AddSackSelectiveAck(packet.get(), this->reassemblyQueue.GetRemainingBytes());\n\n\t\t\tSendPacket(packet.get());\n\t\t}\n\n\t\tvoid TransmissionControlBlock::MayAddForwardTsnChunk(Packet* packet, uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (nowMs >= this->limitForwardTsnUntilMs && this->retransmissionQueue.ShouldSendForwardTsn(nowMs))\n\t\t\t{\n\t\t\t\tif (this->negotiatedCapabilities.messageInterleaving)\n\t\t\t\t{\n\t\t\t\t\tthis->retransmissionQueue.AddIForwardTsn(packet);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->retransmissionQueue.AddForwardTsn(packet);\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc3758\n\t\t\t\t//\n\t\t\t\t// \"IMPLEMENTATION NOTE: An implementation may wish to limit the number\n\t\t\t\t// of duplicate FORWARD TSN chunks it sends by ... waiting a full RTT\n\t\t\t\t// before sending a duplicate FORWARD TSN.\"\n\t\t\t\t// \"Any delay applied to the sending of FORWARD TSN chunk SHOULD NOT\n\t\t\t\t// exceed 200ms and MUST NOT exceed 500ms\".\n\t\t\t\tthis->limitForwardTsnUntilMs = nowMs + std::min(uint64_t{ 200 }, this->rto.GetSrttMs());\n\t\t\t}\n\t\t}\n\n\t\tvoid TransmissionControlBlock::MaySendFastRetransmit()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->retransmissionQueue.HasDataToBeFastRetransmitted())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"Determine how many of the earliest (i.e., lowest TSN) DATA chunks\n\t\t\t// marked for retransmission will fit into a single packet, subject to\n\t\t\t// constraint of the path MTU of the destination transport address to\n\t\t\t// which the packet is being sent. Call this value K. Retransmit those\n\t\t\t// K DATA chunks in a single packet.  When a Fast Retransmit is being\n\t\t\t// performed, the sender SHOULD ignore the value of cwnd and SHOULD NOT\n\t\t\t// delay retransmission for this single packet.\"\n\n\t\t\tconst auto packet = CreatePacket();\n\n\t\t\tauto result =\n\t\t\t  this->retransmissionQueue.GetChunksForFastRetransmit(packet->GetAvailableLength());\n\n\t\t\tfor (auto& [tsn, data] : result)\n\t\t\t{\n\t\t\t\tif (this->negotiatedCapabilities.messageInterleaving)\n\t\t\t\t{\n\t\t\t\t\tauto* iDataChunk = packet->BuildChunkInPlace<IDataChunk>();\n\n\t\t\t\t\tiDataChunk->SetTsn(tsn);\n\t\t\t\t\tiDataChunk->SetUserData(std::move(data));\n\t\t\t\t\tiDataChunk->Consolidate();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tauto* dataChunk = packet->BuildChunkInPlace<DataChunk>();\n\n\t\t\t\t\tdataChunk->SetTsn(tsn);\n\t\t\t\t\tdataChunk->SetUserData(std::move(data));\n\t\t\t\t\tdataChunk->Consolidate();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tSendPacket(packet.get());\n\t\t}\n\n\t\tvoid TransmissionControlBlock::SendBufferedPackets(uint64_t nowMs, bool addCookieAckChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (size_t packetIdx{ 0 }; packetIdx < this->sctpOptions.maxBurst; ++packetIdx)\n\t\t\t{\n\t\t\t\tconst auto packet = CreatePacket();\n\n\t\t\t\t// Only add control Chunks to the first Packet that is sent, if sending\n\t\t\t\t// multiple Packets in one go (as allowed by the congestion window).\n\t\t\t\tif (packetIdx == 0)\n\t\t\t\t{\n\t\t\t\t\tif (addCookieAckChunk)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\"adding COOKIE_ACK Chunk to the Packet\");\n\n\t\t\t\t\t\tconst auto* cookieAckChunk = packet->BuildChunkInPlace<CookieAckChunk>();\n\n\t\t\t\t\t\tcookieAckChunk->Consolidate();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this->remoteStateCookie.has_value())\n\t\t\t\t\t{\n\t\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.1\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// \"The COOKIE ECHO chunk can be bundled with any pending outbound\n\t\t\t\t\t\t// DATA chunks, but it MUST be the first chunk in the packet...\"\n\t\t\t\t\t\tif (packet->GetChunksCount() > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t\t\t  \"Packet must have no Chunks [addCookieAckChunk:%s]\",\n\t\t\t\t\t\t\t  addCookieAckChunk ? \"true\" : \"no\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* cookieEchoChunk = packet->BuildChunkInPlace<CookieEchoChunk>();\n\n\t\t\t\t\t\tcookieEchoChunk->SetCookie(\n\t\t\t\t\t\t  remoteStateCookie->data(), static_cast<uint16_t>(remoteStateCookie->size()));\n\t\t\t\t\t\tcookieEchoChunk->Consolidate();\n\t\t\t\t\t}\n\n\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6\n\t\t\t\t\t//\n\t\t\t\t\t// \"Before an endpoint transmits a DATA chunk, if any received DATA\n\t\t\t\t\t// chunks have not been acknowledged (e.g., due to delayed ack), the\n\t\t\t\t\t// sender should create a SACK and bundle it with the outbound DATA\n\t\t\t\t\t// chunk, as long as the size of the final SCTP packet does not exceed\n\t\t\t\t\t// the current MTU.\"\n\t\t\t\t\tif (this->dataTracker.ShouldSendAck(/*alsoIfDelayed*/ true))\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->dataTracker.AddSackSelectiveAck(\n\t\t\t\t\t\t  packet.get(), this->reassemblyQueue.GetRemainingBytes());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\t\t\tMayAddForwardTsnChunk(packet.get(), nowMs);\n\n\t\t\t\t\tif (this->streamResetHandler.ShouldSendStreamResetRequest())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->streamResetHandler.AddStreamResetRequest(packet.get());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tauto chunksToSend =\n\t\t\t\t  this->retransmissionQueue.GetChunksToSend(nowMs, packet->GetAvailableLength());\n\n\t\t\t\tif (!chunksToSend.empty())\n\t\t\t\t{\n\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.3\n\t\t\t\t\t//\n\t\t\t\t\t// Sending DATA means that the path is not idle, restart heartbeat\n\t\t\t\t\t// timer.\n\t\t\t\t\tthis->heartbeatHandler.RestartTimer();\n\t\t\t\t}\n\n\t\t\t\tconst bool immediateAck =\n\t\t\t\t  GetCwnd() < (this->sctpOptions.immediateSackUnderCwndMtus * this->sctpOptions.mtu);\n\n\t\t\t\tfor (auto& [tsn, data] : chunksToSend)\n\t\t\t\t{\n\t\t\t\t\tif (this->negotiatedCapabilities.messageInterleaving)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* iDataChunk = packet->BuildChunkInPlace<IDataChunk>();\n\n\t\t\t\t\t\tiDataChunk->SetTsn(tsn);\n\t\t\t\t\t\tiDataChunk->SetI(immediateAck);\n\t\t\t\t\t\tiDataChunk->SetUserData(std::move(data));\n\t\t\t\t\t\tiDataChunk->Consolidate();\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* dataChunk = packet->BuildChunkInPlace<DataChunk>();\n\n\t\t\t\t\t\tdataChunk->SetTsn(tsn);\n\t\t\t\t\t\tdataChunk->SetI(immediateAck);\n\t\t\t\t\t\tdataChunk->SetUserData(std::move(data));\n\t\t\t\t\t\tdataChunk->Consolidate();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9653#section-5.2\n\t\t\t\t//\n\t\t\t\t// \"When an end point sends a packet containing a COOKIE ECHO chunk, it\n\t\t\t\t// MUST include a correct CRC32c checksum in the packet containing the\n\t\t\t\t// COOKIE ECHO chunk.\"\n\t\t\t\tif (!this->packetSender.SendPacket(\n\t\t\t\t      packet.get(),\n\t\t\t\t      /*writeChecksum*/ !negotiatedCapabilities.zeroChecksum ||\n\t\t\t\t        this->remoteStateCookie.has_value()))\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-5.1\n\t\t\t\t//\n\t\t\t\t// \"until the COOKIE ACK is returned the sender MUST NOT send any\n\t\t\t\t// other packets to the peer.\"\n\t\t\t\tif (this->remoteStateCookie.has_value())\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid TransmissionControlBlock::OnT3RtxTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// In the COOKIE_ECHO state, let the T1-COOKIE timer trigger\n\t\t\t// retransmissions, to avoid having two timers doing that.\n\t\t\tif (this->remoteStateCookie.has_value())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"not retransmitting as T1-cookie is active\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (IncrementTxErrorCounter(\"t3-rtx expired\"))\n\t\t\t\t{\n\t\t\t\t\tthis->retransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\t\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\t\t\tSendBufferedPackets(nowMs);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid TransmissionControlBlock::OnDelayedAckTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->dataTracker.HandleDelayedAckTimerExpiry();\n\n\t\t\tMaySendSackChunk();\n\t\t}\n\n\t\tvoid TransmissionControlBlock::OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto maxRestarts = backoffTimer->GetMaxRestarts();\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"%s timer has expired [expìrations:%zu/%s]\",\n\t\t\t  backoffTimer->GetLabel().c_str(),\n\t\t\t  backoffTimer->GetExpirationCount(),\n\t\t\t  maxRestarts ? std::to_string(maxRestarts.value()).c_str() : \"Infinite\");\n\n\t\t\tif (backoffTimer == this->t3RtxTimer.get())\n\t\t\t{\n\t\t\t\tOnT3RtxTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t\telse if (backoffTimer == this->delayedAckTimer.get())\n\t\t\t{\n\t\t\t\tOnDelayedAckTimer(baseTimeoutMs, stop);\n\t\t\t}\n\t\t}\n\n\t\tvoid TransmissionControlBlock::OnRetransmissionQueueNewRttMs(uint64_t newRttMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tObserveRttMs(newRttMs);\n\t\t}\n\n\t\tvoid TransmissionControlBlock::OnRetransmissionQueueClearRetransmissionCounter()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->txErrorCounter.Clear();\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/Chunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Chunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp\"\n#include \"RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/StateCookieParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnknownParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst std::unordered_map<Chunk::ChunkType, std::string> Chunk::ChunkType2String =\n\t\t{\n\t\t\t{ Chunk::ChunkType::DATA,              \"DATA\"              },\n\t\t\t{ Chunk::ChunkType::INIT,              \"INIT\"              },\n\t\t\t{ Chunk::ChunkType::INIT_ACK,          \"INIT_ACK\"          },\n\t\t\t{ Chunk::ChunkType::SACK,              \"SACK\"              },\n\t\t\t{ Chunk::ChunkType::HEARTBEAT_REQUEST, \"HEARTBEAT_REQUEST\" },\n\t\t\t{ Chunk::ChunkType::HEARTBEAT_ACK,     \"HEARTBEAT_ACK\"     },\n\t\t\t{ Chunk::ChunkType::ABORT,             \"ABORT\"             },\n\t\t\t{ Chunk::ChunkType::SHUTDOWN,          \"SHUTDOWN\"          },\n\t\t\t{ Chunk::ChunkType::SHUTDOWN_ACK,      \"SHUTDOWN_ACK\"      },\n\t\t\t{ Chunk::ChunkType::OPERATION_ERROR,   \"OPERATION_ERROR\"   },\n\t\t\t{ Chunk::ChunkType::COOKIE_ECHO,       \"COOKIE_ECHO\"       },\n\t\t\t{ Chunk::ChunkType::COOKIE_ACK,        \"COOKIE_ACK\"        },\n\t\t\t{ Chunk::ChunkType::ECNE,              \"ECNE\"              },\n\t\t\t{ Chunk::ChunkType::CWR,               \"CWR\"               },\n\t\t\t{ Chunk::ChunkType::SHUTDOWN_COMPLETE, \"SHUTDOWN_COMPLETE\" },\n\t\t\t{ Chunk::ChunkType::FORWARD_TSN,       \"FORWARD_TSN\"       },\n\t\t\t{ Chunk::ChunkType::RE_CONFIG,         \"RE_CONFIG\"         },\n\t\t\t{ Chunk::ChunkType::I_DATA,            \"I_DATA\"            },\n\t\t\t{ Chunk::ChunkType::I_FORWARD_TSN,     \"I_FORWARD_TSN\"     },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tbool Chunk::IsChunk(\n\t\t  const uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  Chunk::ChunkType& chunkType,\n\t\t  uint16_t& chunkLength,\n\t\t  uint8_t& padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!TLV::IsTLV(buffer, bufferLength, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tchunkType = static_cast<Chunk::ChunkType>(buffer[0]);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tconst std::string& Chunk::ChunkTypeToString(ChunkType chunkType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = Chunk::ChunkType2String.find(chunkType);\n\n\t\t\tif (it == Chunk::ChunkType2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tChunk::Chunk(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tChunk::~Chunk()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: Here we cannot check CanHaveParameters() or CanHaveErrorCauses()\n\t\t\t// because this is the destructor of Chunk so the subclass has been\n\t\t\t// already destroyed (its destructor runs first).\n\n\t\t\tfor (const auto* parameter : this->parameters)\n\t\t\t{\n\t\t\t\tdelete parameter;\n\t\t\t}\n\n\t\t\tfor (const auto* errorCause : this->errorCauses)\n\t\t\t{\n\t\t\t\tdelete errorCause;\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::Serialize(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* previousBuffer = GetBuffer();\n\n\t\t\t// Invoke the parent method to copy the whole buffer.\n\t\t\tSerializable::Serialize(buffer, bufferLength);\n\n\t\t\tif (CanHaveParameters())\n\t\t\t{\n\t\t\t\tfor (auto* parameter : this->parameters)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = parameter->GetBuffer() - previousBuffer;\n\n\t\t\t\t\tparameter->SoftSerialize(buffer + offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (CanHaveErrorCauses())\n\t\t\t{\n\t\t\t\tfor (auto* errorCause : this->errorCauses)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = errorCause->GetBuffer() - previousBuffer;\n\n\t\t\t\t\terrorCause->SoftSerialize(buffer + offset);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::AddParameter(const Parameter* parameter)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertCanHaveParameters();\n\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\tconst size_t previousLength = GetLength();\n\n\t\t\t// This will update the total length and Length field of the Chunk.\n\t\t\t// NOTE: It may throw.\n\t\t\tAddItem(parameter);\n\n\t\t\t// Let's append the Parameter at the end of existing Parameters.\n\t\t\tauto* clonedParameter =\n\t\t\t  parameter->Clone(const_cast<uint8_t*>(GetBuffer()) + previousLength, parameter->GetLength());\n\n\t\t\t// Add the Parameter to the list.\n\t\t\tthis->parameters.push_back(clonedParameter);\n\t\t}\n\n\t\tvoid Chunk::AddErrorCause(const ErrorCause* errorCause)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertCanHaveErrorCauses();\n\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\tconst size_t previousLength = GetLength();\n\n\t\t\t// This will update the total length and Length field of the Chunk.\n\t\t\t// NOTE: It may throw.\n\t\t\tAddItem(errorCause);\n\n\t\t\t// Let's append the Error Cause at the end of existing Error Causes.\n\t\t\tauto* clonedErrorCause = errorCause->Clone(\n\t\t\t  const_cast<uint8_t*>(GetBuffer()) + previousLength, errorCause->GetLength());\n\n\t\t\tthis->errorCauses.push_back(clonedErrorCause);\n\t\t}\n\n\t\tvoid Chunk::DumpCommon(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  type: %\" PRIu8 \" (%s) (unknown: %s)\",\n\t\t\t  static_cast<uint8_t>(GetType()),\n\t\t\t  Chunk::ChunkTypeToString(GetType()).c_str(),\n\t\t\t  HasUnknownType() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  flags: \" MS_UINT8_TO_BINARY_PATTERN, MS_UINT8_TO_BINARY(GetFlags()));\n\t\t\tTLV::DumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  needs consolidation of parameters or error causes: %s\",\n\t\t\t  NeedsConsolidation() ? \"yes\" : \"no\");\n\t\t}\n\n\t\tvoid Chunk::DumpParameters(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (CanHaveParameters())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  parameters count: %zu\", GetParametersCount());\n\t\t\t\tfor (const auto* parameter : this->parameters)\n\t\t\t\t{\n\t\t\t\t\tparameter->Dump(indentation + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::DumpErrorCauses(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (CanHaveErrorCauses())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  error causes count: %zu\", GetErrorCausesCount());\n\t\t\t\tfor (const auto* errorCause : this->errorCauses)\n\t\t\t\t{\n\t\t\t\t\terrorCause->Dump(indentation + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::SoftSerialize(const uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* previousBuffer = GetBuffer();\n\n\t\t\tSetBuffer(const_cast<uint8_t*>(buffer));\n\n\t\t\tif (CanHaveParameters())\n\t\t\t{\n\t\t\t\tfor (auto* parameter : this->parameters)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = parameter->GetBuffer() - previousBuffer;\n\n\t\t\t\t\tparameter->SoftSerialize(buffer + offset);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (CanHaveErrorCauses())\n\t\t\t{\n\t\t\t\tfor (auto* errorCause : this->errorCauses)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = errorCause->GetBuffer() - previousBuffer;\n\n\t\t\t\t\terrorCause->SoftSerialize(buffer + offset);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::SoftCloneInto(Chunk* chunk) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Soft clone Parameters into the given Chunk.\n\t\t\tif (CanHaveParameters())\n\t\t\t{\n\t\t\t\tfor (auto* parameter : this->parameters)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = parameter->GetBuffer() - GetBuffer();\n\n\t\t\t\t\tauto* softClonedParameter = parameter->SoftClone(chunk->GetBuffer() + offset);\n\n\t\t\t\t\tchunk->parameters.push_back(softClonedParameter);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Soft clone Error Causes into the given Chunk.\n\t\t\tif (CanHaveErrorCauses())\n\t\t\t{\n\t\t\t\tfor (auto* errorCause : this->errorCauses)\n\t\t\t\t{\n\t\t\t\t\tconst size_t offset = errorCause->GetBuffer() - GetBuffer();\n\n\t\t\t\t\tauto* softClonedErrorCause = errorCause->SoftClone(chunk->GetBuffer() + offset);\n\n\t\t\t\t\tchunk->errorCauses.push_back(softClonedErrorCause);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Need to manually set Serializable length.\n\t\t\tchunk->SetLength(GetLength());\n\t\t}\n\n\t\tvoid Chunk::InitializeHeader(ChunkType chunkType, uint8_t flags, uint16_t lengthFieldValue)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetType(chunkType);\n\t\t\tSetFlags(flags);\n\t\t\tInitializeTLVHeader(lengthFieldValue);\n\t\t}\n\n\t\tbool Chunk::ParseParameters()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertCanHaveParameters();\n\n\t\t\t// Here we assume that the Chunk buffer has been validated and\n\t\t\t// GetLength() returns the fixed minimum length of the specific Chunk\n\t\t\t// subclass, so GetBuffer() + GetLength() points to the beginning of the\n\t\t\t// potential Parameters. And of course we assume that a Chunk cannot have\n\t\t\t// both Parameters and Error Causes.\n\t\t\tauto* ptr = const_cast<uint8_t*>(GetBuffer()) + GetLength();\n\n\t\t\t// Here we assume that the Chunk has been validated so Length field is\n\t\t\t// reliable. We want to be ready for Length field to include or not the\n\t\t\t// possible padding of the last Parameter (as per RFC recommendation). In\n\t\t\t// fact, we rely on parameter->GetLength() while parsing the buffer so we\n\t\t\t// want to provide each Parameter::StrictParse() call with a 4-bytes\n\t\t\t// padded buffer length.\n\t\t\tconst auto* end = GetBuffer() + Utils::Byte::PadTo4Bytes(GetLengthField());\n\n\t\t\twhile (ptr < end)\n\t\t\t{\n\t\t\t\t// The remaining length in the given length is the potential buffer\n\t\t\t\t// length of the Parameter.\n\t\t\t\tconst size_t parameterMaxBufferLength = end - ptr;\n\n\t\t\t\t// Here we must anticipate the type of each Parameter to use its\n\t\t\t\t// appropriate parser.\n\t\t\t\tParameter::ParameterType parameterType;\n\t\t\t\tuint16_t parameterLength;\n\t\t\t\tuint8_t padding;\n\n\t\t\t\tif (!Parameter::IsParameter(\n\t\t\t\t      ptr, parameterMaxBufferLength, parameterType, parameterLength, padding))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"not an SCTP Parameter\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tParameter* parameter{ nullptr }; // NOLINT(misc-const-correctness)\n\n\t\t\t\tswitch (parameterType)\n\t\t\t\t{\n\t\t\t\t\tcase Parameter::ParameterType::HEARTBEAT_INFO:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = HeartbeatInfoParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::IPV4_ADDRESS:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = IPv4AddressParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::IPV6_ADDRESS:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = IPv6AddressParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::STATE_COOKIE:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = StateCookieParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::UNRECOGNIZED_PARAMETER:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = UnrecognizedParameterParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::COOKIE_PRESERVATIVE:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = CookiePreservativeParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = SupportedAddressTypesParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::FORWARD_TSN_SUPPORTED:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = ForwardTsnSupportedParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::SUPPORTED_EXTENSIONS:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = SupportedExtensionsParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = ZeroChecksumAcceptableParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = OutgoingSsnResetRequestParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = IncomingSsnResetRequestParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::SSN_TSN_RESET_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = SsnTsnResetRequestParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::RECONFIGURATION_RESPONSE:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = ReconfigurationResponseParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = AddOutgoingStreamsRequestParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = AddIncomingStreamsRequestParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tparameter = UnknownParameter::ParseStrict(\n\t\t\t\t\t\t  ptr, parameterLength + padding, parameterLength, padding);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!parameter)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tthis->parameters.push_back(parameter);\n\n\t\t\t\tptr += parameter->GetLength();\n\t\t\t}\n\n\t\t\tif (ptr != end)\n\t\t\t{\n\t\t\t\tauto expectedLength = end - GetBuffer();\n\t\t\t\tauto computedLength = ptr - GetBuffer();\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"computed length (%zu bytes) doesn't match the expected length (%zu bytes)\",\n\t\t\t\t  computedLength,\n\t\t\t\t  expectedLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool Chunk::ParseErrorCauses()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertCanHaveErrorCauses();\n\n\t\t\t// Here we assume that the Chunk buffer has been validated and GetLength()\n\t\t\t// returns the fixed minimum length of the specific Chunk subclass, so\n\t\t\t// GetBuffer() + GetLength() points to the beginning of the potential\n\t\t\t// Error Causes. And of course we assume that a Chunk cannot have both\n\t\t\t// Parameters and Error Causes.\n\t\t\tauto* ptr = const_cast<uint8_t*>(GetBuffer()) + GetLength();\n\n\t\t\t// Here we assume that the Chunk has been validated so Length field is\n\t\t\t// reliable. We want to be ready for Length field to include or not the\n\t\t\t// possible padding of the last Error Cause (as per RFCrecommendation).\n\t\t\t// In fact, we rely on errorCause->GetLength() while parsing the buffer\n\t\t\t// so we want to provide each ErrorCause::StrictParse() call with a\n\t\t\t// 4-bytes padded buffer length.\n\t\t\tconst auto* end = GetBuffer() + Utils::Byte::PadTo4Bytes(GetLengthField());\n\n\t\t\twhile (ptr < end)\n\t\t\t{\n\t\t\t\t// The remaining length in the given length is the potential buffer\n\t\t\t\t// length of the Error Cause.\n\t\t\t\tconst size_t errorCauseMaxBufferLength = end - ptr;\n\n\t\t\t\t// Here we must anticipate the type of each Error Cause to use its\n\t\t\t\t// appropriate parser.\n\t\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\t\tuint16_t causeLength;\n\t\t\t\tuint8_t padding;\n\n\t\t\t\tif (!ErrorCause::IsErrorCause(ptr, errorCauseMaxBufferLength, causeCode, causeLength, padding))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"not an SCTP Error Cause\");\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tErrorCause* errorCause{ nullptr }; // NOLINT(misc-const-correctness)\n\n\t\t\t\tswitch (causeCode)\n\t\t\t\t{\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = InvalidStreamIdentifierErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = MissingMandatoryParameterErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::STALE_COOKIE:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause =\n\t\t\t\t\t\t  StaleCookieErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause =\n\t\t\t\t\t\t  OutOfResourceErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = UnresolvableAddressErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = UnrecognizedChunkTypeErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = InvalidMandatoryParameterErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = UnrecognizedParametersErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::NO_USER_DATA:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause =\n\t\t\t\t\t\t  NoUserDataErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = CookieReceivedWhileShuttingDownErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = UserInitiatedAbortErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause = ProtocolViolationErrorCause::ParseStrict(\n\t\t\t\t\t\t  ptr, causeLength + padding, causeLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\terrorCause =\n\t\t\t\t\t\t  UnknownErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!errorCause)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tthis->errorCauses.push_back(errorCause);\n\n\t\t\t\tptr += errorCause->GetLength();\n\t\t\t}\n\n\t\t\tif (ptr != end)\n\t\t\t{\n\t\t\t\tauto expectedLength = end - GetBuffer();\n\t\t\t\tauto computedLength = ptr - GetBuffer();\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"computed length (%zu bytes) doesn't match the expected length (%zu bytes)\",\n\t\t\t\t  computedLength,\n\t\t\t\t  expectedLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid Chunk::HandleInPlaceParameter(Parameter* parameter)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->needsConsolidation = true;\n\n\t\t\t// When the application completes the Parameter it must call\n\t\t\t// `parameter->Consolidate()` and that will trigger this event.\n\t\t\tparameter->SetConsolidatedListener(\n\t\t\t  [this, parameter]()\n\t\t\t  {\n\t\t\t\t  try\n\t\t\t\t  {\n\t\t\t\t\t  // Fix buffer length assigned to the Parameter.\n\t\t\t\t\t  // NOTE: It may throw.\n\t\t\t\t\t  parameter->SetBufferLength(parameter->GetLength());\n\n\t\t\t\t\t  // This will update the total length and Length field of the Chunk.\n\t\t\t\t\t  // NOTE: It may throw.\n\t\t\t\t\t  AddItem(parameter);\n\n\t\t\t\t\t  // Add the Parameter to the list.\n\t\t\t\t\t  this->parameters.push_back(parameter);\n\n\t\t\t\t\t  this->needsConsolidation = false;\n\t\t\t\t  }\n\t\t\t\t  catch (const MediaSoupError& error)\n\t\t\t\t  {\n\t\t\t\t\t  this->needsConsolidation = false;\n\n\t\t\t\t\t  throw;\n\t\t\t\t  }\n\t\t\t  });\n\t\t}\n\n\t\tvoid Chunk::HandleInPlaceErrorCause(ErrorCause* errorCause)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->needsConsolidation = true;\n\n\t\t\t// When the application completes the Error Cause it must call\n\t\t\t// `errorCause->Consolidate()` and that will trigger this event.\n\t\t\terrorCause->SetConsolidatedListener(\n\t\t\t  [this, errorCause]()\n\t\t\t  {\n\t\t\t\t  try\n\t\t\t\t  {\n\t\t\t\t\t  // Fix buffer length assigned to the Error Cause.\n\t\t\t\t\t  errorCause->SetBufferLength(errorCause->GetLength());\n\n\t\t\t\t\t  // This will update the total length and Length field of the Chunk.\n\t\t\t\t\t  // NOTE: It may throw.\n\t\t\t\t\t  AddItem(errorCause);\n\n\t\t\t\t\t  // Add the Error Cause to the list.\n\t\t\t\t\t  this->errorCauses.push_back(errorCause);\n\n\t\t\t\t\t  this->needsConsolidation = false;\n\t\t\t\t  }\n\t\t\t\t  catch (const MediaSoupError& error)\n\t\t\t\t  {\n\t\t\t\t\t  this->needsConsolidation = false;\n\n\t\t\t\t\t  throw;\n\t\t\t\t  }\n\t\t\t  });\n\t\t}\n\n\t\tvoid Chunk::AssertCanHaveParameters() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!CanHaveParameters())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"this Chunk class cannot have Parameters\");\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::AssertCanHaveErrorCauses() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!CanHaveErrorCauses())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"this Chunk class cannot have Error Causes\");\n\t\t\t}\n\t\t}\n\n\t\tvoid Chunk::AssertDoesNotNeedConsolidation() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->needsConsolidation)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"Chunk needs consolidation of some ongoing Parameter or Error Cause\");\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/ErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst std::unordered_map<ErrorCause::ErrorCauseCode, std::string> ErrorCause::ErrorCauseCode2String =\n\t\t{\n\t\t\t{ ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,                    \"INVALID_STREAM_IDENTIFIER\"                    },\n\t\t\t{ ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,                  \"MISSING_MANDATORY_PARAMETER\"                  },\n\t\t\t{ ErrorCause::ErrorCauseCode::STALE_COOKIE,                                 \"STALE_COOKIE\"                                 },\n\t\t\t{ ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,                              \"OUT_OF_RESOURCE\"                              },\n\t\t\t{ ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,                         \"UNRESOLVABLE_ADDRESS\"                         },\n\t\t\t{ ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,                      \"UNRECOGNIZED_CHUNK_TYPE\"                      },\n\t\t\t{ ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,                  \"INVALID_MANDATORY_PARAMETER\"                  },\n\t\t\t{ ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,                      \"UNRECOGNIZED_PARAMETERS\"                      },\n\t\t\t{ ErrorCause::ErrorCauseCode::NO_USER_DATA,                                 \"NO_USER_DATA\"                                 },\n\t\t\t{ ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,          \"COOKIE_RECEIVED_WHILE_SHUTTING_DOWN\"          },\n\t\t\t{ ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, \"RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES\" },\n\t\t\t{ ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,                         \"USER_INITIATED_ABORT\"                         },\n\t\t\t{ ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,                           \"PROTOCOL_VIOLATION\"                           },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tbool ErrorCause::IsErrorCause(\n\t\t  const uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  ErrorCause::ErrorCauseCode& causeCode,\n\t\t  uint16_t& causeLength,\n\t\t  uint8_t& padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!TLV::IsTLV(buffer, bufferLength, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tcauseCode = static_cast<ErrorCause::ErrorCauseCode>(Utils::Byte::Get2Bytes(buffer, 0));\n\n\t\t\treturn true;\n\t\t}\n\n\t\tconst std::string& ErrorCause::ErrorCauseCodeToString(ErrorCauseCode causeCode)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = ErrorCause::ErrorCauseCode2String.find(causeCode);\n\n\t\t\tif (it == ErrorCause::ErrorCauseCode2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tErrorCause::ErrorCause(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tErrorCause::~ErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ErrorCause::SoftCloneInto(ErrorCause* errorCause) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Need to manually set Serializable length.\n\t\t\terrorCause->SetLength(GetLength());\n\t\t}\n\n\t\tvoid ErrorCause::DumpCommon(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  code: %\" PRIu16 \" (%s) (unknown: %s)\",\n\t\t\t  static_cast<uint16_t>(GetCode()),\n\t\t\t  ErrorCause::ErrorCauseCodeToString(GetCode()).c_str(),\n\t\t\t  HasUnknownCode() ? \"yes\" : \"no\");\n\t\t\tTLV::DumpCommon(indentation);\n\t\t}\n\n\t\tvoid ErrorCause::SoftSerialize(const uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBuffer(const_cast<uint8_t*>(buffer));\n\t\t}\n\n\t\tvoid ErrorCause::InitializeHeader(ErrorCauseCode causeCode, uint16_t lengthFieldValue)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetCode(causeCode);\n\t\t\tInitializeTLVHeader(lengthFieldValue);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/Packet.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Packet\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieEchoChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/OperationErrorChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ReConfigChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/UnknownChunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tbool Packet::IsSctp(const uint8_t* /*buffer*/, size_t bufferLength)\n\t\t{\n\t\t\treturn (\n\t\t\t  bufferLength >= Packet::CommonHeaderLength && Utils::Byte::IsPaddedTo4Bytes(bufferLength));\n\t\t}\n\n\t\tPacket* Packet::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!Packet::IsSctp(buffer, bufferLength))\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"not an SCTP Packet\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* packet = new Packet(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Pointer that initially points to the given data buffer and is later\n\t\t\t// incremented to point to other parts of the Packet.\n\t\t\tconst auto* ptr = buffer;\n\n\t\t\t// Move to chunks.\n\t\t\tptr = packet->GetChunksPointer();\n\n\t\t\twhile (ptr < buffer + bufferLength)\n\t\t\t{\n\t\t\t\t// The remaining length in the buffer is the potential buffer length\n\t\t\t\t// of the Chunk.\n\t\t\t\tconst size_t chunkMaxBufferLength = bufferLength - (ptr - buffer);\n\n\t\t\t\t// Here we must anticipate the type of each Chunk to use its appropriate\n\t\t\t\t// parser.\n\t\t\t\tChunk::ChunkType chunkType;\n\t\t\t\tuint16_t chunkLength;\n\t\t\t\tuint8_t padding;\n\n\t\t\t\tif (!Chunk::IsChunk(ptr, chunkMaxBufferLength, chunkType, chunkLength, padding))\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"not an SCTP Chunk\");\n\n\t\t\t\t\tdelete packet;\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tChunk* chunk{ nullptr }; // NOLINT(misc-const-correctness)\n\n\t\t\t\tMS_DEBUG_DEV(\"parsing SCTP Chunk [ptr:%zu, type:%\" PRIu8 \"]\", ptr - buffer, chunkType);\n\n\t\t\t\tswitch (chunkType)\n\t\t\t\t{\n\t\t\t\t\tcase Chunk::ChunkType::DATA:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = DataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::INIT:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = InitChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::INIT_ACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = InitAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::SACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = SackChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::HEARTBEAT_REQUEST:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk =\n\t\t\t\t\t\t  HeartbeatRequestChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::HEARTBEAT_ACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = HeartbeatAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::ABORT:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk =\n\t\t\t\t\t\t  AbortAssociationChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::SHUTDOWN:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = ShutdownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::SHUTDOWN_ACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = ShutdownAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::OPERATION_ERROR:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk =\n\t\t\t\t\t\t  OperationErrorChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::COOKIE_ECHO:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = CookieEchoChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::COOKIE_ACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = CookieAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::SHUTDOWN_COMPLETE:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk =\n\t\t\t\t\t\t  ShutdownCompleteChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::FORWARD_TSN:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = ForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::RE_CONFIG:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = ReConfigChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::I_DATA:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = IDataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase Chunk::ChunkType::I_FORWARD_TSN:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = IForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tchunk = UnknownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!chunk)\n\t\t\t\t{\n\t\t\t\t\tdelete packet;\n\t\t\t\t\treturn nullptr;\n\t\t\t\t}\n\n\t\t\t\tpacket->chunks.push_back(chunk);\n\n\t\t\t\tptr += chunk->GetLength();\n\t\t\t}\n\n\t\t\tconst size_t computedLength = ptr - buffer;\n\n\t\t\t// Ensure computed length matches the total given buffer length.\n\t\t\tif (computedLength != bufferLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"computed length (%zu bytes) != buffer length (%zu bytes)\",\n\t\t\t\t  computedLength,\n\t\t\t\t  bufferLength);\n\n\t\t\t\tdelete packet;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// It's mandatory to call SetLength() once we are done and we know the\n\t\t\t// exact length of the Packet.\n\t\t\tpacket->SetLength(computedLength);\n\n\t\t\treturn packet;\n\t\t}\n\n\t\tPacket* Packet::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t computedLength = Packet::CommonHeaderLength;\n\n\t\t\t// No space for common header.\n\t\t\tif (bufferLength < computedLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"no space for common header\");\n\t\t\t}\n\n\t\t\tauto* packet = new Packet(buffer, bufferLength);\n\n\t\t\t// Must initialize extra fields in the header.\n\t\t\tpacket->SetSourcePort(0u);\n\t\t\tpacket->SetDestinationPort(0u);\n\t\t\tpacket->SetVerificationTag(0u);\n\t\t\tpacket->SetChecksum(0u);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum Packet length.\n\n\t\t\treturn packet;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tPacket::Packet(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Packet::CommonHeaderLength);\n\t\t}\n\n\t\tPacket::~Packet()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (const auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tdelete chunk;\n\t\t\t}\n\t\t}\n\n\t\tvoid Packet::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::Packet>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  length: %zu (buffer length: %zu)\", GetLength(), GetBufferLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  source port: %\" PRIu16, GetSourcePort());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  destination port: %\" PRIu16, GetDestinationPort());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  verification tag: %\" PRIu32, GetVerificationTag());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  checksum: %\" PRIu32, GetChecksum());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  chunks count: %zu\", GetChunksCount());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  needs consolidation of chunks: %s\", NeedsConsolidation() ? \"yes\" : \"no\");\n\t\t\tfor (const auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tchunk->Dump(indentation + 1);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::Packet>\");\n\t\t}\n\n\t\tvoid Packet::Serialize(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto* previousBuffer = GetBuffer();\n\n\t\t\t// Invoke the parent method to copy the whole buffer.\n\t\t\tSerializable::Serialize(buffer, bufferLength);\n\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tconst size_t offset = chunk->GetBuffer() - previousBuffer;\n\n\t\t\t\tchunk->SoftSerialize(buffer + offset);\n\t\t\t}\n\t\t}\n\n\t\tPacket* Packet::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedPacket = new Packet(buffer, bufferLength);\n\n\t\t\tSerializable::CloneInto(clonedPacket);\n\n\t\t\t// Soft clone Packet Chunks into the given cloned Packet.\n\t\t\tfor (auto* chunk : this->chunks)\n\t\t\t{\n\t\t\t\tconst size_t offset = chunk->GetBuffer() - GetBuffer();\n\n\t\t\t\tauto* softClonedChunk = chunk->SoftClone(buffer + offset);\n\n\t\t\t\tclonedPacket->chunks.push_back(softClonedChunk);\n\t\t\t}\n\n\t\t\treturn clonedPacket;\n\t\t}\n\n\t\tvoid Packet::SetSourcePort(uint16_t sourcePort)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetHeaderPointer()->sourcePort = htons(sourcePort);\n\t\t}\n\n\t\tvoid Packet::SetDestinationPort(uint16_t destinationPort)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetHeaderPointer()->destinationPort = htons(destinationPort);\n\t\t}\n\n\t\tvoid Packet::SetVerificationTag(uint32_t verificationTag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetHeaderPointer()->verificationTag = htonl(verificationTag);\n\t\t}\n\n\t\tvoid Packet::SetChecksum(uint32_t checksum)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetHeaderPointer()->checksum = htonl(checksum);\n\t\t}\n\n\t\tvoid Packet::AddChunk(const Chunk* chunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tAssertDoesNotNeedConsolidation();\n\n\t\t\tconst size_t length = GetLength() + chunk->GetLength();\n\n\t\t\t// Let's append the Chunk at the end of existing Chunks.\n\t\t\tauto* clonedChunk =\n\t\t\t  chunk->Clone(const_cast<uint8_t*>(GetBuffer()) + GetLength(), chunk->GetLength());\n\n\t\t\t// Update Serializable length.\n\t\t\ttry\n\t\t\t{\n\t\t\t\tSetLength(length);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tdelete clonedChunk;\n\n\t\t\t\tthrow;\n\t\t\t}\n\n\t\t\tthis->chunks.push_back(clonedChunk);\n\t\t}\n\n\t\tvoid Packet::WriteCRC32cChecksum()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetChecksum(0u);\n\n\t\t\tauto crc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength());\n\n\t\t\tSetChecksum(crc32c);\n\t\t}\n\n\t\tbool Packet::ValidateCRC32cChecksum() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto crc32c = GetChecksum();\n\n\t\t\t// NOTE: Cannot use SetChecksum() because its a `const` method.\n\t\t\tGetHeaderPointer()->checksum = 0;\n\n\t\t\tauto computedCrc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength());\n\n\t\t\tGetHeaderPointer()->checksum = htonl(crc32c);\n\n\t\t\treturn computedCrc32c == crc32c;\n\t\t}\n\n\t\tvoid Packet::HandleInPlaceChunk(Chunk* chunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->needsConsolidation = true;\n\n\t\t\t// When the application completes the Chunk it must call\n\t\t\t// `chunk->Consolidate()` and that will trigger this event.\n\t\t\tchunk->SetConsolidatedListener(\n\t\t\t  [this, chunk]()\n\t\t\t  {\n\t\t\t\t  try\n\t\t\t\t  {\n\t\t\t\t\t  if (chunk->NeedsConsolidation())\n\t\t\t\t\t  {\n\t\t\t\t\t\t  MS_THROW_ERROR(\"ongoing Chunk needs consolidation\");\n\t\t\t\t\t  }\n\n\t\t\t\t\t  // Fix buffer length assigned to the Chunk.\n\t\t\t\t\t  chunk->SetBufferLength(chunk->GetLength());\n\n\t\t\t\t\t  // Update Packet length.\n\t\t\t\t\t  // NOTE: This will throw if there is no enough space in the Packet\n\t\t\t\t\t  // buffer.\n\t\t\t\t\t  SetLength(GetLength() + chunk->GetLength());\n\n\t\t\t\t\t  // Add the Chunk to the list.\n\t\t\t\t\t  this->chunks.push_back(chunk);\n\t\t\t\t\t  this->needsConsolidation = false;\n\t\t\t\t  }\n\t\t\t\t  catch (const MediaSoupError& error)\n\t\t\t\t  {\n\t\t\t\t\t  this->needsConsolidation = false;\n\n\t\t\t\t\t  throw;\n\t\t\t\t  }\n\t\t\t  });\n\t\t}\n\n\t\tvoid Packet::AssertDoesNotNeedConsolidation() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->needsConsolidation)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"Packet needs consolidation of some ongoing Chunk\");\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/Parameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Parameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst std::unordered_map<Parameter::ParameterType, std::string> Parameter::ParameterType2String =\n\t\t{\n\t\t\t{ Parameter::ParameterType::HEARTBEAT_INFO,               \"HEARTBEAT_INFO\"               },\n\t\t\t{ Parameter::ParameterType::IPV4_ADDRESS,                 \"IPV4_ADDRESS\"                 },\n\t\t\t{ Parameter::ParameterType::IPV6_ADDRESS,                 \"IPV6_ADDRESS\"                 },\n\t\t\t{ Parameter::ParameterType::STATE_COOKIE,                 \"STATE_COOKIE\"                 },\n\t\t\t{ Parameter::ParameterType::UNRECOGNIZED_PARAMETER,       \"UNRECOGNIZED_PARAMETER\"       },\n\t\t\t{ Parameter::ParameterType::COOKIE_PRESERVATIVE,          \"COOKIE_PRESERVATIVE\"          },\n\t\t\t{ Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,      \"SUPPORTED_ADDRESS_TYPES\"      },\n\t\t\t{ Parameter::ParameterType::FORWARD_TSN_SUPPORTED,        \"FORWARD_TSN_SUPPORTED\"        },\n\t\t\t{ Parameter::ParameterType::SUPPORTED_EXTENSIONS,         \"SUPPORTED_EXTENSIONS\"         },\n\t\t\t{ Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,   \"OUTGOING_SSN_RESET_REQUEST\"   },\n\t\t\t{ Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,   \"INCOMING_SSN_RESET_REQUEST\"   },\n\t\t\t{ Parameter::ParameterType::SSN_TSN_RESET_REQUEST,        \"SSN_TSN_RESET_REQUEST\"        },\n\t\t\t{ Parameter::ParameterType::RECONFIGURATION_RESPONSE,     \"RECONFIGURATION_RESPONSE\"     },\n\t\t\t{ Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, \"ADD_OUTGOING_STREAMS_REQUEST\" },\n\t\t\t{ Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, \"ADD_INCOMING_STREAMS_REQUEST\" },\n\t\t\t{ Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,     \"ZERO_CHECKSUM_ACCEPTABLE\"     },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tbool Parameter::IsParameter(\n\t\t  const uint8_t* buffer,\n\t\t  size_t bufferLength,\n\t\t  Parameter::ParameterType& parameterType,\n\t\t  uint16_t& parameterLength,\n\t\t  uint8_t& padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!TLV::IsTLV(buffer, bufferLength, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tparameterType = static_cast<Parameter::ParameterType>(Utils::Byte::Get2Bytes(buffer, 0));\n\n\t\t\treturn true;\n\t\t}\n\n\t\tconst std::string& Parameter::ParameterTypeToString(ParameterType parameterType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = Parameter::ParameterType2String.find(parameterType);\n\n\t\t\tif (it == Parameter::ParameterType2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tParameter::Parameter(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tParameter::~Parameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid Parameter::SoftCloneInto(Parameter* parameter) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Need to manually set Serializable length.\n\t\t\tparameter->SetLength(GetLength());\n\t\t}\n\n\t\tvoid Parameter::DumpCommon(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  type: %\" PRIu16 \" (%s) (unknown: %s)\",\n\t\t\t  static_cast<uint16_t>(GetType()),\n\t\t\t  Parameter::ParameterTypeToString(GetType()).c_str(),\n\t\t\t  HasUnknownType() ? \"yes\" : \"no\");\n\t\t\tTLV::DumpCommon(indentation);\n\t\t}\n\n\t\tvoid Parameter::SoftSerialize(const uint8_t* buffer)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBuffer(const_cast<uint8_t*>(buffer));\n\t\t}\n\n\t\tvoid Parameter::InitializeHeader(ParameterType parameterType, uint16_t lengthFieldValue)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetType(parameterType);\n\t\t\tInitializeTLVHeader(lengthFieldValue);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/TLV.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::TLV\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/TLV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memmove()\n#include <limits>  // std::numeric_limits\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tbool TLV::IsTLV(const uint8_t* buffer, size_t bufferLength, uint16_t& itemLength, uint8_t& padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < TLV::TLVHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"no space for Header [bufferLength:%zu]\", bufferLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\titemLength = Utils::Byte::Get2Bytes(buffer, 2);\n\n\t\t\tif (itemLength < TLV::TLVHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"Length field must have value greater or equal than %zu\", TLV::TLVHeaderLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Item total length must be multiple of 4 bytes and must include padding\n\t\t\t// bytes despite item Length field does not include padding.\n\t\t\t// NOTE: We must cast to size_t, otherwise a maximum item Length value of\n\t\t\t// 65535 would generate a padded length of 0 bytes!\n\t\t\tconst size_t paddedItemLength = Utils::Byte::PadTo4Bytes(size_t{ itemLength });\n\n\t\t\tif (bufferLength < paddedItemLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"no space for 4-byte padded announced Length [paddedItemLength:%zu, bufferLength:%zu]\",\n\t\t\t\t  paddedItemLength,\n\t\t\t\t  bufferLength);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tpadding = paddedItemLength - itemLength;\n\n\t\t\treturn true;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tTLV::TLV(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tTLV::~TLV()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid TLV::DumpCommon(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  length field: %\" PRIu16 \" (padding: %zu, buffer length: %zu)\",\n\t\t\t  GetLengthField(),\n\t\t\t  GetLength() - GetLengthField(),\n\t\t\t  GetBufferLength());\n\t\t}\n\n\t\tvoid TLV::InitializeTLVHeader(uint16_t lengthFieldValue)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLengthField(lengthFieldValue);\n\t\t}\n\n\t\tvoid TLV::SetLengthField(size_t lengthField)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (lengthField > std::numeric_limits<uint16_t>::max())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"lengthField (%zu bytes) cannot be greater than 65535\", lengthField);\n\t\t\t}\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 2, lengthField);\n\t\t}\n\n\t\tvoid TLV::SetVariableLengthValue(const uint8_t* value, size_t valueLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(value != nullptr || valueLength == 0, \"value cannot be nullptr if valueLength is > 0\");\n\n\t\t\t// NOTE: This can throw.\n\t\t\tSetVariableLengthValueLength(valueLength);\n\n\t\t\tif (value)\n\t\t\t{\n\t\t\t\tstd::memmove(GetVariableLengthValuePointer(), value, valueLength);\n\t\t\t}\n\t\t}\n\n\t\tvoid TLV::SetVariableLengthValueLength(size_t valueLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousLength      = GetLength();\n\t\t\tauto previousLengthField = GetLengthField();\n\t\t\tauto previousValueLength = GetVariableLengthValueLength();\n\t\t\tauto newNotPaddedLength =\n\t\t\t  size_t{ previousLengthField } - size_t{ previousValueLength } + valueLength;\n\t\t\tauto newPaddedLength = Utils::Byte::PadTo4Bytes(newNotPaddedLength);\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\t// Let's call SetLength() on parent with the new computed length.\n\t\t\t\t// NOTE: If there is no space in the buffer for it, it will throw.\n\t\t\t\tSetLength(newPaddedLength);\n\n\t\t\t\t// Update Length field.\n\t\t\t\t// NOTE: This will throw if computed value is too big.\n\t\t\t\tSetLengthField(newNotPaddedLength);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\t// Rollback.\n\t\t\t\tSetLength(previousLength);\n\t\t\t\tSetLengthField(previousLengthField);\n\n\t\t\t\tthrow;\n\t\t\t}\n\n\t\t\t// Fill padding bytes with zero.\n\t\t\tFillPadding(newPaddedLength - newNotPaddedLength);\n\t\t}\n\n\t\tvoid TLV::AddItem(const TLV* item)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousLength      = GetLength();\n\t\t\tauto previousLengthField = GetLengthField();\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\t// Update length.\n\t\t\t\t// NOTE: This will throw if there is no enough space in the buffer.\n\t\t\t\tSetLength(previousLength + item->GetLength());\n\n\t\t\t\t// Update Length field.\n\t\t\t\t// NOTE: This will throw if computed Length field value is too big.\n\t\t\t\tSetLengthField(previousLength + item->GetLengthField());\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\t// Rollback.\n\t\t\t\tSetLength(previousLength);\n\t\t\t\tSetLengthField(previousLengthField);\n\n\t\t\t\tthrow;\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/UserData.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UserData\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tUserData::UserData(\n\t\t  uint16_t streamId,\n\t\t  uint16_t ssn,\n\t\t  uint32_t mid,\n\t\t  uint32_t fsn,\n\t\t  uint32_t ppid,\n\t\t  std::vector<uint8_t> payload,\n\t\t  bool isBeginning,\n\t\t  bool isEnd,\n\t\t  bool isUnordered)\n\t\t  : streamId(streamId),\n\t\t    ssn(ssn),\n\t\t    mid(mid),\n\t\t    fsn(fsn),\n\t\t    ppid(ppid),\n\t\t    payload(std::move(payload)),\n\t\t    isBeginning(isBeginning),\n\t\t    isEnd(isEnd),\n\t\t    isUnordered(isUnordered)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tUserData::~UserData()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UserData::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UserData>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream id: %\" PRIu16, GetStreamId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ssn: %\" PRIu16, GetStreamSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  mid: %\" PRIu32, GetMessageId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  fsn: %\" PRIu32, GetFragmentSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ppid: %\" PRIu32, GetPayloadProtocolId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload length: %zu\", GetPayloadLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  is beginning: %s\", IsBeginning() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  is end: %s\", IsEnd() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  is unordered: %s\", IsUnordered() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UserData>\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/AbortAssociationChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::AbortAssociationChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tAbortAssociationChunk* AbortAssociationChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::ABORT)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn AbortAssociationChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tAbortAssociationChunk* AbortAssociationChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new AbortAssociationChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::ABORT, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum AbortAssociationChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tAbortAssociationChunk* AbortAssociationChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new AbortAssociationChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Error Causes.\n\t\t\tif (!chunk->ParseErrorCauses())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Error Causes\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tAbortAssociationChunk::AbortAssociationChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tAbortAssociationChunk::~AbortAssociationChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid AbortAssociationChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::AbortAssociationChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag T: %\" PRIu8, GetT());\n\t\t\tDumpErrorCauses(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::AbortAssociationChunk>\");\n\t\t}\n\n\t\tAbortAssociationChunk* AbortAssociationChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new AbortAssociationChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid AbortAssociationChunk::SetT(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit0(flag);\n\t\t}\n\n\t\tAbortAssociationChunk* AbortAssociationChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new AbortAssociationChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/CookieAckChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::CookieAckChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/CookieAckChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tCookieAckChunk* CookieAckChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::COOKIE_ACK)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn CookieAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tCookieAckChunk* CookieAckChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new CookieAckChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::COOKIE_ACK, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// CookieAckChunk fixed length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tCookieAckChunk* CookieAckChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength != Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"CookieAckChunk Length field must be %zu\", Chunk::ChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new CookieAckChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tCookieAckChunk::CookieAckChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tCookieAckChunk::~CookieAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid CookieAckChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::CookieAckChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::CookieAckChunk>\");\n\t\t}\n\n\t\tCookieAckChunk* CookieAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new CookieAckChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tCookieAckChunk* CookieAckChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new CookieAckChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/CookieEchoChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::CookieEchoChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/CookieEchoChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tCookieEchoChunk* CookieEchoChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::COOKIE_ECHO)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn CookieEchoChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tCookieEchoChunk* CookieEchoChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new CookieEchoChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::COOKIE_ECHO, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum CookieEchoChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tCookieEchoChunk* CookieEchoChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new CookieEchoChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tCookieEchoChunk::CookieEchoChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tCookieEchoChunk::~CookieEchoChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid CookieEchoChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::CookieEchoChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  cookie length: %\" PRIu16 \" (has cookie: %s)\",\n\t\t\t  GetCookieLength(),\n\t\t\t  HasCookie() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::CookieEchoChunk>\");\n\t\t}\n\n\t\tCookieEchoChunk* CookieEchoChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new CookieEchoChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid CookieEchoChunk::SetCookie(const uint8_t* cookie, uint16_t cookieLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(cookie, cookieLength);\n\t\t}\n\n\t\tCookieEchoChunk* CookieEchoChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new CookieEchoChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/DataChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::DataChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tDataChunk* DataChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::DATA)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn DataChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tDataChunk* DataChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < DataChunk::DataChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new DataChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::DATA, 0, DataChunk::DataChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetTsn(0);\n\t\t\tchunk->SetStreamId(0);\n\t\t\tchunk->SetStreamSequenceNumber(0);\n\t\t\tchunk->SetPayloadProtocolId(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum DataChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tDataChunk* DataChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < DataChunk::DataChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"DataChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  DataChunk::DataChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new DataChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tDataChunk::DataChunk(uint8_t* buffer, size_t bufferLength) : AnyDataChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(DataChunk::DataChunkHeaderLength);\n\t\t}\n\n\t\tDataChunk::~DataChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid DataChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::DataChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag I: %\" PRIu8, GetI());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag U: %\" PRIu8, GetU());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag B: %\" PRIu8, GetB());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag E: %\" PRIu8, GetE());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tsn: %\" PRIu32, GetTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream id: %\" PRIu16, GetStreamId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream sequence number: %\" PRIu16, GetStreamSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload protocol id (PPID): %\" PRIu32, GetPayloadProtocolId());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  user data length: %\" PRIu16 \" (has user data: %s)\",\n\t\t\t  GetUserDataPayloadLength(),\n\t\t\t  HasUserDataPayload() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::DataChunk>\");\n\t\t}\n\n\t\tDataChunk* DataChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new DataChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid DataChunk::SetI(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit3(flag);\n\t\t}\n\n\t\tvoid DataChunk::SetU(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit2(flag);\n\t\t}\n\n\t\tvoid DataChunk::SetB(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit1(flag);\n\t\t}\n\n\t\tvoid DataChunk::SetE(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit0(flag);\n\t\t}\n\n\t\tvoid DataChunk::SetTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid DataChunk::SetStreamId(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tvoid DataChunk::SetStreamSequenceNumber(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 10, value);\n\t\t}\n\n\t\tvoid DataChunk::SetPayloadProtocolId(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tvoid DataChunk::SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(userDataPayload, userDataPayloadLength);\n\t\t}\n\n\t\tvoid DataChunk::SetUserData(UserData userData)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetStreamId(userData.GetStreamId());\n\t\t\tSetStreamSequenceNumber(userData.GetStreamSequenceNumber());\n\t\t\tSetPayloadProtocolId(userData.GetPayloadProtocolId());\n\n\t\t\tSetB(userData.IsBeginning());\n\t\t\tSetE(userData.IsEnd());\n\t\t\tSetU(userData.IsUnordered());\n\n\t\t\tconst auto payload = std::move(userData).ReleasePayload();\n\n\t\t\tSetUserDataPayload(payload.data(), payload.size());\n\t\t}\n\n\t\tDataChunk* DataChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new DataChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/ForwardTsnChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ForwardTsnChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tForwardTsnChunk* ForwardTsnChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::FORWARD_TSN)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ForwardTsnChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tForwardTsnChunk* ForwardTsnChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ForwardTsnChunk::ForwardTsnChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new ForwardTsnChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(\n\t\t\t  Chunk::ChunkType::FORWARD_TSN, 0, ForwardTsnChunk::ForwardTsnChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetNewCumulativeTsn(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum ForwardTsnChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tForwardTsnChunk* ForwardTsnChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < ForwardTsnChunk::ForwardTsnChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"ForwardTsnChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  ForwardTsnChunk::ForwardTsnChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Here we must validate that length is multiple of 4.\n\t\t\tif (chunkLength % 4 != 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"wrong length (not multiple of 4)\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new ForwardTsnChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tForwardTsnChunk::ForwardTsnChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : AnyForwardTsnChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ForwardTsnChunk::ForwardTsnChunkHeaderLength);\n\t\t}\n\n\t\tForwardTsnChunk::~ForwardTsnChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ForwardTsnChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ForwardTsnChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  new cumulative tsn: %\" PRIu32, GetNewCumulativeTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of skipped streams: %\" PRIu16, GetNumberOfSkippedStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  skipped streams:\");\n\t\t\tfor (const auto& skippedStream : GetSkippedStreams())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  - stream id: %\" PRIu16 \", ssn:%\" PRIu16,\n\t\t\t\t  skippedStream.streamId,\n\t\t\t\t  skippedStream.ssn);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ForwardTsnChunk>\");\n\t\t}\n\n\t\tForwardTsnChunk* ForwardTsnChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new ForwardTsnChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid ForwardTsnChunk::SetNewCumulativeTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> ForwardTsnChunk::GetSkippedStreams() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> skippedStreams;\n\t\t\tconst uint16_t numSkippedStreams = GetNumberOfSkippedStreams();\n\n\t\t\tskippedStreams.reserve(numSkippedStreams);\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numSkippedStreams; ++idx)\n\t\t\t{\n\t\t\t\tskippedStreams.emplace_back(GetSkippedStreamIdAt(idx), GetStreamSequenceAt(idx));\n\t\t\t}\n\n\t\t\treturn skippedStreams;\n\t\t}\n\n\t\tvoid ForwardTsnChunk::AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousVariableLengthValueLength = GetVariableLengthValueLength();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(previousVariableLengthValueLength + 4);\n\n\t\t\t// Add the new stream and stream sequence.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength, skippedStream.streamId);\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength + 2, skippedStream.ssn);\n\t\t}\n\n\t\tForwardTsnChunk* ForwardTsnChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new ForwardTsnChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/HeartbeatAckChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::HeartbeatAckChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tHeartbeatAckChunk* HeartbeatAckChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::HEARTBEAT_ACK)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn HeartbeatAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tHeartbeatAckChunk* HeartbeatAckChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new HeartbeatAckChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::HEARTBEAT_ACK, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum HeartbeatAckChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tHeartbeatAckChunk* HeartbeatAckChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new HeartbeatAckChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Parameters.\n\t\t\tif (!chunk->ParseParameters())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Parameters\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tHeartbeatAckChunk::HeartbeatAckChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tHeartbeatAckChunk::~HeartbeatAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid HeartbeatAckChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::HeartbeatAckChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tDumpParameters(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::HeartbeatAckChunk>\");\n\t\t}\n\n\t\tHeartbeatAckChunk* HeartbeatAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new HeartbeatAckChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tHeartbeatAckChunk* HeartbeatAckChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new HeartbeatAckChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::HeartbeatRequestChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tHeartbeatRequestChunk* HeartbeatRequestChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::HEARTBEAT_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn HeartbeatRequestChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tHeartbeatRequestChunk* HeartbeatRequestChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new HeartbeatRequestChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::HEARTBEAT_REQUEST, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum HeartbeatRequestChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tHeartbeatRequestChunk* HeartbeatRequestChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new HeartbeatRequestChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Parameters.\n\t\t\tif (!chunk->ParseParameters())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Parameters\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tHeartbeatRequestChunk::HeartbeatRequestChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tHeartbeatRequestChunk::~HeartbeatRequestChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid HeartbeatRequestChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::HeartbeatRequestChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tDumpParameters(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::HeartbeatRequestChunk>\");\n\t\t}\n\n\t\tHeartbeatRequestChunk* HeartbeatRequestChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new HeartbeatRequestChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tHeartbeatRequestChunk* HeartbeatRequestChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new HeartbeatRequestChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/IDataChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::IDataChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tIDataChunk* IDataChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::I_DATA)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn IDataChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tIDataChunk* IDataChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < IDataChunk::IDataChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new IDataChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::I_DATA, 0, IDataChunk::IDataChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetTsn(0);\n\t\t\tchunk->SetStreamId(0);\n\t\t\tchunk->SetReserved();\n\t\t\tchunk->SetMessageId(0);\n\t\t\t// NOTE: BitB is not set so we must set FSN to 0 rather than setting PPID.\n\t\t\tchunk->SetFragmentSequenceNumber(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum IDataChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tIDataChunk* IDataChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < IDataChunk::IDataChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"IDataChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  IDataChunk::IDataChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new IDataChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIDataChunk::IDataChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : AnyDataChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(IDataChunk::IDataChunkHeaderLength);\n\t\t}\n\n\t\tIDataChunk::~IDataChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid IDataChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::IDataChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag I: %\" PRIu8, GetI());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag U: %\" PRIu8, GetU());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag B: %\" PRIu8, GetB());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag E: %\" PRIu8, GetE());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tsn: %\" PRIu32, GetTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream id: %\" PRIu16, GetStreamId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  message id: %\" PRIu32, GetMessageId());\n\t\t\tif (GetB())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  payload protocol id (PPID): %\" PRIu32, GetPayloadProtocolId());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  fragment sequence number (FSN): %\" PRIu32, GetFragmentSequenceNumber());\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  user data length: %\" PRIu16 \" (has user data: %s)\",\n\t\t\t  GetUserDataPayloadLength(),\n\t\t\t  HasUserDataPayload() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::IDataChunk>\");\n\t\t}\n\n\t\tIDataChunk* IDataChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new IDataChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid IDataChunk::SetI(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit3(flag);\n\t\t}\n\n\t\tvoid IDataChunk::SetU(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit2(flag);\n\t\t}\n\n\t\tvoid IDataChunk::SetB(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit1(flag);\n\t\t}\n\n\t\tvoid IDataChunk::SetE(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit0(flag);\n\t\t}\n\n\t\tvoid IDataChunk::SetTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid IDataChunk::SetStreamId(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tvoid IDataChunk::SetMessageId(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tvoid IDataChunk::SetPayloadProtocolId(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!GetB())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"cannot set payload protocol id (PPID) if bit B is not set\");\n\t\t\t}\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 16, value);\n\t\t}\n\n\t\tvoid IDataChunk::SetFragmentSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (GetB())\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"cannot set payload protocol id (PPID) if bit B is set\");\n\t\t\t}\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 16, value);\n\t\t}\n\n\t\tvoid IDataChunk::SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(userDataPayload, userDataPayloadLength);\n\t\t}\n\n\t\tvoid IDataChunk::SetUserData(UserData userData)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetStreamId(userData.GetStreamId());\n\t\t\tSetMessageId(userData.GetMessageId());\n\n\t\t\tSetB(userData.IsBeginning());\n\t\t\tSetE(userData.IsEnd());\n\t\t\tSetU(userData.IsUnordered());\n\n\t\t\tif (GetB())\n\t\t\t{\n\t\t\t\tSetPayloadProtocolId(userData.GetPayloadProtocolId());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tSetFragmentSequenceNumber(userData.GetFragmentSequenceNumber());\n\t\t\t}\n\n\t\t\tconst auto payload = std::move(userData).ReleasePayload();\n\n\t\t\tSetUserDataPayload(payload.data(), payload.size());\n\t\t}\n\n\t\tIDataChunk* IDataChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new IDataChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\n\t\tvoid IDataChunk::SetReserved()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 10, 0);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/IForwardTsnChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::IForwardTsnChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tIForwardTsnChunk* IForwardTsnChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::I_FORWARD_TSN)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn IForwardTsnChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tIForwardTsnChunk* IForwardTsnChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < IForwardTsnChunk::IForwardTsnChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new IForwardTsnChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(\n\t\t\t  Chunk::ChunkType::I_FORWARD_TSN, 0, IForwardTsnChunk::IForwardTsnChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetNewCumulativeTsn(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum IForwardTsnChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tIForwardTsnChunk* IForwardTsnChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < IForwardTsnChunk::IForwardTsnChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"IForwardTsnChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  IForwardTsnChunk::IForwardTsnChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Here we must validate that length is multiple of 8.\n\t\t\tif (chunkLength % 8 != 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"wrong length (not multiple of 4)\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new IForwardTsnChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIForwardTsnChunk::IForwardTsnChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : AnyForwardTsnChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(IForwardTsnChunk::IForwardTsnChunkHeaderLength);\n\t\t}\n\n\t\tIForwardTsnChunk::~IForwardTsnChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid IForwardTsnChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::IForwardTsnChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  new cumulative tsn: %\" PRIu32, GetNewCumulativeTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of skipped streams: %\" PRIu16, GetNumberOfSkippedStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  skipped streams:\");\n\t\t\tfor (auto& skippedStream : GetSkippedStreams())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  - unordered:%s, stream id: %\" PRIu16 \", mid:%\" PRIu32,\n\t\t\t\t  skippedStream.unordered ? \"yes\" : \"no\",\n\t\t\t\t  skippedStream.streamId,\n\t\t\t\t  skippedStream.mid);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::IForwardTsnChunk>\");\n\t\t}\n\n\t\tIForwardTsnChunk* IForwardTsnChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new IForwardTsnChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid IForwardTsnChunk::SetNewCumulativeTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> IForwardTsnChunk::GetSkippedStreams() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<AnyForwardTsnChunk::SkippedStream> skippedStreams;\n\t\t\tconst uint16_t numSkippedStreams = GetNumberOfSkippedStreams();\n\n\t\t\tskippedStreams.reserve(numSkippedStreams);\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numSkippedStreams; ++idx)\n\t\t\t{\n\t\t\t\tskippedStreams.emplace_back(\n\t\t\t\t  GetUFlagAt(idx), GetSkippedStreamIdAt(idx), GetMessageIdentifierAt(idx));\n\t\t\t}\n\n\t\t\treturn skippedStreams;\n\t\t}\n\n\t\tvoid IForwardTsnChunk::AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousVariableLengthValueLength = GetVariableLengthValueLength();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(previousVariableLengthValueLength + 8);\n\n\t\t\t// Add the new stream, flag U and message identifier.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength, skippedStream.streamId);\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(),\n\t\t\t  previousVariableLengthValueLength + 2,\n\t\t\t  skippedStream.unordered);\n\t\t\tUtils::Byte::Set4Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength + 4, skippedStream.mid);\n\t\t}\n\n\t\tIForwardTsnChunk* IForwardTsnChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new IForwardTsnChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/InitAckChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::InitAckChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/InitAckChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tInitAckChunk* InitAckChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::INIT_ACK)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn InitAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tInitAckChunk* InitAckChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < InitAckChunk::InitAckChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new InitAckChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::INIT_ACK, 0, InitAckChunk::InitAckChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetInitiateTag(0);\n\t\t\tchunk->SetAdvertisedReceiverWindowCredit(0);\n\t\t\tchunk->SetNumberOfOutboundStreams(0);\n\t\t\tchunk->SetNumberOfInboundStreams(0);\n\t\t\tchunk->SetInitialTsn(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum InitAckChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tInitAckChunk* InitAckChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < InitAckChunk::InitAckChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"InitAckChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  InitAckChunk::InitAckChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new InitAckChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Parameters.\n\t\t\tif (!chunk->ParseParameters())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Parameters\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tInitAckChunk::InitAckChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : AnyInitChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(InitAckChunk::InitAckChunkHeaderLength);\n\t\t}\n\n\t\tInitAckChunk::~InitAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid InitAckChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::InitAckChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  initiate tag: %\" PRIu32, GetInitiateTag());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  advertised receiver window credit: %\" PRIu32,\n\t\t\t  GetAdvertisedReceiverWindowCredit());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  number of outbound streams: %\" PRIu16, GetNumberOfOutboundStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of inbound streams: %\" PRIu16, GetNumberOfInboundStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  initial tsn: %\" PRIu32, GetInitialTsn());\n\t\t\tDumpParameters(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::InitAckChunk>\");\n\t\t}\n\n\t\tInitAckChunk* InitAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new InitAckChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid InitAckChunk::SetInitiateTag(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid InitAckChunk::SetAdvertisedReceiverWindowCredit(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tvoid InitAckChunk::SetNumberOfOutboundStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tvoid InitAckChunk::SetNumberOfInboundStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 14, value);\n\t\t}\n\n\t\tvoid InitAckChunk::SetInitialTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 16, value);\n\t\t}\n\n\t\tInitAckChunk* InitAckChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new InitAckChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/InitChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::InitChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tInitChunk* InitChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::INIT)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn InitChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tInitChunk* InitChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < InitChunk::InitChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new InitChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::INIT, 0, InitChunk::InitChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetInitiateTag(0);\n\t\t\tchunk->SetAdvertisedReceiverWindowCredit(0);\n\t\t\tchunk->SetNumberOfOutboundStreams(0);\n\t\t\tchunk->SetNumberOfInboundStreams(0);\n\t\t\tchunk->SetInitialTsn(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum InitChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tInitChunk* InitChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < InitChunk::InitChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"InitChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  InitChunk::InitChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new InitChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Parameters.\n\t\t\tif (!chunk->ParseParameters())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Parameters\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tInitChunk::InitChunk(uint8_t* buffer, size_t bufferLength) : AnyInitChunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(InitChunk::InitChunkHeaderLength);\n\t\t}\n\n\t\tInitChunk::~InitChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid InitChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::InitChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  initiate tag: %\" PRIu32, GetInitiateTag());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  advertised receiver window credit: %\" PRIu32,\n\t\t\t  GetAdvertisedReceiverWindowCredit());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  number of outbound streams: %\" PRIu16, GetNumberOfOutboundStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of inbound streams: %\" PRIu16, GetNumberOfInboundStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  initial tsn: %\" PRIu32, GetInitialTsn());\n\t\t\tDumpParameters(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::InitChunk>\");\n\t\t}\n\n\t\tInitChunk* InitChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new InitChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid InitChunk::SetInitiateTag(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid InitChunk::SetAdvertisedReceiverWindowCredit(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tvoid InitChunk::SetNumberOfOutboundStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tvoid InitChunk::SetNumberOfInboundStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 14, value);\n\t\t}\n\n\t\tvoid InitChunk::SetInitialTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 16, value);\n\t\t}\n\n\t\tInitChunk* InitChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new InitChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/OperationErrorChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::OperationErrorChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/OperationErrorChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tOperationErrorChunk* OperationErrorChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::OPERATION_ERROR)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn OperationErrorChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tOperationErrorChunk* OperationErrorChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new OperationErrorChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::OPERATION_ERROR, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum OperationErrorChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tOperationErrorChunk* OperationErrorChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new OperationErrorChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Error Causes.\n\t\t\tif (!chunk->ParseErrorCauses())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Error Causes\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tOperationErrorChunk::OperationErrorChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tOperationErrorChunk::~OperationErrorChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid OperationErrorChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::OperationErrorChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tDumpErrorCauses(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::OperationErrorChunk>\");\n\t\t}\n\n\t\tOperationErrorChunk* OperationErrorChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new OperationErrorChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tOperationErrorChunk* OperationErrorChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new OperationErrorChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/ReConfigChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ReConfigChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/ReConfigChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tReConfigChunk* ReConfigChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::RE_CONFIG)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ReConfigChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tReConfigChunk* ReConfigChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new ReConfigChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::RE_CONFIG, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum ReConfigChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tReConfigChunk* ReConfigChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new ReConfigChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Parse Parameters.\n\t\t\tif (!chunk->ParseParameters())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"failed to parse Parameters\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tReConfigChunk::ReConfigChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tReConfigChunk::~ReConfigChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ReConfigChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ReConfigChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tDumpParameters(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ReConfigChunk>\");\n\t\t}\n\n\t\tReConfigChunk* ReConfigChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new ReConfigChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tReConfigChunk* ReConfigChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new ReConfigChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/SackChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::SackChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memmove()\n#include <ranges>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tSackChunk* SackChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::SACK)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn SackChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tSackChunk* SackChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < SackChunk::SackChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new SackChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::SACK, 0, SackChunk::SackChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetCumulativeTsnAck(0);\n\t\t\tchunk->SetAdvertisedReceiverWindowCredit(0);\n\t\t\tchunk->SetNumberOfGapAckBlocks(0);\n\t\t\tchunk->SetNumberOfDuplicateTsns(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// minimum SackChunk length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tSackChunk* SackChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength < SackChunk::SackChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"SackChunk Length field must be equal or greater than %zu\",\n\t\t\t\t  SackChunk::SackChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new SackChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// In this Chunk we must validate that some fields have correct values.\n\t\t\tif (\n\t\t\t  (chunk->GetNumberOfGapAckBlocks() * 4) + (chunk->GetNumberOfDuplicateTsns() * 4) !=\n\t\t\t  chunkLength - SackChunk::SackChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"wrong values in Number of Gap Ack Blocks and/or Number of Duplicate TSNs fields\");\n\n\t\t\t\tdelete chunk;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tSackChunk::SackChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(SackChunk::SackChunkHeaderLength);\n\t\t}\n\n\t\tSackChunk::~SackChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid SackChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::SackChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  cumulative tsn ack: %\" PRIu32, GetCumulativeTsnAck());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  advertised receiver window credit: %\" PRIu32,\n\t\t\t  GetAdvertisedReceiverWindowCredit());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  validated gap ack blocks:\");\n\t\t\tfor (const auto& gapAckBlock : GetValidatedGapAckBlocks())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  - start: %\" PRIu16 \", end:%\" PRIu16, gapAckBlock.start, gapAckBlock.end);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"  duplicate tsns:\");\n\t\t\tfor (const uint32_t duplicateTsn : GetDuplicateTsns())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  - tsn: %\" PRIu32, duplicateTsn);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::SackChunk>\");\n\t\t}\n\n\t\tSackChunk* SackChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new SackChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid SackChunk::SetCumulativeTsnAck(uint32_t tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, tsn);\n\t\t}\n\n\t\tvoid SackChunk::SetAdvertisedReceiverWindowCredit(uint32_t aRwnd)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 8, aRwnd);\n\t\t}\n\n\t\tstd::vector<SackChunk::GapAckBlock> SackChunk::GetGapAckBlocks() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks();\n\t\t\tstd::vector<SackChunk::GapAckBlock> gapAckBlocks;\n\n\t\t\tgapAckBlocks.reserve(numberOfGapAckBlocks);\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx)\n\t\t\t{\n\t\t\t\tgapAckBlocks.emplace_back(GetAckBlockStartAt(idx), GetAckBlockEndAt(idx));\n\t\t\t}\n\n\t\t\treturn gapAckBlocks;\n\t\t}\n\n\t\tstd::vector<SackChunk::GapAckBlock> SackChunk::GetValidatedGapAckBlocks() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks();\n\t\t\tstd::vector<SackChunk::GapAckBlock> gapAckBlocks;\n\n\t\t\tgapAckBlocks.reserve(numberOfGapAckBlocks);\n\n\t\t\tif (ValidateGapAckBlocks())\n\t\t\t{\n\t\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx)\n\t\t\t\t{\n\t\t\t\t\tgapAckBlocks.emplace_back(GetAckBlockStartAt(idx), GetAckBlockEndAt(idx));\n\t\t\t\t}\n\n\t\t\t\treturn gapAckBlocks;\n\t\t\t}\n\n\t\t\t// First: Only keep blocks that are sane.\n\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx)\n\t\t\t{\n\t\t\t\tconst uint16_t start = GetAckBlockStartAt(idx);\n\t\t\t\tconst uint16_t end   = GetAckBlockEndAt(idx);\n\n\t\t\t\tif (end > start)\n\t\t\t\t{\n\t\t\t\t\tgapAckBlocks.emplace_back(start, end);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Not more than at most one remaining? Exit early.\n\t\t\tif (gapAckBlocks.size() == 1)\n\t\t\t{\n\t\t\t\treturn gapAckBlocks;\n\t\t\t}\n\n\t\t\t// Sort the intervals by their start value, to aid in the merging below.\n\t\t\tstd::ranges::sort(gapAckBlocks, {}, &SackChunk::GapAckBlock::start);\n\n\t\t\t// Merge overlapping ranges.\n\t\t\tsize_t writeIdx{ 0 };\n\n\t\t\tfor (size_t readIdx{ 1 }; readIdx < gapAckBlocks.size(); ++readIdx)\n\t\t\t{\n\t\t\t\tif (gapAckBlocks[writeIdx].end + 1 >= gapAckBlocks[readIdx].start)\n\t\t\t\t{\n\t\t\t\t\tgapAckBlocks[writeIdx].end =\n\t\t\t\t\t  std::max(gapAckBlocks[writeIdx].end, gapAckBlocks[readIdx].end);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++writeIdx;\n\t\t\t\t\tgapAckBlocks[writeIdx] = gapAckBlocks[readIdx];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tgapAckBlocks.resize(writeIdx + 1);\n\n\t\t\treturn gapAckBlocks;\n\t\t}\n\n\t\tstd::vector<uint32_t> SackChunk::GetDuplicateTsns() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint32_t numberOfDuplicateTsns = GetNumberOfDuplicateTsns();\n\t\t\tstd::vector<uint32_t> duplicateTsns;\n\n\t\t\tduplicateTsns.reserve(numberOfDuplicateTsns);\n\n\t\t\tfor (uint32_t idx{ 0 }; idx < numberOfDuplicateTsns; ++idx)\n\t\t\t{\n\t\t\t\tduplicateTsns.emplace_back(GetDuplicateTsnAt(idx));\n\t\t\t}\n\n\t\t\treturn duplicateTsns;\n\t\t}\n\n\t\tvoid SackChunk::AddAckBlock(uint16_t start, uint16_t end)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(GetVariableLengthValueLength() + 4);\n\n\t\t\t// Must move duplicate TSNs down.\n\t\t\tstd::memmove(\n\t\t\t  GetDuplicateTsnsPointer() + 4, GetDuplicateTsnsPointer(), GetNumberOfDuplicateTsns() * 4);\n\n\t\t\t// Add the new ack block.\n\t\t\tUtils::Byte::Set2Bytes(GetAckBlocksPointer(), GetNumberOfGapAckBlocks() * 4, start);\n\t\t\tUtils::Byte::Set2Bytes(GetAckBlocksPointer(), (GetNumberOfGapAckBlocks() * 4) + 2, end);\n\n\t\t\t// Update the counter field.\n\t\t\t// NOTE: Do this after moving bytes.\n\t\t\tSetNumberOfGapAckBlocks(GetNumberOfGapAckBlocks() + 1);\n\t\t}\n\n\t\tvoid SackChunk::AddDuplicateTsn(uint32_t tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(GetVariableLengthValueLength() + 4);\n\n\t\t\t// Add the new duplicate TSN.\n\t\t\tUtils::Byte::Set4Bytes(GetDuplicateTsnsPointer(), GetNumberOfDuplicateTsns() * 4, tsn);\n\n\t\t\t// Update the counter field.\n\t\t\t// NOTE: Do this after moving bytes.\n\t\t\tSetNumberOfDuplicateTsns(GetNumberOfDuplicateTsns() + 1);\n\t\t}\n\n\t\tSackChunk* SackChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new SackChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\n\t\tvoid SackChunk::SetNumberOfGapAckBlocks(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tvoid SackChunk::SetNumberOfDuplicateTsns(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 14, value);\n\t\t}\n\n\t\tbool SackChunk::ValidateGapAckBlocks() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks();\n\n\t\t\tif (numberOfGapAckBlocks == 0)\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Ensure that gap-ack-blocks are sorted, has an \"end\" that is not before\n\t\t\t// \"start\" and are non-overlapping and non-adjacent.\n\t\t\tuint16_t prevEnd{ 0 };\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx)\n\t\t\t{\n\t\t\t\tconst uint16_t start = GetAckBlockStartAt(idx);\n\t\t\t\tconst uint16_t end   = GetAckBlockEndAt(idx);\n\n\t\t\t\tif (end < start)\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\telse if (start <= (prevEnd + 1))\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tprevEnd = end;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/ShutdownAckChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ShutdownAckChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tShutdownAckChunk* ShutdownAckChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::SHUTDOWN_ACK)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ShutdownAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tShutdownAckChunk* ShutdownAckChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownAckChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN_ACK, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// ShutdownAckChunk fixed length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tShutdownAckChunk* ShutdownAckChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength != Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"ShutdownAckChunk Length field must be %zu\", Chunk::ChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownAckChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tShutdownAckChunk::ShutdownAckChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tShutdownAckChunk::~ShutdownAckChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ShutdownAckChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ShutdownAckChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ShutdownAckChunk>\");\n\t\t}\n\n\t\tShutdownAckChunk* ShutdownAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new ShutdownAckChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tShutdownAckChunk* ShutdownAckChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new ShutdownAckChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/ShutdownChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ShutdownChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/ShutdownChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tShutdownChunk* ShutdownChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::SHUTDOWN)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ShutdownChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tShutdownChunk* ShutdownChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ShutdownChunk::ShutdownChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN, 0, ShutdownChunk::ShutdownChunkHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tchunk->SetCumulativeTsnAck(0);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// ShutdownChunk fixed length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tShutdownChunk* ShutdownChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength != ShutdownChunk::ShutdownChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"ShutdownChunk Length field must be %zu\", ShutdownChunk::ShutdownChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tShutdownChunk::ShutdownChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ShutdownChunk::ShutdownChunkHeaderLength);\n\t\t}\n\n\t\tShutdownChunk::~ShutdownChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ShutdownChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ShutdownChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  cumulative tsn ack : %\" PRIu32, GetCumulativeTsnAck());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ShutdownChunk>\");\n\t\t}\n\n\t\tShutdownChunk* ShutdownChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new ShutdownChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid ShutdownChunk::SetCumulativeTsnAck(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tShutdownChunk* ShutdownChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new ShutdownChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ShutdownCompleteChunk\"\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tShutdownCompleteChunk* ShutdownCompleteChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (chunkType != Chunk::ChunkType::SHUTDOWN_COMPLETE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Chunk type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ShutdownCompleteChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tShutdownCompleteChunk* ShutdownCompleteChunk::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownCompleteChunk(buffer, bufferLength);\n\n\t\t\tchunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN_COMPLETE, 0, Chunk::ChunkHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since constructor invoked it with\n\t\t\t// ShutdownCompleteChunk fixed length.\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\tShutdownCompleteChunk* ShutdownCompleteChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (chunkLength != Chunk::ChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"ShutdownCompleteChunk Length field must be %zu\", Chunk::ChunkHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* chunk = new ShutdownCompleteChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tShutdownCompleteChunk::ShutdownCompleteChunk(uint8_t* buffer, size_t bufferLength)\n\t\t  : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tShutdownCompleteChunk::~ShutdownCompleteChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ShutdownCompleteChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ShutdownCompleteChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  flag T: %\" PRIu8, GetT());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ShutdownCompleteChunk>\");\n\t\t}\n\n\t\tShutdownCompleteChunk* ShutdownCompleteChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new ShutdownCompleteChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tvoid ShutdownCompleteChunk::SetT(bool flag)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetBit0(flag);\n\t\t}\n\n\t\tShutdownCompleteChunk* ShutdownCompleteChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new ShutdownCompleteChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/chunks/UnknownChunk.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnknownChunk\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/chunks/UnknownChunk.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnknownChunk* UnknownChunk::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tChunk::ChunkType chunkType;\n\t\t\tuint16_t chunkLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnknownChunk::ParseStrict(buffer, bufferLength, chunkLength, padding);\n\t\t}\n\n\t\tUnknownChunk* UnknownChunk::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* chunk = new UnknownChunk(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tchunk->SetLength(chunkLength + padding);\n\n\t\t\treturn chunk;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnknownChunk::UnknownChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Chunk::ChunkHeaderLength);\n\t\t}\n\n\t\tUnknownChunk::~UnknownChunk()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnknownChunk::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnknownChunk>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  unknown value length: %\" PRIu16 \" (has unknown value: %s)\",\n\t\t\t  GetUnknownValueLength(),\n\t\t\t  HasUnknownValue() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnknownChunk>\");\n\t\t}\n\n\t\tUnknownChunk* UnknownChunk::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedChunk = new UnknownChunk(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedChunk);\n\t\t\tSoftCloneInto(clonedChunk);\n\n\t\t\treturn clonedChunk;\n\t\t}\n\n\t\tUnknownChunk* UnknownChunk::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedChunk = new UnknownChunk(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedChunk);\n\n\t\t\treturn softClonedChunk;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tCookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn CookieReceivedWhileShuttingDownErrorCause::ParseStrict(\n\t\t\t  buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tCookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new CookieReceivedWhileShuttingDownErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t\t  ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tCookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"CookieReceivedWhileShuttingDownErrorCause Length field must be %zu\",\n\t\t\t\t  ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause =\n\t\t\t  new CookieReceivedWhileShuttingDownErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tCookieReceivedWhileShuttingDownErrorCause::CookieReceivedWhileShuttingDownErrorCause(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tCookieReceivedWhileShuttingDownErrorCause::~CookieReceivedWhileShuttingDownErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid CookieReceivedWhileShuttingDownErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::CookieReceivedWhileShuttingDownErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::CookieReceivedWhileShuttingDownErrorCause>\");\n\t\t}\n\n\t\tCookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new CookieReceivedWhileShuttingDownErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tCookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new CookieReceivedWhileShuttingDownErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::InvalidMandatoryParameterErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tInvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn InvalidMandatoryParameterErrorCause::ParseStrict(\n\t\t\t  buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tInvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new InvalidMandatoryParameterErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tInvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"InvalidMandatoryParameterErrorCause Length field must be %zu\",\n\t\t\t\t  ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause =\n\t\t\t  new InvalidMandatoryParameterErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tInvalidMandatoryParameterErrorCause::InvalidMandatoryParameterErrorCause(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tInvalidMandatoryParameterErrorCause::~InvalidMandatoryParameterErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid InvalidMandatoryParameterErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::InvalidMandatoryParameterErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::InvalidMandatoryParameterErrorCause>\");\n\t\t}\n\n\t\tInvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new InvalidMandatoryParameterErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tInvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new InvalidMandatoryParameterErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::InvalidStreamIdentifierErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tInvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn InvalidStreamIdentifierErrorCause::ParseStrict(\n\t\t\t  buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tInvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new InvalidStreamIdentifierErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t\t  InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength);\n\n\t\t\t// Initialize header extra fields.\n\t\t\terrorCause->SetStreamIdentifier(0);\n\t\t\terrorCause->SetReserved();\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tInvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"InvalidStreamIdentifierErrorCause Length field must be %zu\",\n\t\t\t\t  InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause =\n\t\t\t  new InvalidStreamIdentifierErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tInvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCause(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength);\n\t\t}\n\n\t\tInvalidStreamIdentifierErrorCause::~InvalidStreamIdentifierErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid InvalidStreamIdentifierErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::InvalidStreamIdentifierErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream identifier: %\" PRIu16, GetStreamIdentifier());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::InvalidStreamIdentifierErrorCause>\");\n\t\t}\n\n\t\tInvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new InvalidStreamIdentifierErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid InvalidStreamIdentifierErrorCause::SetStreamIdentifier(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tInvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new InvalidStreamIdentifierErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string InvalidStreamIdentifierErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn \"stream:\" + std::to_string(GetStreamIdentifier());\n\t\t}\n\n\t\tvoid InvalidStreamIdentifierErrorCause::SetReserved()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 6, 0);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::MissingMandatoryParameterErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <sstream> // std::ostringstream\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tMissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn MissingMandatoryParameterErrorCause::ParseStrict(\n\t\t\t  buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tMissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new MissingMandatoryParameterErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t\t  MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength);\n\n\t\t\t// Initialize extra fields.\n\t\t\terrorCause->SetNumberOfMissingParameters(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tMissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength < MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"MissingMandatoryParameterErrorCause Length field must be equal or greater than %zu\",\n\t\t\t\t  MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause =\n\t\t\t  new MissingMandatoryParameterErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// In this Chunk we must validate that some fields have correct values.\n\t\t\tif (\n\t\t\t  (errorCause->GetNumberOfMissingParameters() * 2) !=\n\t\t\t  causeLength -\n\t\t\t    MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"wrong values in Number of Missing Parameters field\");\n\n\t\t\t\tdelete errorCause;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tMissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCause(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength);\n\t\t}\n\n\t\tMissingMandatoryParameterErrorCause::~MissingMandatoryParameterErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid MissingMandatoryParameterErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::MissingMandatoryParameterErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  number of missing parameters: %\" PRIu32, GetNumberOfMissingParameters());\n\t\t\tfor (uint32_t idx{ 0 }; idx < GetNumberOfMissingParameters(); ++idx)\n\t\t\t{\n\t\t\t\tconst auto parameterType = GetMissingParameterTypeAt(idx);\n\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  - idx: %\" PRIu32 \", parameter type: %s (%\" PRIu16 \")\",\n\t\t\t\t  idx,\n\t\t\t\t  Parameter::ParameterTypeToString(parameterType).c_str(),\n\t\t\t\t  static_cast<uint16_t>(parameterType));\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::MissingMandatoryParameterErrorCause>\");\n\t\t}\n\n\t\tMissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new MissingMandatoryParameterErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid MissingMandatoryParameterErrorCause::AddMissingParameterType(\n\t\t  Parameter::ParameterType parameterType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(GetVariableLengthValueLength() + 2);\n\n\t\t\t// Add the new missing mandatory parameter type.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(),\n\t\t\t  GetNumberOfMissingParameters() * 2,\n\t\t\t  static_cast<uint16_t>(parameterType));\n\n\t\t\t// Update the counter field.\n\t\t\tSetNumberOfMissingParameters(GetNumberOfMissingParameters() + 1);\n\t\t}\n\n\t\tMissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new MissingMandatoryParameterErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string MissingMandatoryParameterErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::ostringstream missingParameterTypesOss;\n\t\t\tbool firstParameterType = true;\n\n\t\t\tfor (uint32_t idx{ 0 }; idx < GetNumberOfMissingParameters(); ++idx)\n\t\t\t{\n\t\t\t\tconst auto parameterType = GetMissingParameterTypeAt(idx);\n\n\t\t\t\tmissingParameterTypesOss << (firstParameterType ? \"\" : \", \")\n\t\t\t\t                         << RTC::SCTP::Parameter::ParameterTypeToString(parameterType).c_str()\n\t\t\t\t                         << \" (\" << std::to_string(static_cast<uint16_t>(parameterType))\n\t\t\t\t                         << \")\";\n\n\t\t\t\tfirstParameterType = false;\n\t\t\t}\n\n\t\t\treturn \"types:[\" + missingParameterTypesOss.str() + \"]\";\n\t\t}\n\n\t\tvoid MissingMandatoryParameterErrorCause::SetNumberOfMissingParameters(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::NoUserDataErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tNoUserDataErrorCause* NoUserDataErrorCause::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::NO_USER_DATA)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn NoUserDataErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tNoUserDataErrorCause* NoUserDataErrorCause::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new NoUserDataErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t\t  NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength);\n\n\t\t\t// Initialize value.\n\t\t\terrorCause->SetTsn(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tNoUserDataErrorCause* NoUserDataErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"NoUserDataErrorCause Length field must be %zu\",\n\t\t\t\t  NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause = new NoUserDataErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tNoUserDataErrorCause::NoUserDataErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength);\n\t\t}\n\n\t\tNoUserDataErrorCause::~NoUserDataErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid NoUserDataErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::NoUserDataErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tsn: %\" PRIu32, GetTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::NoUserDataErrorCause>\");\n\t\t}\n\n\t\tNoUserDataErrorCause* NoUserDataErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new NoUserDataErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid NoUserDataErrorCause::SetTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tNoUserDataErrorCause* NoUserDataErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new NoUserDataErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string NoUserDataErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn \"tsn:\\\"\" + std::to_string(GetTsn()) + \"\\\"\";\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::OutOfResourceErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tOutOfResourceErrorCause* OutOfResourceErrorCause::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn OutOfResourceErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tOutOfResourceErrorCause* OutOfResourceErrorCause::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new OutOfResourceErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tOutOfResourceErrorCause* OutOfResourceErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"OutOfResourceErrorCause Length field must be %zu\", ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause = new OutOfResourceErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tOutOfResourceErrorCause::OutOfResourceErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tOutOfResourceErrorCause::~OutOfResourceErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid OutOfResourceErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::OutOfResourceErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::OutOfResourceErrorCause>\");\n\t\t}\n\n\t\tOutOfResourceErrorCause* OutOfResourceErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new OutOfResourceErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tOutOfResourceErrorCause* OutOfResourceErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new OutOfResourceErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ProtocolViolationErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tProtocolViolationErrorCause* ProtocolViolationErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ProtocolViolationErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tProtocolViolationErrorCause* ProtocolViolationErrorCause::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new ProtocolViolationErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tProtocolViolationErrorCause* ProtocolViolationErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause = new ProtocolViolationErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tProtocolViolationErrorCause::ProtocolViolationErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tProtocolViolationErrorCause::~ProtocolViolationErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ProtocolViolationErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ProtocolViolationErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  additional information length: %\" PRIu16 \" (has additional information: %s)\",\n\t\t\t  GetAdditionalInformationLength(),\n\t\t\t  HasAdditionalInformation() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ProtocolViolationErrorCause>\");\n\t\t}\n\n\t\tProtocolViolationErrorCause* ProtocolViolationErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new ProtocolViolationErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid ProtocolViolationErrorCause::SetAdditionalInformation(const uint8_t* info, uint16_t infoLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(info, infoLength);\n\t\t}\n\n\t\tvoid ProtocolViolationErrorCause::SetAdditionalInformation(const std::string& info)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(reinterpret_cast<const uint8_t*>(info.c_str()), info.size());\n\t\t}\n\n\t\tProtocolViolationErrorCause* ProtocolViolationErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new ProtocolViolationErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string ProtocolViolationErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (HasAdditionalInformation())\n\t\t\t{\n\t\t\t\treturn \"info::\\\"\" +\n\t\t\t\t       std::string(\n\t\t\t\t         reinterpret_cast<const char*>(GetAdditionalInformation()),\n\t\t\t\t         GetAdditionalInformationLength()) +\n\t\t\t\t       \"\\\"\";\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict(\n\t\t\t  buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new RestartOfAnAssociationWithNewAddressesErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t\t  ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause = new RestartOfAnAssociationWithNewAddressesErrorCause(\n\t\t\t  const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause::RestartOfAnAssociationWithNewAddressesErrorCause(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause::~RestartOfAnAssociationWithNewAddressesErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid RestartOfAnAssociationWithNewAddressesErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::RestartOfAnAssociationWithNewAddressesErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  new address tlvs length: %\" PRIu16 \" (has new address tlvs: %s)\",\n\t\t\t  GetNewAddressTlvsLength(),\n\t\t\t  HasNewAddressTlvs() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::RestartOfAnAssociationWithNewAddressesErrorCause>\");\n\t\t}\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause =\n\t\t\t  new RestartOfAnAssociationWithNewAddressesErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid RestartOfAnAssociationWithNewAddressesErrorCause::SetNewAddressTlvs(\n\t\t  const uint8_t* tlvs, uint16_t tlvsLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(tlvs, tlvsLength);\n\t\t}\n\n\t\tRestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause = new RestartOfAnAssociationWithNewAddressesErrorCause(\n\t\t\t  const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::StaleCookieErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tStaleCookieErrorCause* StaleCookieErrorCause::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::STALE_COOKIE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn StaleCookieErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tStaleCookieErrorCause* StaleCookieErrorCause::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new StaleCookieErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t\t  StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength);\n\n\t\t\t// Initialize value.\n\t\t\terrorCause->SetMeasureOfStaleness(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tStaleCookieErrorCause* StaleCookieErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (causeLength != StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"StaleCookieErrorCause Length field must be %zu\",\n\t\t\t\t  StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* errorCause = new StaleCookieErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tStaleCookieErrorCause::StaleCookieErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength);\n\t\t}\n\n\t\tStaleCookieErrorCause::~StaleCookieErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid StaleCookieErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::StaleCookieErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  measure of staleness: %\" PRIu32, GetMeasureOfStaleness());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::StaleCookieErrorCause>\");\n\t\t}\n\n\t\tStaleCookieErrorCause* StaleCookieErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new StaleCookieErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid StaleCookieErrorCause::SetMeasureOfStaleness(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tStaleCookieErrorCause* StaleCookieErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new StaleCookieErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string StaleCookieErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn \"staleness:\" + std::to_string(GetMeasureOfStaleness());\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/UnknownErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnknownErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnknownErrorCause* UnknownErrorCause::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnknownErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tUnknownErrorCause* UnknownErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause = new UnknownErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnknownErrorCause::UnknownErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tUnknownErrorCause::~UnknownErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnknownErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnknownErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnknownErrorCause>\");\n\t\t}\n\n\t\tUnknownErrorCause* UnknownErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new UnknownErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tUnknownErrorCause* UnknownErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause = new UnknownErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnrecognizedChunkTypeErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnrecognizedChunkTypeErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tUnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new UnrecognizedChunkTypeErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tUnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause =\n\t\t\t  new UnrecognizedChunkTypeErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnrecognizedChunkTypeErrorCause::UnrecognizedChunkTypeErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tUnrecognizedChunkTypeErrorCause::~UnrecognizedChunkTypeErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnrecognizedChunkTypeErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnrecognizedChunkTypeErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  unrecognized chunk length: %\" PRIu16 \" (has unrecognized chunk: %s)\",\n\t\t\t  GetUnrecognizedChunkLength(),\n\t\t\t  HasUnrecognizedChunk() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnrecognizedChunkTypeErrorCause>\");\n\t\t}\n\n\t\tUnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new UnrecognizedChunkTypeErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid UnrecognizedChunkTypeErrorCause::SetUnrecognizedChunk(const uint8_t* chunk, uint16_t chunkLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(chunk, chunkLength);\n\t\t}\n\n\t\tUnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new UnrecognizedChunkTypeErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnrecognizedParametersErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnrecognizedParametersErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tUnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new UnrecognizedParametersErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tUnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause =\n\t\t\t  new UnrecognizedParametersErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnrecognizedParametersErrorCause::UnrecognizedParametersErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tUnrecognizedParametersErrorCause::~UnrecognizedParametersErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnrecognizedParametersErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnrecognizedParametersErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  unrecognized parameters length: %\" PRIu16 \" (has unrecognized parameters: %s)\",\n\t\t\t  GetUnrecognizedParametersLength(),\n\t\t\t  HasUnrecognizedParameters() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnrecognizedParametersErrorCause>\");\n\t\t}\n\n\t\tUnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new UnrecognizedParametersErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid UnrecognizedParametersErrorCause::SetUnrecognizedParameters(\n\t\t  const uint8_t* parameters, uint16_t parametersLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(parameters, parametersLength);\n\t\t}\n\n\t\tUnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new UnrecognizedParametersErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnresolvableAddressErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnresolvableAddressErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tUnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new UnresolvableAddressErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tUnresolvableAddressErrorCause* UnresolvableAddressErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause =\n\t\t\t  new UnresolvableAddressErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnresolvableAddressErrorCause::UnresolvableAddressErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tUnresolvableAddressErrorCause::~UnresolvableAddressErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnresolvableAddressErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnresolvableAddressErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  unresolvable address length: %\" PRIu16 \" (has unresolvable address: %s)\",\n\t\t\t  GetUnresolvableAddressLength(),\n\t\t\t  HasUnresolvableAddress() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnresolvableAddressErrorCause>\");\n\t\t}\n\n\t\tUnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new UnresolvableAddressErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid UnresolvableAddressErrorCause::SetUnresolvableAddress(\n\t\t  const uint8_t* address, uint16_t addressLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(address, addressLength);\n\t\t}\n\n\t\tUnresolvableAddressErrorCause* UnresolvableAddressErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new UnresolvableAddressErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UserInitiatedAbortErrorCause\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tErrorCause::ErrorCauseCode causeCode;\n\t\t\tuint16_t causeLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (causeCode != ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Error Cause code\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UserInitiatedAbortErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding);\n\t\t}\n\n\t\tUserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ErrorCause::ErrorCauseHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* errorCause = new UserInitiatedAbortErrorCause(buffer, bufferLength);\n\n\t\t\terrorCause->InitializeHeader(\n\t\t\t  ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, ErrorCause::ErrorCauseHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\tUserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* errorCause = new UserInitiatedAbortErrorCause(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\terrorCause->SetLength(causeLength + padding);\n\n\t\t\treturn errorCause;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUserInitiatedAbortErrorCause::UserInitiatedAbortErrorCause(uint8_t* buffer, size_t bufferLength)\n\t\t  : ErrorCause(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ErrorCause::ErrorCauseHeaderLength);\n\t\t}\n\n\t\tUserInitiatedAbortErrorCause::~UserInitiatedAbortErrorCause()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UserInitiatedAbortErrorCause::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UserInitiatedAbortErrorCause>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tif (HasUpperLayerAbortReason())\n\t\t\t{\n\t\t\t\tconst auto reason = GetUpperLayerAbortReason();\n\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  has upper layer abort reason: yes\");\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  upper layer abort reason: \\\"%.*s\\\"\",\n\t\t\t\t  static_cast<int>(reason.size()),\n\t\t\t\t  reason.data());\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  has upper layer abort reason: no\");\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UserInitiatedAbortErrorCause>\");\n\t\t}\n\n\t\tUserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedErrorCause = new UserInitiatedAbortErrorCause(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedErrorCause);\n\n\t\t\treturn clonedErrorCause;\n\t\t}\n\n\t\tvoid UserInitiatedAbortErrorCause::SetUpperLayerAbortReason(const std::string_view& reason)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(\n\t\t\t  reinterpret_cast<const uint8_t*>(reason.data()), static_cast<uint16_t>(reason.size()));\n\t\t}\n\n\t\tUserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedErrorCause =\n\t\t\t  new UserInitiatedAbortErrorCause(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedErrorCause);\n\n\t\t\treturn softClonedErrorCause;\n\t\t}\n\n\t\tconst std::string UserInitiatedAbortErrorCause::ContentToString() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (HasUpperLayerAbortReason())\n\t\t\t{\n\t\t\t\treturn \"reason::\\\"\" + std::string(GetUpperLayerAbortReason()) + \"\\\"\";\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn \"\";\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::AddIncomingStreamsRequestParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tAddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn AddIncomingStreamsRequestParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tAddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new AddIncomingStreamsRequestParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t\t  AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength);\n\n\t\t\t// Initialize header extra fields to zero.\n\t\t\tparameter->SetReconfigurationRequestSequenceNumber(0);\n\t\t\tparameter->SetNumberOfNewStreams(0);\n\t\t\tparameter->SetReserved();\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tAddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"AddIncomingStreamsRequestParameter Length field must be %zu\",\n\t\t\t\t  AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new AddIncomingStreamsRequestParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tAddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameter(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength);\n\t\t}\n\n\t\tAddIncomingStreamsRequestParameter::~AddIncomingStreamsRequestParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid AddIncomingStreamsRequestParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::AddIncomingStreamsRequestParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration request sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationRequestSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of new streams: %\" PRIu16, GetNumberOfNewStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::AddIncomingStreamsRequestParameter>\");\n\t\t}\n\n\t\tAddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new AddIncomingStreamsRequestParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid AddIncomingStreamsRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid AddIncomingStreamsRequestParameter::SetNumberOfNewStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tAddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new AddIncomingStreamsRequestParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\n\t\tvoid AddIncomingStreamsRequestParameter::SetReserved()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 10, 0);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::AddOutgoingStreamsRequestParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tAddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn AddOutgoingStreamsRequestParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tAddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new AddOutgoingStreamsRequestParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t\t  AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength);\n\n\t\t\t// Initialize header extra fields to zero.\n\t\t\tparameter->SetReconfigurationRequestSequenceNumber(0);\n\t\t\tparameter->SetNumberOfNewStreams(0);\n\t\t\tparameter->SetReserved();\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tAddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"AddOutgoingStreamsRequestParameter Length field must be %zu\",\n\t\t\t\t  AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new AddOutgoingStreamsRequestParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tAddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameter(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength);\n\t\t}\n\n\t\tAddOutgoingStreamsRequestParameter::~AddOutgoingStreamsRequestParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid AddOutgoingStreamsRequestParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::AddOutgoingStreamsRequestParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration request sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationRequestSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of new streams: %\" PRIu16, GetNumberOfNewStreams());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::AddOutgoingStreamsRequestParameter>\");\n\t\t}\n\n\t\tAddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new AddOutgoingStreamsRequestParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid AddOutgoingStreamsRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid AddOutgoingStreamsRequestParameter::SetNumberOfNewStreams(uint16_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tAddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new AddOutgoingStreamsRequestParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\n\t\tvoid AddOutgoingStreamsRequestParameter::SetReserved()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set2Bytes(const_cast<uint8_t*>(GetBuffer()), 10, 0);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/CookiePreservativeParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::CookiePreservativeParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tCookiePreservativeParameter* CookiePreservativeParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::COOKIE_PRESERVATIVE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn CookiePreservativeParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tCookiePreservativeParameter* CookiePreservativeParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < CookiePreservativeParameter::CookiePreservativeParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new CookiePreservativeParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t\t  CookiePreservativeParameter::CookiePreservativeParameterHeaderLength);\n\n\t\t\t// Must also initialize the value.\n\t\t\tparameter->SetLifeSpanIncrement(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tCookiePreservativeParameter* CookiePreservativeParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != CookiePreservativeParameter::CookiePreservativeParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"CookiePreservativeParameter Length field must be %zu\",\n\t\t\t\t  CookiePreservativeParameter::CookiePreservativeParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter = new CookiePreservativeParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tCookiePreservativeParameter::CookiePreservativeParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(CookiePreservativeParameter::CookiePreservativeParameterHeaderLength);\n\t\t}\n\n\t\tCookiePreservativeParameter::~CookiePreservativeParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid CookiePreservativeParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::CookiePreservativeParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  suggested cookie life-span increment: %\" PRIu32, GetLifeSpanIncrement());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::CookiePreservativeParameter>\");\n\t\t}\n\n\t\tCookiePreservativeParameter* CookiePreservativeParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new CookiePreservativeParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid CookiePreservativeParameter::SetLifeSpanIncrement(uint32_t increment)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, increment);\n\t\t}\n\n\t\tCookiePreservativeParameter* CookiePreservativeParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new CookiePreservativeParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ForwardTsnSupportedParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::FORWARD_TSN_SUPPORTED)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ForwardTsnSupportedParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new ForwardTsnSupportedParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::FORWARD_TSN_SUPPORTED, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tForwardTsnSupportedParameter* ForwardTsnSupportedParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"ForwardTsnSupportedParameter Length field must be %zu\",\n\t\t\t\t  Parameter::ParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter = new ForwardTsnSupportedParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tForwardTsnSupportedParameter::ForwardTsnSupportedParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tForwardTsnSupportedParameter::~ForwardTsnSupportedParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ForwardTsnSupportedParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ForwardTsnSupportedParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ForwardTsnSupportedParameter>\");\n\t\t}\n\n\t\tForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new ForwardTsnSupportedParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tForwardTsnSupportedParameter* ForwardTsnSupportedParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new ForwardTsnSupportedParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::HeartbeatInfoParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tHeartbeatInfoParameter* HeartbeatInfoParameter::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::HEARTBEAT_INFO)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn HeartbeatInfoParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tHeartbeatInfoParameter* HeartbeatInfoParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new HeartbeatInfoParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::HEARTBEAT_INFO, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tHeartbeatInfoParameter* HeartbeatInfoParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter = new HeartbeatInfoParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tHeartbeatInfoParameter::HeartbeatInfoParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tHeartbeatInfoParameter::~HeartbeatInfoParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid HeartbeatInfoParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::HeartbeatInfoParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  info length: %\" PRIu16 \" (has info: %s)\",\n\t\t\t  GetInfoLength(),\n\t\t\t  HasInfo() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::HeartbeatInfoParameter>\");\n\t\t}\n\n\t\tHeartbeatInfoParameter* HeartbeatInfoParameter::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new HeartbeatInfoParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid HeartbeatInfoParameter::SetInfo(const uint8_t* info, uint16_t infoLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(info, infoLength);\n\t\t}\n\n\t\tHeartbeatInfoParameter* HeartbeatInfoParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new HeartbeatInfoParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/IPv4AddressParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::IPv4AddressParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <uv.h>\n#include <cstring> // std::memset(), std::memmove()\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tIPv4AddressParameter* IPv4AddressParameter::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::IPV4_ADDRESS)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn IPv4AddressParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tIPv4AddressParameter* IPv4AddressParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < IPv4AddressParameter::IPv4AddressParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new IPv4AddressParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::IPV4_ADDRESS,\n\t\t\t  IPv4AddressParameter::IPv4AddressParameterHeaderLength);\n\n\t\t\t// Must also initialize the IPv4 field to zero.\n\t\t\tparameter->ResetIPv4Address();\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tIPv4AddressParameter* IPv4AddressParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != IPv4AddressParameter::IPv4AddressParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"IPv4AddressParameter Length field must be %zu\",\n\t\t\t\t  IPv4AddressParameter::IPv4AddressParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter = new IPv4AddressParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIPv4AddressParameter::IPv4AddressParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(IPv4AddressParameter::IPv4AddressParameterHeaderLength);\n\t\t}\n\n\t\tIPv4AddressParameter::~IPv4AddressParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid IPv4AddressParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tchar ipStr[INET_ADDRSTRLEN] = { 0 };\n\n\t\t\tuv_inet_ntop(AF_INET, GetIPv4Address(), ipStr, sizeof(ipStr));\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::IPv4AddressParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ipv4 address: %s\", ipStr);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::IPv4AddressParameter>\");\n\t\t}\n\n\t\tIPv4AddressParameter* IPv4AddressParameter::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new IPv4AddressParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid IPv4AddressParameter::SetIPv4Address(const uint8_t* ip)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memmove(const_cast<uint8_t*>(GetBuffer()) + 4, ip, 4);\n\t\t}\n\n\t\tIPv4AddressParameter* IPv4AddressParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter = new IPv4AddressParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\n\t\tvoid IPv4AddressParameter::ResetIPv4Address()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memset(const_cast<uint8_t*>(GetBuffer()) + 4, 0x00, 4);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/IPv6AddressParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::IPv6AddressParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <uv.h>\n#include <cstring> // std::memset(), std::memmove()\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tIPv6AddressParameter* IPv6AddressParameter::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::IPV6_ADDRESS)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn IPv6AddressParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tIPv6AddressParameter* IPv6AddressParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < IPv6AddressParameter::IPv6AddressParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new IPv6AddressParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::IPV6_ADDRESS,\n\t\t\t  IPv6AddressParameter::IPv6AddressParameterHeaderLength);\n\n\t\t\t// Must also initialize the IPv6 field to zero.\n\t\t\tparameter->ResetIPv6Address();\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tIPv6AddressParameter* IPv6AddressParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != IPv6AddressParameter::IPv6AddressParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"IPv6AddressParameter Length field must be %zu\",\n\t\t\t\t  IPv6AddressParameter::IPv6AddressParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter = new IPv6AddressParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIPv6AddressParameter::IPv6AddressParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(IPv6AddressParameter::IPv6AddressParameterHeaderLength);\n\t\t}\n\n\t\tIPv6AddressParameter::~IPv6AddressParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid IPv6AddressParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tchar ipStr[INET6_ADDRSTRLEN] = { 0 };\n\n\t\t\tuv_inet_ntop(AF_INET6, GetIPv6Address(), ipStr, sizeof(ipStr));\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::IPv6AddressParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ipv6 address: %s\", ipStr);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::IPv6AddressParameter>\");\n\t\t}\n\n\t\tIPv6AddressParameter* IPv6AddressParameter::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new IPv6AddressParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid IPv6AddressParameter::SetIPv6Address(const uint8_t* ip)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memmove(const_cast<uint8_t*>(GetBuffer()) + 4, ip, 16);\n\t\t}\n\n\t\tIPv6AddressParameter* IPv6AddressParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter = new IPv6AddressParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\n\t\tvoid IPv6AddressParameter::ResetIPv6Address()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::memset(const_cast<uint8_t*>(GetBuffer()) + 4, 0x00, 16);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::IncomingSsnResetRequestParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tIncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn IncomingSsnResetRequestParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tIncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new IncomingSsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t\t  IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tparameter->SetReconfigurationRequestSequenceNumber(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tIncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength < IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"IncomingSsnResetRequestParameter Length field must be equal or greater than %zu\",\n\t\t\t\t  IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new IncomingSsnResetRequestParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tIncomingSsnResetRequestParameter::IncomingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength);\n\t\t}\n\n\t\tIncomingSsnResetRequestParameter::~IncomingSsnResetRequestParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid IncomingSsnResetRequestParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::IncomingSsnResetRequestParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration request sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationRequestSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream ids:\");\n\t\t\tfor (const uint16_t streamId : GetStreamIds())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  - stream id: %\" PRIu16, streamId);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::IncomingSsnResetRequestParameter>\");\n\t\t}\n\n\t\tIncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new IncomingSsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid IncomingSsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tstd::vector<uint16_t> IncomingSsnResetRequestParameter::GetStreamIds() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t numberOfStreams = GetNumberOfStreams();\n\t\t\tstd::vector<uint16_t> streamIds;\n\n\t\t\tstreamIds.reserve(numberOfStreams);\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfStreams; ++idx)\n\t\t\t{\n\t\t\t\tstreamIds.emplace_back(GetStreamAt(idx));\n\t\t\t}\n\n\t\t\treturn streamIds;\n\t\t}\n\n\t\tvoid IncomingSsnResetRequestParameter::AddStreamId(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousVariableLengthValueLength = GetVariableLengthValueLength();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(previousVariableLengthValueLength + 2);\n\n\t\t\t// Add the new stream.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength, streamId);\n\t\t}\n\n\t\tIncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new IncomingSsnResetRequestParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::OutgoingSsnResetRequestParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tOutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn OutgoingSsnResetRequestParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tOutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new OutgoingSsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t\t  OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tparameter->SetReconfigurationRequestSequenceNumber(0);\n\t\t\tparameter->SetReconfigurationResponseSequenceNumber(0);\n\t\t\tparameter->SetSenderLastAssignedTsn(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tOutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength < OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"OutgoingSsnResetRequestParameter Length field must be equal or greater than %zu\",\n\t\t\t\t  OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new OutgoingSsnResetRequestParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tOutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength);\n\t\t}\n\n\t\tOutgoingSsnResetRequestParameter::~OutgoingSsnResetRequestParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid OutgoingSsnResetRequestParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::OutgoingSsnResetRequestParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration request sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationRequestSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration response sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationResponseSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  sender last assigned tsn: %\" PRIu32, GetSenderLastAssignedTsn());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream ids:\");\n\t\t\tfor (const uint16_t streamId : GetStreamIds())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  - stream id: %\" PRIu16, streamId);\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::OutgoingSsnResetRequestParameter>\");\n\t\t}\n\n\t\tOutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new OutgoingSsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid OutgoingSsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid OutgoingSsnResetRequestParameter::SetReconfigurationResponseSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 8, value);\n\t\t}\n\n\t\tvoid OutgoingSsnResetRequestParameter::SetSenderLastAssignedTsn(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 12, value);\n\t\t}\n\n\t\tstd::vector<uint16_t> OutgoingSsnResetRequestParameter::GetStreamIds() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst uint16_t numberOfStreams = GetNumberOfStreams();\n\t\t\tstd::vector<uint16_t> streamIds;\n\n\t\t\tstreamIds.reserve(numberOfStreams);\n\n\t\t\tfor (uint16_t idx{ 0 }; idx < numberOfStreams; ++idx)\n\t\t\t{\n\t\t\t\tstreamIds.emplace_back(GetStreamAt(idx));\n\t\t\t}\n\n\t\t\treturn streamIds;\n\t\t}\n\n\t\tvoid OutgoingSsnResetRequestParameter::AddStreamId(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto previousVariableLengthValueLength = GetVariableLengthValueLength();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(previousVariableLengthValueLength + 2);\n\n\t\t\t// Add the new stream.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousVariableLengthValueLength, streamId);\n\t\t}\n\n\t\tOutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new OutgoingSsnResetRequestParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ReconfigurationResponseParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst std::unordered_map<ReconfigurationResponseParameter::Result, std::string> ReconfigurationResponseParameter::Result2String =\n\t\t{\n\t\t\t{ ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO,             \"SUCCESS_NOTHING_TO_DO\"             },\n\t\t\t{ ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED,                 \"SUCCESS_PERFORMED\"                 },\n\t\t\t{ ReconfigurationResponseParameter::Result::DENIED,                            \"DENIED\"                            },\n\t\t\t{ ReconfigurationResponseParameter::Result::ERROR_WRONG_SSN,                   \"ERROR_WRONG_SSN\"                   },\n\t\t\t{ ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS, \"ERROR_REQUEST_ALREADY_IN_PROGRESS\" },\n\t\t\t{ ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER,         \"ERROR_BAD_SEQUENCE_NUMBER\"         },\n\t\t\t{ ReconfigurationResponseParameter::Result::IN_PROGRESS,                       \"IN_PROGRESS\"                       },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tReconfigurationResponseParameter* ReconfigurationResponseParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::RECONFIGURATION_RESPONSE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ReconfigurationResponseParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tReconfigurationResponseParameter* ReconfigurationResponseParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new ReconfigurationResponseParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t\t  ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength);\n\n\t\t\t// Must also initialize extra fields in the header.\n\t\t\tparameter->SetReconfigurationResponseSequenceNumber(0);\n\t\t\tparameter->SetResult(static_cast<Result>(0));\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tconst std::string& ReconfigurationResponseParameter::ResultToString(\n\t\t  ReconfigurationResponseParameter::Result result)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = ReconfigurationResponseParameter::Result2String.find(result);\n\n\t\t\tif (it == ReconfigurationResponseParameter::Result2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\tReconfigurationResponseParameter* ReconfigurationResponseParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (\n\t\t\t  parameterLength !=\n\t\t\t    ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength &&\n\t\t\t  parameterLength !=\n\t\t\t    ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"ReconfigurationResponseParameter Length field must be %zu or %zu\",\n\t\t\t\t  ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength,\n\t\t\t\t  ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new ReconfigurationResponseParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tReconfigurationResponseParameter::ReconfigurationResponseParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength);\n\t\t}\n\n\t\tReconfigurationResponseParameter::~ReconfigurationResponseParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ReconfigurationResponseParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ReconfigurationResponseParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration response sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationResponseSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  result: %\" PRIu32 \" (%s)\",\n\t\t\t  static_cast<uint32_t>(GetResult()),\n\t\t\t  ReconfigurationResponseParameter::ResultToString(GetResult()).c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  has next tsns: %s\", HasNextTsns() ? \"yes\" : \"no\");\n\t\t\tif (HasNextTsns())\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  sender next tsn: %\" PRIu32, GetSenderNextTsn());\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  receiver next tsn: %\" PRIu32, GetReceiverNextTsn());\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ReconfigurationResponseParameter>\");\n\t\t}\n\n\t\tReconfigurationResponseParameter* ReconfigurationResponseParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new ReconfigurationResponseParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid ReconfigurationResponseParameter::SetReconfigurationResponseSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tvoid ReconfigurationResponseParameter::SetResult(Result result)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 8, static_cast<uint32_t>(result));\n\t\t}\n\n\t\tvoid ReconfigurationResponseParameter::SetNextTsns(uint32_t senderNextTsn, uint32_t receiverNextTsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!HasNextTsns())\n\t\t\t{\n\t\t\t\t// This may throw.\n\t\t\t\tSetVariableLengthValueLength(\n\t\t\t\t  ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields -\n\t\t\t\t  ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength);\n\t\t\t}\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 12, senderNextTsn);\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 16, receiverNextTsn);\n\t\t}\n\n\t\tReconfigurationResponseParameter* ReconfigurationResponseParameter::SoftClone(\n\t\t  const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new ReconfigurationResponseParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::SsnTsnResetRequestParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tSsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::SSN_TSN_RESET_REQUEST)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn SsnTsnResetRequestParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tSsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new SsnTsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t\t  SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength);\n\n\t\t\t// Initialize header extra fields to zero.\n\t\t\tparameter->SetReconfigurationRequestSequenceNumber(0);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tSsnTsnResetRequestParameter* SsnTsnResetRequestParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"SsnTsnResetRequestParameter Length field must be %zu\",\n\t\t\t\t  SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter = new SsnTsnResetRequestParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tSsnTsnResetRequestParameter::SsnTsnResetRequestParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength);\n\t\t}\n\n\t\tSsnTsnResetRequestParameter::~SsnTsnResetRequestParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid SsnTsnResetRequestParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::SsnTsnResetRequestParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  re-configuration request sequence number: %\" PRIu32,\n\t\t\t  GetReconfigurationRequestSequenceNumber());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::SsnTsnResetRequestParameter>\");\n\t\t}\n\n\t\tSsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new SsnTsnResetRequestParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid SsnTsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(const_cast<uint8_t*>(GetBuffer()), 4, value);\n\t\t}\n\n\t\tSsnTsnResetRequestParameter* SsnTsnResetRequestParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new SsnTsnResetRequestParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/StateCookieParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::StateCookieParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/StateCookieParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tStateCookieParameter* StateCookieParameter::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::STATE_COOKIE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn StateCookieParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tStateCookieParameter* StateCookieParameter::Factory(uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new StateCookieParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::STATE_COOKIE, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tStateCookieParameter* StateCookieParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter = new StateCookieParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tStateCookieParameter::StateCookieParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tStateCookieParameter::~StateCookieParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid StateCookieParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::StateCookieParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  cookie length: %\" PRIu16 \" (has cookie: %s)\",\n\t\t\t  GetCookieLength(),\n\t\t\t  HasCookie() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::StateCookieParameter>\");\n\t\t}\n\n\t\tStateCookieParameter* StateCookieParameter::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new StateCookieParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid StateCookieParameter::SetCookie(const uint8_t* cookie, uint16_t cookieLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(cookie, cookieLength);\n\t\t}\n\n\t\tvoid StateCookieParameter::WriteStateCookieInPlace(\n\t\t  uint32_t localVerificationTag,\n\t\t  uint32_t remoteVerificationTag,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  uint64_t tieTag,\n\t\t  const NegotiatedCapabilities& negotiatedCapabilities)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// The buffer in which the StateCookie will be written starts at the\n\t\t\t// position of the Cookie field in the StateCookieParameter.\n\t\t\tauto* buffer = GetVariableLengthValuePointer();\n\t\t\t// The available buffer length is the total buffer length of the\n\t\t\t// StateCookieParameter minus its fixed header length (no matter there\n\t\t\t// was a Cookie already in the Parameter since we are overriding it\n\t\t\t// anyway).\n\t\t\tconst size_t bufferLength = GetBufferLength() - Parameter::ParameterHeaderLength;\n\n\t\t\tStateCookie::Write(\n\t\t\t  buffer,\n\t\t\t  bufferLength,\n\t\t\t  localVerificationTag,\n\t\t\t  remoteVerificationTag,\n\t\t\t  localInitialTsn,\n\t\t\t  remoteInitialTsn,\n\t\t\t  remoteAdvertisedReceiverWindowCredit,\n\t\t\t  tieTag,\n\t\t\t  negotiatedCapabilities);\n\n\t\t\tSetVariableLengthValueLength(StateCookie::StateCookieLength);\n\t\t}\n\n\t\tStateCookieParameter* StateCookieParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter = new StateCookieParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::SupportedAddressTypesParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tSupportedAddressTypesParameter* SupportedAddressTypesParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn SupportedAddressTypesParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tSupportedAddressTypesParameter* SupportedAddressTypesParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new SupportedAddressTypesParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tSupportedAddressTypesParameter* SupportedAddressTypesParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter =\n\t\t\t  new SupportedAddressTypesParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Here we must validate that Length field is even.\n\t\t\tif (parameter->GetLengthField() % 2 != 0)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(sctp, \"wrong Length value (not even)\");\n\n\t\t\t\tdelete parameter;\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tSupportedAddressTypesParameter::SupportedAddressTypesParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tSupportedAddressTypesParameter::~SupportedAddressTypesParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid SupportedAddressTypesParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::SupportedAddressTypesParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of address types: %\" PRIu16, GetNumberOfAddressTypes());\n\t\t\tfor (uint32_t idx{ 0 }; idx < GetNumberOfAddressTypes(); ++idx)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation, \"  - idx: %\" PRIu16 \", address type: %\" PRIu16, idx, GetAddressTypeAt(idx));\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::SupportedAddressTypesParameter>\");\n\t\t}\n\n\t\tSupportedAddressTypesParameter* SupportedAddressTypesParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new SupportedAddressTypesParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid SupportedAddressTypesParameter::AddAddressType(uint16_t addressType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// We must save previous count since SetVariableLengthValueLength() will\n\t\t\t// make GetNumberOfAddressTypes() return a different value.\n\t\t\tauto previousNumberOfAddressTypes = GetNumberOfAddressTypes();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(GetVariableLengthValueLength() + 2);\n\n\t\t\t// Add the new missing mandatory parameter type.\n\t\t\tUtils::Byte::Set2Bytes(\n\t\t\t  GetVariableLengthValuePointer(), previousNumberOfAddressTypes * 2, addressType);\n\t\t}\n\n\t\tSupportedAddressTypesParameter* SupportedAddressTypesParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new SupportedAddressTypesParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::SupportedExtensionsParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tSupportedExtensionsParameter* SupportedExtensionsParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::SUPPORTED_EXTENSIONS)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn SupportedExtensionsParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tSupportedExtensionsParameter* SupportedExtensionsParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new SupportedExtensionsParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::SUPPORTED_EXTENSIONS, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tSupportedExtensionsParameter* SupportedExtensionsParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter = new SupportedExtensionsParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tSupportedExtensionsParameter::SupportedExtensionsParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tSupportedExtensionsParameter::~SupportedExtensionsParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid SupportedExtensionsParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::SupportedExtensionsParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  number of chunk types: %\" PRIu16, GetNumberOfChunkTypes());\n\t\t\tfor (uint32_t idx{ 0 }; idx < GetNumberOfChunkTypes(); ++idx)\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(\n\t\t\t\t  indentation,\n\t\t\t\t  \"  - idx: %\" PRIu16 \", chunk type: %\" PRIu8 \" (%s)\",\n\t\t\t\t  idx,\n\t\t\t\t  static_cast<uint8_t>(GetChunkTypeAt(idx)),\n\t\t\t\t  Chunk::ChunkTypeToString(GetChunkTypeAt(idx)).c_str());\n\t\t\t}\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::SupportedExtensionsParameter>\");\n\t\t}\n\n\t\tSupportedExtensionsParameter* SupportedExtensionsParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new SupportedExtensionsParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid SupportedExtensionsParameter::AddChunkType(Chunk::ChunkType chunkType)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// We must save previous count since SetVariableLengthValueLength() will\n\t\t\t// make GetNumberOfChunkTypes() return a different value.\n\t\t\tauto previousNumberOfChunkTypes = GetNumberOfChunkTypes();\n\n\t\t\t// NOTE: This may throw.\n\t\t\tSetVariableLengthValueLength(GetVariableLengthValueLength() + 1);\n\n\t\t\t// Add the new missing mandatory parameter type.\n\t\t\tUtils::Byte::Set1Byte(\n\t\t\t  GetVariableLengthValuePointer(), previousNumberOfChunkTypes, static_cast<uint8_t>(chunkType));\n\t\t}\n\n\t\tSupportedExtensionsParameter* SupportedExtensionsParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new SupportedExtensionsParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/UnknownParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnknownParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/UnknownParameter.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnknownParameter* UnknownParameter::Parse(const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnknownParameter::ParseStrict(buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tUnknownParameter* UnknownParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter = new UnknownParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnknownParameter::UnknownParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tUnknownParameter::~UnknownParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnknownParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnknownParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnknownParameter>\");\n\t\t}\n\n\t\tUnknownParameter* UnknownParameter::Clone(uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new UnknownParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tUnknownParameter* UnknownParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter = new UnknownParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::UnrecognizedParameterParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class methods. */\n\n\t\tUnrecognizedParameterParameter* UnrecognizedParameterParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::UNRECOGNIZED_PARAMETER)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn UnrecognizedParameterParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tUnrecognizedParameterParameter* UnrecognizedParameterParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < Parameter::ParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new UnrecognizedParameterParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::UNRECOGNIZED_PARAMETER, Parameter::ParameterHeaderLength);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tUnrecognizedParameterParameter* UnrecognizedParameterParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* parameter =\n\t\t\t  new UnrecognizedParameterParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\t// Must always invoke SetLength() after constructing a Serializable with\n\t\t\t// not fixed length.\n\t\t\tparameter->SetLength(parameterLength + padding);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tUnrecognizedParameterParameter::UnrecognizedParameterParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(Parameter::ParameterHeaderLength);\n\t\t}\n\n\t\tUnrecognizedParameterParameter::~UnrecognizedParameterParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid UnrecognizedParameterParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::UnrecognizedParameterParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  unrecognized parameter length: %\" PRIu16 \" (has unrecognized parameter: %s)\",\n\t\t\t  GetUnrecognizedParameterLength(),\n\t\t\t  HasUnrecognizedParameter() ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::UnrecognizedParameterParameter>\");\n\t\t}\n\n\t\tUnrecognizedParameterParameter* UnrecognizedParameterParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new UnrecognizedParameterParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid UnrecognizedParameterParameter::SetUnrecognizedParameter(\n\t\t  const uint8_t* parameter, uint16_t parameterLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetVariableLengthValue(parameter, parameterLength);\n\t\t}\n\n\t\tUnrecognizedParameterParameter* UnrecognizedParameterParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new UnrecognizedParameterParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ZeroChecksumAcceptableParameter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Class variables. */\n\n\t\t// clang-format off\n\t\tconst std::unordered_map<ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod, std::string> ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String =\n\t\t{\n\t\t\t{ ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE,           \"NONE\"           },\n\t\t\t{ ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS, \"SCTP_OVER_DTLS\" },\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Class methods. */\n\n\t\tZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Parse(\n\t\t  const uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tParameter::ParameterType parameterType;\n\t\t\tuint16_t parameterLength;\n\t\t\tuint8_t padding;\n\n\t\t\tif (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding))\n\t\t\t{\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tif (parameterType != Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"invalid Parameter type\");\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\treturn ZeroChecksumAcceptableParameter::ParseStrict(\n\t\t\t  buffer, bufferLength, parameterLength, padding);\n\t\t}\n\n\t\tZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Factory(\n\t\t  uint8_t* buffer, size_t bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (bufferLength < ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"buffer too small\");\n\t\t\t}\n\n\t\t\tauto* parameter = new ZeroChecksumAcceptableParameter(buffer, bufferLength);\n\n\t\t\tparameter->InitializeHeader(\n\t\t\t  Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t\t  ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength);\n\n\t\t\t// Initialize Alternate Error Detection Method (EDMID) to zero (none).\n\t\t\tparameter->SetAlternateErrorDetectionMethod(AlternateErrorDetectionMethod::NONE);\n\n\t\t\t// No need to invoke SetLength() since parent constructor invoked it.\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::ParseStrict(\n\t\t  const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (parameterLength != ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"ZeroChecksumAcceptableParameter Length field must be %zu\",\n\t\t\t\t  ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength);\n\n\t\t\t\treturn nullptr;\n\t\t\t}\n\n\t\t\tauto* parameter =\n\t\t\t  new ZeroChecksumAcceptableParameter(const_cast<uint8_t*>(buffer), bufferLength);\n\n\t\t\treturn parameter;\n\t\t}\n\n\t\tconst std::string& ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethodToString(\n\t\t  AlternateErrorDetectionMethod alternateErrorDetectionMethod)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstatic const std::string Unknown(\"UNKNOWN\");\n\n\t\t\tauto it = ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String.find(\n\t\t\t  alternateErrorDetectionMethod);\n\n\t\t\tif (it == ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String.end())\n\t\t\t{\n\t\t\t\treturn Unknown;\n\t\t\t}\n\n\t\t\treturn it->second;\n\t\t}\n\n\t\t/* Instance methods. */\n\n\t\tZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameter(uint8_t* buffer, size_t bufferLength)\n\t\t  : Parameter(buffer, bufferLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tSetLength(ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength);\n\t\t}\n\n\t\tZeroChecksumAcceptableParameter::~ZeroChecksumAcceptableParameter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ZeroChecksumAcceptableParameter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::ZeroChecksumAcceptableParameter>\");\n\t\t\tDumpCommon(indentation);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  alternate error detection method: %\" PRIu32 \" (%s)\",\n\t\t\t  static_cast<uint32_t>(GetAlternateErrorDetectionMethod()),\n\t\t\t  ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethodToString(\n\t\t\t    GetAlternateErrorDetectionMethod())\n\t\t\t    .c_str());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::ZeroChecksumAcceptableParameter>\");\n\t\t}\n\n\t\tZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Clone(\n\t\t  uint8_t* buffer, size_t bufferLength) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* clonedParameter = new ZeroChecksumAcceptableParameter(buffer, bufferLength);\n\n\t\t\tCloneInto(clonedParameter);\n\n\t\t\treturn clonedParameter;\n\t\t}\n\n\t\tvoid ZeroChecksumAcceptableParameter::SetAlternateErrorDetectionMethod(\n\t\t  AlternateErrorDetectionMethod alternateErrorDetectionMethod)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUtils::Byte::Set4Bytes(\n\t\t\t  const_cast<uint8_t*>(GetBuffer()), 4, static_cast<uint32_t>(alternateErrorDetectionMethod));\n\t\t}\n\n\t\tZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::SoftClone(const uint8_t* buffer) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto* softClonedParameter =\n\t\t\t  new ZeroChecksumAcceptableParameter(const_cast<uint8_t*>(buffer), GetLength());\n\n\t\t\tSoftCloneInto(softClonedParameter);\n\n\t\t\treturn softClonedParameter;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/public/AssociationMetrics.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::AssociationMetrics\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/public/AssociationMetrics.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tvoid AssociationMetrics::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto peerImplementationStringView = Types::SctpImplementationToString(this->peerImplementation);\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::AssociationMetrics>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tx packets count: %\" PRIu64, this->txPacketsCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  tx messages count: %\" PRIu64, this->txMessagesCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rx packets count: %\" PRIu64, this->rxPacketsCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rx messages count: %\" PRIu64, this->rxMessagesCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rtx packets count: %\" PRIu64, this->rtxPacketsCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rtx bytes count: %\" PRIu64, this->rtxBytesCount);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  current congestion window (bytes): %zu\", this->cwndBytes);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  smoothed round trip time (ms): %\" PRIu64, this->srttMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  unacked data count: %zu\", this->unackDataCount);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  peer's last announced receiver window size: %\" PRIu32, this->peerRwndBytes);\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  peer implementation: %.*s\",\n\t\t\t  static_cast<int>(peerImplementationStringView.size()),\n\t\t\t  peerImplementationStringView.data());\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  uses partial reliability: %s\", this->usesPartialReliability ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation, \"  uses message interleaving: %s\", this->usesMessageInterleaving ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  uses re-config: %s\", this->usesReConfig ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  uses zero checksum: %s\", this->usesZeroChecksum ? \"yes\" : \"no\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::AssociationMetrics>\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/public/Message.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::Message\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tMessage::Message(uint16_t streamId, uint32_t ppid, std::vector<uint8_t> payload)\n\t\t  : streamId(streamId), ppid(ppid), payload(std::move(payload))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tMessage::~Message()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid Message::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::Message>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  stream id: %\" PRIu16, GetStreamId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  ppid: %\" PRIu32, GetPayloadProtocolId());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  payload length: %zu\", GetPayloadLength());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::Message>\");\n\t\t}\n\n\t\tvoid Message::SetStreamId(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->streamId = streamId;\n\t\t\t;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/rx/DataTracker.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::DataTracker\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/rx/DataTracker.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include <ranges>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tDataTracker::DataTracker(BackoffTimerHandleInterface* delayedAckTimer, uint32_t remoteInitialTsn)\n\t\t  : delayedAckTimer(delayedAckTimer),\n\t\t    lastCumulativeAckedTsn(this->tsnUnwrapper.Unwrap(remoteInitialTsn - 1))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tbool DataTracker::IsTsnValid(uint32_t tsn) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Note that this method doesn't return `false` for old DATA/I-DATA chunks,\n\t\t\t// as those are actually valid, and receiving those may affect the\n\t\t\t// generated SACK response (by setting \"duplicate TSNs\").\n\n\t\t\tconst Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.PeekUnwrap(tsn);\n\t\t\tconst uint32_t difference =\n\t\t\t  Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn);\n\n\t\t\tif (difference > DataTracker::MaxAcceptedOutstandingFragments)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool DataTracker::Observe(uint32_t tsn, bool immediateAck)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.Unwrap(tsn);\n\n\t\t\t// `IsTsnValid()` must be called prior to calling this method.\n\t\t\tMS_ASSERT(\n\t\t\t  Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn) <=\n\t\t\t    DataTracker::MaxAcceptedOutstandingFragments,\n\t\t\t  \"Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn) >     DataTracker::MaxAcceptedOutstandingFragments\");\n\n\t\t\tbool isDuplicate = false;\n\n\t\t\t// Old chunk already seen before?\n\t\t\tif (unwrappedTsn <= this->lastCumulativeAckedTsn)\n\t\t\t{\n\t\t\t\tif (this->duplicateTsns.size() < DataTracker::MaxDuplicateTsnReported)\n\t\t\t\t{\n\t\t\t\t\tthis->duplicateTsns.insert(unwrappedTsn.Wrap());\n\t\t\t\t}\n\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.2\n\t\t\t\t//\n\t\t\t\t// \"When a packet arrives with duplicate DATA chunk(s) and with no new\n\t\t\t\t// DATA chunk(s), the endpoint MUST immediately send a SACK with no\n\t\t\t\t// delay. If a packet arrives with duplicate DATA chunk(s) bundled with\n\t\t\t\t// new DATA chunks, the endpoint MAY immediately send a SACK.\"\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"duplicate data\");\n\n\t\t\t\tisDuplicate = true;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (unwrappedTsn == this->lastCumulativeAckedTsn.GetNextValue())\n\t\t\t\t{\n\t\t\t\t\tthis->lastCumulativeAckedTsn = unwrappedTsn;\n\n\t\t\t\t\t// The cumulative acked `tsn` may be moved even further, if a gap was\n\t\t\t\t\t// filled.\n\t\t\t\t\tif (\n\t\t\t\t\t  !this->additionalTsnBlocks.IsEmpty() && this->additionalTsnBlocks.Front().firstTsn ==\n\t\t\t\t\t                                            this->lastCumulativeAckedTsn.GetNextValue())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->lastCumulativeAckedTsn = this->additionalTsnBlocks.Front().lastTsn;\n\t\t\t\t\t\tthis->additionalTsnBlocks.PopFront();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tconst bool inserted = this->additionalTsnBlocks.Add(unwrappedTsn);\n\n\t\t\t\t\tif (!inserted)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Already seen before.\n\t\t\t\t\t\tif (this->duplicateTsns.size() < DataTracker::MaxDuplicateTsnReported)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->duplicateTsns.insert(unwrappedTsn.Wrap());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.2\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// \"When a packet arrives with duplicate DATA chunk(s) and with no\n\t\t\t\t\t\t// new DATA chunk(s), the endpoint MUST immediately send a SACK with\n\t\t\t\t\t\t// no delay. If a packet arrives with duplicate DATA chunk(s)\n\t\t\t\t\t\t// bundled with new DATA chunks, the endpoint MAY immediately send a\n\t\t\t\t\t\t// SACK.\"\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// No need to do this. SACKs are sent immediately on packet loss below.\n\t\t\t\t\t\tisDuplicate = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.7\n\t\t\t//\n\t\t\t// \"Upon the reception of a new DATA chunk, an endpoint shall examine the\n\t\t\t// continuity of the TSNs received.  If the endpoint detects a gap in the\n\t\t\t// received DATA chunk sequence, it SHOULD send a SACK with Gap Ack Blocks\n\t\t\t// immediately. The data receiver continues sending a SACK after receipt\n\t\t\t// of each SCTP packet that doesn't fill the gap.\"\n\t\t\tif (!this->additionalTsnBlocks.IsEmpty())\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"packet loss\");\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc7053#section-5.2\n\t\t\t//\n\t\t\t// \"Upon receipt of an SCTP packet containing a DATA chunk with the I bit\n\t\t\t// set, the receiver SHOULD NOT delay the sending of the corresponding\n\t\t\t// SACK chunk, i.e., the receiver SHOULD immediately respond with the\n\t\t\t// corresponding SACK chunk.\"\n\t\t\tif (immediateAck)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"immediate-ack bit set\");\n\t\t\t}\n\n\t\t\tif (!this->packetSeen)\n\t\t\t{\n\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-5.1\n\t\t\t\t//\n\t\t\t\t// \"After the reception of the first DATA chunk in an association the\n\t\t\t\t// endpoint MUST immediately respond with a SACK to acknowledge the DATA\n\t\t\t\t// chunk.\"\n\t\t\t\tthis->packetSeen = true;\n\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"first data chunk\");\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.2\n\t\t\t//\n\t\t\t// \"Specifically, an acknowledgement SHOULD be generated for at least\n\t\t\t// every second packet (not every second DATA chunk) received, and SHOULD\n\t\t\t// be generated within 200 ms of the arrival of any unacknowledged DATA\n\t\t\t// chunk.\"\n\t\t\tif (this->ackState == AckState::IDLE)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::BECOMING_DELAYED, \"received data when idle\");\n\t\t\t}\n\t\t\telse if (this->ackState == AckState::DELAYED)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"received data when already delayed\");\n\t\t\t}\n\n\t\t\treturn !isDuplicate;\n\t\t}\n\n\t\tvoid DataTracker::ObservePacketEnd()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->ackState == AckState::BECOMING_DELAYED)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::DELAYED, \"packet end\");\n\t\t\t}\n\t\t}\n\n\t\tbool DataTracker::HandleForwardTsn(uint32_t newCumulativeTsn)\n\t\t{\n\t\t\t// Forward-TSN is sent to make the receiver (this association) \"forget\"\n\t\t\t// about partly received (or not received at all) data, up until\n\t\t\t// `newCumulativeTsn`.\n\n\t\t\tconst Types::UnwrappedTsn unwrappedTsn      = this->tsnUnwrapper.Unwrap(newCumulativeTsn);\n\t\t\tconst Types::UnwrappedTsn prevLastCumAckTsn = this->lastCumulativeAckedTsn;\n\n\t\t\t// Old chunk already seen before?\n\t\t\tif (unwrappedTsn <= this->lastCumulativeAckedTsn)\n\t\t\t{\n\t\t\t\t// https://tools.ietf.org/html/rfc3758#section-3.6\n\t\t\t\t//\n\t\t\t\t// \"Note, if the \"New Cumulative TSN\" value carried in the arrived\n\t\t\t\t// FORWARD-TSN chunk is found to be behind or at the current cumulative\n\t\t\t\t// TSN point, the data receiver MUST treat this FORWARD TSN as out-of-date\n\t\t\t\t// and MUST NOT update its Cumulative TSN.  The receiver SHOULD send a\n\t\t\t\t// SACK to its peer (the sender of the FORWARD TSN) since such a duplicate\n\t\t\t\t// may indicate the previous SACK was lost in the network.\"\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"Forward TSN new cumulative tsn was behind\");\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc3758#section-3.6\n\t\t\t//\n\t\t\t// \"When a FORWARD TSN chunk arrives, the data receiver MUST first update\n\t\t\t// its cumulative TSN point to the value carried in the FORWARD TSN chunk,\n\t\t\t// and then MUST further advance its cumulative TSN point locally if\n\t\t\t// possible\"\n\n\t\t\t// The `newCumulativeTsn` will become the current\n\t\t\t// `this->lastCumulativeAckedTsn`, and if there have been prior \"gaps\"\n\t\t\t// that are now overlapping with the new value, remove them.\n\t\t\tthis->lastCumulativeAckedTsn = unwrappedTsn;\n\t\t\tthis->additionalTsnBlocks.EraseTo(unwrappedTsn);\n\n\t\t\t// See if the `this->lastCumulativeAckedTsn` can be moved even further.\n\t\t\tif (\n\t\t\t  !this->additionalTsnBlocks.IsEmpty() &&\n\t\t\t  this->additionalTsnBlocks.Front().firstTsn == this->lastCumulativeAckedTsn.GetNextValue())\n\t\t\t{\n\t\t\t\tthis->lastCumulativeAckedTsn = this->additionalTsnBlocks.Front().lastTsn;\n\t\t\t\tthis->additionalTsnBlocks.PopFront();\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"Forward TSN [prevLastCumAckTsn:%\" PRIu32 \", newCumulativeTsn:%\" PRIu32\n\t\t\t  \", lastCumulativeAckedTsn:%\" PRIu32,\n\t\t\t  prevLastCumAckTsn.Wrap(),\n\t\t\t  newCumulativeTsn,\n\t\t\t  this->lastCumulativeAckedTsn.Wrap());\n\n\t\t\t// https://tools.ietf.org/html/rfc3758#section-3.6\n\t\t\t//\n\t\t\t// \"Any time a FORWARD TSN chunk arrives, for the purposes of sending a\n\t\t\t// SACK, the receiver MUST follow the same rules as if a DATA chunk had\n\t\t\t// been received (i.e., follow the delayed sack rules specified in ...\".\n\t\t\tif (this->ackState == AckState::IDLE)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::BECOMING_DELAYED, \"received forward TSN when idle\");\n\t\t\t}\n\t\t\telse if (this->ackState == AckState::DELAYED)\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::IMMEDIATE, \"received forward TSN when already delayed\");\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tbool DataTracker::ShouldSendAck(bool alsoIfDelayed)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (\n\t\t\t  this->ackState == AckState::IMMEDIATE ||\n\t\t\t  (alsoIfDelayed &&\n\t\t\t   (this->ackState == AckState::BECOMING_DELAYED || this->ackState == AckState::DELAYED)))\n\t\t\t{\n\t\t\t\tUpdateAckState(AckState::IDLE, \"should send SACK\");\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tvoid DataTracker::ForceImmediateSack()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUpdateAckState(AckState::IMMEDIATE, \"force immediate SACK\");\n\t\t}\n\n\t\tbool DataTracker::WillIncreaseCumAckTsn(uint32_t tsn) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.PeekUnwrap(tsn);\n\n\t\t\treturn unwrappedTsn == this->lastCumulativeAckedTsn.GetNextValue();\n\t\t}\n\n\t\tvoid DataTracker::AddSackSelectiveAck(Packet* packet, size_t aRwnd)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Note that in SCTP, the receiver side is allowed to discard received data\n\t\t\t// and signal that to the sender, but only chunks that have previously been\n\t\t\t// reported in the gap-ack-blocks. However, this implementation will never\n\t\t\t// do that. So this SACK produced is more like a NR-SACK as explained in\n\t\t\t// https://ieeexplore.ieee.org/document/4697037 and which there is an RFC\n\t\t\t// draft at https://tools.ietf.org/html/draft-tuexen-tsvwg-sctp-multipath-17.\n\n\t\t\tauto* sackChunk = packet->BuildChunkInPlace<SackChunk>();\n\n\t\t\tsackChunk->SetCumulativeTsnAck(this->lastCumulativeAckedTsn.Wrap());\n\t\t\tsackChunk->SetAdvertisedReceiverWindowCredit(aRwnd);\n\n\t\t\tconst auto& tsnBlocks = this->additionalTsnBlocks.GetBlocks();\n\n\t\t\tfor (size_t i{ 0 }; i < tsnBlocks.size() && i < DataTracker::MaxGapAckBlocksReported; ++i)\n\t\t\t{\n\t\t\t\tconst auto startDiff =\n\t\t\t\t  Types::UnwrappedTsn::Difference(tsnBlocks[i].firstTsn, this->lastCumulativeAckedTsn);\n\t\t\t\tconst auto endDiff =\n\t\t\t\t  Types::UnwrappedTsn::Difference(tsnBlocks[i].lastTsn, this->lastCumulativeAckedTsn);\n\n\t\t\t\tsackChunk->AddAckBlock(startDiff, endDiff);\n\t\t\t}\n\n\t\t\tstd::set<uint32_t> duplicateTsns;\n\n\t\t\tthis->duplicateTsns.swap(duplicateTsns);\n\n\t\t\tfor (const uint32_t tsn : duplicateTsns)\n\t\t\t{\n\t\t\t\tsackChunk->AddDuplicateTsn(tsn);\n\t\t\t}\n\n\t\t\tsackChunk->Consolidate();\n\t\t}\n\n\t\tvoid DataTracker::HandleDelayedAckTimerExpiry()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tUpdateAckState(AckState::IMMEDIATE, \"delayed ack timer expired\");\n\t\t}\n\n\t\tvoid DataTracker::UpdateAckState(AckState newAckState, std::string_view reason)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (newAckState != this->ackState)\n\t\t\t{\n\t\t\t\tconst auto& previousAckStateStrView = DataTracker::AckStateToString(this->ackState);\n\t\t\t\tconst auto& newAckStateStrView      = DataTracker::AckStateToString(newAckState);\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"ack state changed from %.*s to %.*s due to %.*s\",\n\t\t\t\t  static_cast<int>(previousAckStateStrView.size()),\n\t\t\t\t  previousAckStateStrView.data(),\n\t\t\t\t  static_cast<int>(newAckStateStrView.size()),\n\t\t\t\t  newAckStateStrView.data(),\n\t\t\t\t  static_cast<int>(reason.size()),\n\t\t\t\t  reason.data());\n#endif\n\n\t\t\t\tif (this->ackState == AckState::DELAYED)\n\t\t\t\t{\n\t\t\t\t\tthis->delayedAckTimer->Stop();\n\t\t\t\t}\n\t\t\t\telse if (newAckState == AckState::DELAYED)\n\t\t\t\t{\n\t\t\t\t\tthis->delayedAckTimer->Start();\n\t\t\t\t}\n\t\t\t\tthis->ackState = newAckState;\n\t\t\t}\n\t\t}\n\n\t\tbool DataTracker::AdditionalTsnBlocks::Add(Types::UnwrappedTsn tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Find any block to expand. It will look for any block that includes (also\n\t\t\t// when expanded) the provided `tsn`. It will return the block that is greater\n\t\t\t// than, or equal to `tsn`.\n\t\t\tconst auto it = std::ranges::lower_bound(\n\t\t\t  this->blocks,\n\t\t\t  tsn,\n\t\t\t  std::less<Types::UnwrappedTsn>{},\n\t\t\t  [](const TsnRange& e)\n\t\t\t  {\n\t\t\t\t  return e.lastTsn.GetNextValue();\n\t\t\t  });\n\n\t\t\t// No matching block found. There is no greater than, or equal block,\n\t\t\t// which means that this TSN is greater than any block. It can then be\n\t\t\t// inserted at the end.\n\t\t\tif (it == this->blocks.end())\n\t\t\t{\n\t\t\t\tthis->blocks.emplace_back(tsn, tsn);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// `tsn` is already in this block.\n\t\t\tif (tsn >= it->firstTsn && tsn <= it->lastTsn)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// This block can be expanded to the right, or merged with the next.\n\t\t\tif (it->lastTsn.GetNextValue() == tsn)\n\t\t\t{\n\t\t\t\tconst auto nextIt = it + 1;\n\n\t\t\t\tif (nextIt != this->blocks.end() && tsn.GetNextValue() == nextIt->firstTsn)\n\t\t\t\t{\n\t\t\t\t\t// Expanding it would make it adjacent to next block, merge those.\n\t\t\t\t\tit->lastTsn = nextIt->lastTsn;\n\n\t\t\t\t\tthis->blocks.erase(nextIt);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\t// Expand to the right.\n\t\t\t\tit->lastTsn = tsn;\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// This block can be expanded to the left. Merging to the left would've\n\t\t\t// been covered by the above \"merge to the right\". Both blocks (expand a\n\t\t\t// right-most block to the left and expand a left-most block to the right)\n\t\t\t// would match, but the left-most would be returned by std::lower_bound.\n\t\t\tif (it->firstTsn == tsn.GetNextValue())\n\t\t\t{\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  it == this->blocks.begin() || (it - 1)->lastTsn.GetNextValue() != tsn,\n\t\t\t\t  \"got wrong iterator\");\n\n\t\t\t\tit->firstTsn = tsn;\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Need to create a new block in the middle.\n\t\t\tthis->blocks.emplace(it, tsn, tsn);\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid DataTracker::AdditionalTsnBlocks::EraseTo(Types::UnwrappedTsn tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Find the block that is greater than or equals `tsn`.\n\t\t\tconst auto it = std::ranges::lower_bound(\n\t\t\t  this->blocks, tsn, std::less<Types::UnwrappedTsn>{}, &TsnRange::lastTsn);\n\n\t\t\t// The block that is found is greater or equal (or possibly ::end(), when\n\t\t\t// no block is greater or equal). All blocks before this block can be\n\t\t\t// safely removed. The TSN might be within this block, so possibly truncate\n\t\t\t// it.\n\t\t\tconst bool tsnIsWithinBlock = it != this->blocks.end() && tsn >= it->firstTsn;\n\n\t\t\tthis->blocks.erase(this->blocks.begin(), it);\n\n\t\t\tif (tsnIsWithinBlock)\n\t\t\t{\n\t\t\t\tthis->blocks.front().firstTsn = tsn.GetNextValue();\n\t\t\t}\n\t\t}\n\n\t\tvoid DataTracker::AdditionalTsnBlocks::PopFront()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(!this->blocks.empty(), \"this->blocks is empty\");\n\n\t\t\tthis->blocks.erase(this->blocks.begin());\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::InterleavedReassemblyStreams\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/rx/InterleavedReassemblyStreams.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tInterleavedReassemblyStreams::InterleavedReassemblyStreams(\n\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage)\n\t\t  : onAssembledMessage(std::move(onAssembledMessage))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tint32_t InterleavedReassemblyStreams::AddData(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn GetOrCreateStream(FullStreamId(data.IsUnordered(), data.GetStreamId()))\n\t\t\t  .AddData(tsn, std::move(data));\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::HandleForwardTsn(\n\t\t  Types::UnwrappedTsn /*newCumulativeTsn*/,\n\t\t  std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t bytesRemoved = 0;\n\n\t\t\tfor (const auto& skippedStream : skippedStreams)\n\t\t\t{\n\t\t\t\tbytesRemoved +=\n\t\t\t\t  GetOrCreateStream(FullStreamId(skippedStream.unordered, skippedStream.streamId))\n\t\t\t\t    .EraseTo(skippedStream.mid);\n\t\t\t}\n\n\t\t\treturn bytesRemoved;\n\t\t}\n\n\t\tvoid InterleavedReassemblyStreams::ResetStreams(std::span<const uint16_t> streamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (streamIds.empty())\n\t\t\t{\n\t\t\t\tfor (auto& [fullStreamId, stream] : this->streams)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"resetting implicit stream [streamId:%\" PRIu16 \"]\", fullStreamId.streamId);\n\n\t\t\t\t\tstream.Reset();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (const auto streamId : streamIds)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"resetting explicit stream [streamId:%\" PRIu16 \"]\", streamId);\n\n\t\t\t\t\tGetOrCreateStream(FullStreamId(/*unordered*/ true, streamId)).Reset();\n\t\t\t\t\tGetOrCreateStream(FullStreamId(/*unordered*/ false, streamId)).Reset();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tInterleavedReassemblyStreams::Stream& InterleavedReassemblyStreams::GetOrCreateStream(\n\t\t  const FullStreamId& streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tauto it = this->streams.find(streamId);\n\n\t\t\tif (it == this->streams.end())\n\t\t\t{\n\t\t\t\tit = this->streams\n\t\t\t\t       .emplace(\n\t\t\t\t         std::piecewise_construct,\n\t\t\t\t         std::forward_as_tuple(streamId),\n\t\t\t\t         std::forward_as_tuple(streamId, this))\n\t\t\t\t       .first;\n\t\t\t}\n\n\t\t\tauto& stream = it->second;\n\n\t\t\treturn stream;\n\t\t}\n\n\t\tint32_t InterleavedReassemblyStreams::Stream::AddData(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  data.IsUnordered() == this->fullStreamId.unordered,\n\t\t\t  \"data.IsUnordered() != this->streamId.unordered\");\n\t\t\tMS_ASSERT(\n\t\t\t  data.GetStreamId() == this->fullStreamId.streamId,\n\t\t\t  \"data.GetStreamId() != this->fullStreamId.streamId\");\n\n\t\t\tauto queuedBytes = static_cast<int32_t>(data.GetPayloadLength());\n\n\t\t\tconst Types::UnwrappedMid mid = this->midUnwrapper.Unwrap(data.GetMessageId());\n\t\t\tconst uint32_t fsn            = data.GetFragmentSequenceNumber();\n\n\t\t\t// Avoid inserting it into any map if it can be delivered directly.\n\t\t\tif (this->fullStreamId.unordered && data.IsBeginning() && data.IsEnd())\n\t\t\t{\n\t\t\t\tAssembleMessage(tsn, std::move(data));\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\telse if (!this->fullStreamId.unordered && mid == this->nextMid && data.IsBeginning() && data.IsEnd())\n\t\t\t{\n\t\t\t\tAssembleMessage(tsn, std::move(data));\n\n\t\t\t\tthis->nextMid.Increment();\n\n\t\t\t\t// This might unblock assembling more messages.\n\t\t\t\treturn -TryToAssembleMessages();\n\t\t\t}\n\n\t\t\t// Slow path.\n\t\t\tconst auto [unused, inserted] =\n\t\t\t  this->chunksByMid[mid].emplace(fsn, std::make_pair(tsn, std::move(data)));\n\n\t\t\tif (!inserted)\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (this->fullStreamId.unordered)\n\t\t\t{\n\t\t\t\tqueuedBytes -= TryToAssembleMessage(mid);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tif (mid == this->nextMid)\n\t\t\t\t{\n\t\t\t\t\tqueuedBytes -= TryToAssembleMessages();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn queuedBytes;\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::Stream::EraseTo(uint32_t mid)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst Types::UnwrappedMid unwrappedMid = this->midUnwrapper.Unwrap(mid);\n\t\t\tsize_t removedBytes                    = 0;\n\n\t\t\tauto it = this->chunksByMid.begin();\n\n\t\t\twhile (it != this->chunksByMid.end() && it->first <= unwrappedMid)\n\t\t\t{\n\t\t\t\tremovedBytes += std::accumulate(\n\t\t\t\t  it->second.begin(),\n\t\t\t\t  it->second.end(),\n\t\t\t\t  0,\n\t\t\t\t  [](size_t acc, const auto& i)\n\t\t\t\t  {\n\t\t\t\t\t  const auto& data = i.second.second;\n\n\t\t\t\t\t  return acc + data.GetPayloadLength();\n\t\t\t\t  });\n\n\t\t\t\tit = this->chunksByMid.erase(it);\n\t\t\t}\n\n\t\t\tif (!this->fullStreamId.unordered)\n\t\t\t{\n\t\t\t\t// For ordered streams, erasing a message might suddenly unblock that\n\t\t\t\t// queue and allow it to deliver any following received messages.\n\t\t\t\tif (unwrappedMid >= this->nextMid)\n\t\t\t\t{\n\t\t\t\t\tthis->nextMid = unwrappedMid.GetNextValue();\n\t\t\t\t}\n\n\t\t\t\tremovedBytes += TryToAssembleMessages();\n\t\t\t}\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::Stream::TryToAssembleMessage(Types::UnwrappedMid mid)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto it = this->chunksByMid.find(mid);\n\n\t\t\tif (it == this->chunksByMid.end())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"trying to assemble message [mid:%\" PRIu32 \"]\", mid.Wrap());\n\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tChunkMap& chunks = it->second;\n\n\t\t\tif (!chunks.begin()->second.second.IsBeginning() || !chunks.rbegin()->second.second.IsEnd())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"cannot assemble message, missing beggining or end [mid:%\" PRIu32 \"]\", mid.Wrap());\n\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\t// NOTE: This is uint32_t - uint32_t casted to uint64_t. Not very nice but\n\t\t\t// it works.\n\t\t\tconst int64_t fnsDiff = chunks.rbegin()->first - chunks.begin()->first;\n\n\t\t\tif (fnsDiff != (static_cast<int64_t>(chunks.size()) - 1))\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"cannot assemble message, not all chunks exist [has:%zu, expected:%\" PRIi64 \"]\",\n\t\t\t\t  chunks.size(),\n\t\t\t\t  fnsDiff + 1);\n\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst size_t removedBytes = AssembleMessage(chunks);\n\n\t\t\tMS_DEBUG_DEV(\"message assembled [mid:%\" PRIu32 \", removedBytes:%zu]\", mid.Wrap(), removedBytes);\n\n\t\t\tthis->chunksByMid.erase(mid);\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::Stream::TryToAssembleMessages()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t removedBytes = 0;\n\n\t\t\tfor (;;)\n\t\t\t{\n\t\t\t\tconst size_t removedBytesThisIt = TryToAssembleMessage(this->nextMid);\n\n\t\t\t\tif (removedBytesThisIt == 0)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tremovedBytes += removedBytesThisIt;\n\n\t\t\t\tthis->nextMid.Increment();\n\t\t\t}\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::Stream::AssembleMessage(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t payloadLength        = data.GetPayloadLength();\n\t\t\tconst auto streamId               = data.GetStreamId();\n\t\t\tconst auto pip                    = data.GetPayloadProtocolId();\n\t\t\tconst Types::UnwrappedTsn tsns[1] = { tsn };\n\n\t\t\tMessage message(\n\t\t\t  streamId,\n\t\t\t  pip,\n\t\t\t  // NOTE: clang-tidy doesn't understand that this is fine.\n\t\t\t  // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\t\t  std::move(data).ReleasePayload());\n\n\t\t\tthis->parent.onAssembledMessage(tsns, std::move(message));\n\n\t\t\treturn payloadLength;\n\t\t}\n\n\t\tsize_t InterleavedReassemblyStreams::Stream::AssembleMessage(ChunkMap& tsnChunks)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t count = tsnChunks.size();\n\n\t\t\t// Fast path - zero-copy.\n\t\t\tif (count == 1)\n\t\t\t{\n\t\t\t\treturn AssembleMessage(\n\t\t\t\t  tsnChunks.begin()->second.first, std::move(tsnChunks.begin()->second.second));\n\t\t\t}\n\n\t\t\t// Slow path - will need to concatenate the payload.\n\t\t\tstd::vector<Types::UnwrappedTsn> tsns;\n\t\t\tstd::vector<uint8_t> payload;\n\n\t\t\tconst size_t payloadLength = std::accumulate(\n\t\t\t  tsnChunks.begin(),\n\t\t\t  tsnChunks.end(),\n\t\t\t  0,\n\t\t\t  [](size_t acc, const auto& i)\n\t\t\t  {\n\t\t\t\t  const auto& data = i.second.second;\n\n\t\t\t\t  return acc + data.GetPayloadLength();\n\t\t\t  });\n\n\t\t\tpayload.reserve(payloadLength);\n\t\t\ttsns.reserve(count);\n\n\t\t\tfor (auto& item : tsnChunks)\n\t\t\t{\n\t\t\t\tconst Types::UnwrappedTsn tsn = item.second.first;\n\n\t\t\t\tUserData& data = item.second.second;\n\n\t\t\t\ttsns.push_back(tsn);\n\t\t\t\tpayload.insert(payload.end(), data.GetPayload().begin(), data.GetPayload().end());\n\t\t\t}\n\n\t\t\tconst UserData& data = tsnChunks.begin()->second.second;\n\n\t\t\tMessage message(data.GetStreamId(), data.GetPayloadProtocolId(), std::move(payload));\n\n\t\t\tthis->parent.onAssembledMessage(tsns, std::move(message));\n\n\t\t\treturn payloadLength;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::ReassemblyQueue\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/rx/ReassemblyQueue.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/SCTP/rx/InterleavedReassemblyStreams.hpp\"\n#include \"RTC/SCTP/rx/TraditionalReassemblyStreams.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tReassemblyQueue::ReassemblyQueue(size_t maxLengthBytes, bool useMessageInterleaving)\n\t\t  : maxLengthBytes(maxLengthBytes),\n\t\t    watermarkBytes(this->maxLengthBytes * ReassemblyQueue::HighWatermarkLimit),\n\t\t    reassemblyStreams(CreateReassemblyStreams(\n\t\t      [this](std::span<const Types::UnwrappedTsn> tsns, Message message)\n\t\t      {\n\t\t\t      AddReassembledMessage(tsns, std::move(message));\n\t\t      },\n\t\t      useMessageInterleaving))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tReassemblyQueue::~ReassemblyQueue()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid ReassemblyQueue::AddData(uint32_t tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"added data [tsn:%\" PRIu32 \", streamId:%\" PRIu16 \", mid:%\" PRIu32 \", fsn:%\" PRIu32\n\t\t\t  \", type:%s]\",\n\t\t\t  tsn,\n\t\t\t  data.GetStreamId(),\n\t\t\t  data.GetMessageId(),\n\t\t\t  data.GetFragmentSequenceNumber(),\n\t\t\t  (data.IsBeginning() && data.IsEnd() ? \"complete\"\n\t\t\t   : data.IsBeginning()               ? \"first\"\n\t\t\t   : data.IsEnd()                     ? \"last\"\n\t\t\t                                      : \"middle\"));\n\n\t\t\tconst Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.Unwrap(tsn);\n\n\t\t\t// If a stream reset has been received with a \"sender's last assigned tsn\"\n\t\t\t// in the future, the association is in \"deferred reset processing\" mode\n\t\t\t// and must buffer chunks until it's exited.\n\t\t\tif (\n\t\t\t  this->deferredResetStreams.has_value() &&\n\t\t\t  unwrappedTsn > this->deferredResetStreams->senderLastAssignedTsn &&\n\t\t\t  this->deferredResetStreams->streamIds.contains(data.GetStreamId()))\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"deferrink chunk [tsn:%\" PRIu32 \", streamId:%\" PRIu16 \"] until tsn %\" PRIu32,\n\t\t\t\t  tsn,\n\t\t\t\t  data.GetStreamId(),\n\t\t\t\t  this->deferredResetStreams->senderLastAssignedTsn.Wrap());\n\n\t\t\t\t// https://tools.ietf.org/html/rfc6525#section-5.2.2\n\t\t\t\t//\n\t\t\t\t// \"In this mode, any data arriving with a TSN larger than the Sender's\n\t\t\t\t// Last Assigned TSN for the affected stream(s) MUST be queued locally\n\t\t\t\t// and held until the cumulative acknowledgment point reaches the\n\t\t\t\t// Sender's Last Assigned TSN.\"\n\n\t\t\t\tthis->queuedBytes += data.GetPayloadLength();\n\n\t\t\t\t// NOTE: We use C++20 so we don't support `std::move_only_function` and\n\t\t\t\t// hence we need to move UserData to a shared pointer. Otherwise it will\n\t\t\t\t// fail to compile because `std::function` doesn't accept move-only\n\t\t\t\t// callables and `UserData` has its copy constructor deleted, so the\n\t\t\t\t// resulting lambda is not copyable and `std::function` will reject it.\n\t\t\t\tauto sharedData = std::make_shared<UserData>(std::move(data));\n\n\t\t\t\tthis->deferredResetStreams->deferredActions.emplace_back(\n\t\t\t\t  [this, tsn, sharedData]() mutable\n\t\t\t\t  {\n\t\t\t\t\t  this->queuedBytes -= sharedData->GetPayloadLength();\n\n\t\t\t\t\t  AddData(tsn, std::move(*sharedData));\n\t\t\t\t  });\n\n\t\t\t\t// TODO: Once we upgrade to C++23, replace the above with:\n\t\t\t\t// this->deferredResetStreams->deferredActions.emplace_back(\n\t\t\t\t//   [this, tsn, data = std::move(data)]() mutable\n\t\t\t\t//   {\n\t\t\t\t// \t  this->queuedBytes -= data.GetPayloadLength();\n\n\t\t\t\t// \t  AddData(tsn, std::move(data));\n\t\t\t\t//   });\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->queuedBytes += this->reassemblyStreams->AddData(unwrappedTsn, std::move(data));\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.9\n\t\t\t//\n\t\t\t// \"If the data receiver runs out of buffer space while still waiting for\n\t\t\t// more fragments to complete the reassembly of the message, it SHOULD\n\t\t\t// dispatch part of its inbound message through a partial delivery API\n\t\t\t// (see Section 11), freeing some of its receive buffer space so that the\n\t\t\t// rest of the message can be received.\"\n\n\t\t\t// TODO: dcsctp: Support EOR flag and partial delivery?\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tstd::optional<Message> ReassemblyQueue::GetNextMessage()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->reassembledMessages.empty())\n\t\t\t{\n\t\t\t\treturn std::nullopt;\n\t\t\t}\n\n\t\t\tMessage message = std::move(this->reassembledMessages.front());\n\n\t\t\tthis->reassembledMessages.pop_front();\n\t\t\tthis->queuedBytes -= message.GetPayloadLength();\n\n\t\t\treturn message;\n\t\t}\n\n\t\tvoid ReassemblyQueue::HandleForwardTsn(\n\t\t  uint32_t newCumulativeTsn, std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst Types::UnwrappedTsn tsn = this->tsnUnwrapper.Unwrap(newCumulativeTsn);\n\n\t\t\tif (this->deferredResetStreams.has_value() && tsn > this->deferredResetStreams->senderLastAssignedTsn)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"forward TSN to %\" PRIu32 \", deferring\", tsn.Wrap());\n\n\t\t\t\tthis->deferredResetStreams->deferredActions.emplace_back(\n\t\t\t\t  [this,\n\t\t\t\t   newCumulativeTsn,\n\t\t\t\t   skippedStreams2 = std::vector<AnyForwardTsnChunk::SkippedStream>(\n\t\t\t\t     skippedStreams.begin(), skippedStreams.end())]\n\t\t\t\t  {\n\t\t\t\t\t  HandleForwardTsn(newCumulativeTsn, skippedStreams2);\n\t\t\t\t  });\n\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\"forward TSN to %\" PRIu32 \", performing\", tsn.Wrap());\n\n\t\t\tthis->queuedBytes -= this->reassemblyStreams->HandleForwardTsn(tsn, skippedStreams);\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid ReassemblyQueue::ResetStreamsAndLeaveDeferredReset(std::span<const uint16_t> streamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tstd::string streamIdList;\n\n\t\t\tfor (const auto streamId : streamIds)\n\t\t\t{\n\t\t\t\tif (!streamIdList.empty())\n\t\t\t\t{\n\t\t\t\t\tstreamIdList += ',';\n\t\t\t\t}\n\n\t\t\t\tstreamIdList += std::to_string(streamId);\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\"resetting streams [streamIds:%s]\", streamIdList.c_str());\n#endif\n\n\t\t\t// https://tools.ietf.org/html/rfc6525#section-5.2.2\n\t\t\t//\n\t\t\t// \"streams MUST be reset to 0 as the next expected SSN.\"\n\t\t\tthis->reassemblyStreams->ResetStreams(streamIds);\n\n\t\t\tif (this->deferredResetStreams.has_value())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"leaving deferred reset processing, feeding back %zu actions\",\n\t\t\t\t  this->deferredResetStreams->deferredActions.size());\n\n\t\t\t\t// https://tools.ietf.org/html/rfc6525#section-5.2.2\n\t\t\t\t//\n\t\t\t\t// \"Any queued TSNs (queued at step E2) MUST now be released and\n\t\t\t\t// processed normally.\"\n\t\t\t\tauto deferredActions = std::move(this->deferredResetStreams->deferredActions);\n\n\t\t\t\tthis->deferredResetStreams = std::nullopt;\n\n\t\t\t\tfor (auto& action : deferredActions)\n\t\t\t\t{\n\t\t\t\t\taction();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid ReassemblyQueue::EnterDeferredReset(\n\t\t  uint32_t senderLastAssignedTsn, std::span<const uint16_t> streamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->deferredResetStreams.has_value())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"entering deferred reset [senderLastAssignedTsn:%\" PRIu32 \"]\", senderLastAssignedTsn);\n\n\t\t\tthis->deferredResetStreams = std::make_optional<DeferredResetStreams>(\n\t\t\t  this->tsnUnwrapper.Unwrap(senderLastAssignedTsn),\n\t\t\t  std::set<uint16_t>(streamIds.begin(), streamIds.end()));\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tstd::unique_ptr<ReassemblyStreamsInterface> ReassemblyQueue::CreateReassemblyStreams(\n\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage, bool useMessageInterleaving)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (useMessageInterleaving)\n\t\t\t{\n\t\t\t\treturn std::make_unique<InterleavedReassemblyStreams>(std::move(onAssembledMessage));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn std::make_unique<TraditionalReassemblyStreams>(std::move(onAssembledMessage));\n\t\t\t}\n\t\t}\n\n\t\tvoid ReassemblyQueue::AddReassembledMessage(std::span<const Types::UnwrappedTsn> tsns, Message message)\n\t\t{\n\t\t\tMS_TRACE();\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tstd::string tsnList;\n\n\t\t\tfor (const auto tsn : tsns)\n\t\t\t{\n\t\t\t\tif (!tsnList.empty())\n\t\t\t\t{\n\t\t\t\t\ttsnList += ',';\n\t\t\t\t}\n\n\t\t\t\ttsnList += std::to_string(tsn.Wrap());\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"resetting streams [ppid:%\" PRIu32 \", payloadLength:%zu, tsns:%s]\",\n\t\t\t  message.GetPayloadProtocolId(),\n\t\t\t  message.GetPayloadLength(),\n\t\t\t  tsnList.c_str());\n#endif\n\n\t\t\tthis->queuedBytes += message.GetPayloadLength();\n\t\t\tthis->reassembledMessages.emplace_back(std::move(message));\n\t\t}\n\n\t\tvoid ReassemblyQueue::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Allow this->queuedBytes to be larger than this->maxLengthBytes, as it's\n\t\t\t// not actively enforced in this class. But in case it wraps around\n\t\t\t// (becomes negative, but as it's unsigned, that would wrap to very big),\n\t\t\t// this would trigger.\n\t\t\tMS_ASSERT(\n\t\t\t  this->queuedBytes <= 2 * this->maxLengthBytes,\n\t\t\t  \"this->queuedBytes > 2 * this->maxLengthBytes\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::TraditionalReassemblyStreams\"\n// TODO: SCTP: COMMENT\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/rx/TraditionalReassemblyStreams.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\tTraditionalReassemblyStreams::TraditionalReassemblyStreams(\n\t\t  ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage)\n\t\t  : onAssembledMessage(std::move(onAssembledMessage))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tint32_t TraditionalReassemblyStreams::AddData(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (data.IsUnordered())\n\t\t\t{\n\t\t\t\tconst auto it = this->unorderedStreams.try_emplace(data.GetStreamId(), this).first;\n\n\t\t\t\tauto& stream = it->second;\n\n\t\t\t\treturn stream.AddData(tsn, std::move(data));\n\t\t\t}\n\n\t\t\tconst auto it = this->orderedStreams.try_emplace(data.GetStreamId(), this).first;\n\n\t\t\tauto& stream = it->second;\n\n\t\t\treturn stream.AddData(tsn, std::move(data));\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::HandleForwardTsn(\n\t\t  Types::UnwrappedTsn newCumulativeTsn,\n\t\t  std::span<const AnyForwardTsnChunk::SkippedStream> skippedStreams)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t removedBytes = 0;\n\n\t\t\t// The `skippedStreams` only cover ordered messages - need to iterate all\n\t\t\t// unordered streams manually to remove those chunks.\n\t\t\tfor (auto& [unused, stream] : this->unorderedStreams)\n\t\t\t{\n\t\t\t\tremovedBytes += stream.EraseTo(newCumulativeTsn);\n\t\t\t}\n\n\t\t\tfor (const auto& skippedStream : skippedStreams)\n\t\t\t{\n\t\t\t\tconst auto it = this->orderedStreams.try_emplace(skippedStream.streamId, this).first;\n\n\t\t\t\tauto& stream = it->second;\n\n\t\t\t\tremovedBytes += stream.EraseTo(skippedStream.ssn);\n\t\t\t}\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tvoid TraditionalReassemblyStreams::ResetStreams(std::span<const uint16_t> streamIds)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (streamIds.empty())\n\t\t\t{\n\t\t\t\tfor (auto& [streamId, stream] : this->orderedStreams)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"resetting implicit stream [streamId:%\" PRIu16 \"]\", streamId);\n\n\t\t\t\t\tstream.Reset();\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tfor (const auto streamId : streamIds)\n\t\t\t\t{\n\t\t\t\t\tconst auto it = this->orderedStreams.find(streamId);\n\n\t\t\t\t\tif (it != this->orderedStreams.end())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\"resetting explicit stream [streamId:%\" PRIu16 \"]\", streamId);\n\n\t\t\t\t\t\tauto& stream = it->second;\n\n\t\t\t\t\t\tstream.Reset();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(\n\t\t  const ChunkMap::iterator start, const ChunkMap::iterator end)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t count = std::distance(start, end);\n\n\t\t\t// Fast path - zero-copy\n\t\t\tif (count == 1)\n\t\t\t{\n\t\t\t\treturn AssembleMessage(start->first, std::move(start->second));\n\t\t\t}\n\n\t\t\t// Slow path - will need to concatenate the payload.\n\t\t\tstd::vector<Types::UnwrappedTsn> tsns;\n\t\t\tstd::vector<uint8_t> payload;\n\n\t\t\tconst size_t payloadLength = std::accumulate(\n\t\t\t  start,\n\t\t\t  end,\n\t\t\t  0,\n\t\t\t  [](size_t acc, const auto& i)\n\t\t\t  {\n\t\t\t\t  const auto& data = i.second;\n\n\t\t\t\t  return acc + data.GetPayloadLength();\n\t\t\t  });\n\n\t\t\ttsns.reserve(count);\n\t\t\tpayload.reserve(payloadLength);\n\n\t\t\tfor (auto it = start; it != end; ++it)\n\t\t\t{\n\t\t\t\tconst auto tsn = it->first;\n\n\t\t\t\tauto& data = it->second;\n\n\t\t\t\ttsns.push_back(tsn);\n\t\t\t\tpayload.insert(payload.end(), data.GetPayload().begin(), data.GetPayload().end());\n\t\t\t}\n\n\t\t\tconst auto& startData = start->second;\n\n\t\t\tMessage message(startData.GetStreamId(), startData.GetPayloadProtocolId(), std::move(payload));\n\n\t\t\tthis->parent.onAssembledMessage(tsns, std::move(message));\n\n\t\t\treturn payloadLength;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(\n\t\t  Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Fast path - zero-copy.\n\t\t\tconst size_t payloadLength        = data.GetPayloadLength();\n\t\t\tconst auto streamId               = data.GetStreamId();\n\t\t\tconst auto pip                    = data.GetPayloadProtocolId();\n\t\t\tconst Types::UnwrappedTsn tsns[1] = { tsn };\n\n\t\t\tMessage message(\n\t\t\t  streamId,\n\t\t\t  pip,\n\t\t\t  // NOTE: clang-tidy doesn't understand that this is fine.\n\t\t\t  // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\t\t  std::move(data).ReleasePayload());\n\n\t\t\tthis->parent.onAssembledMessage(tsns, std::move(message));\n\n\t\t\treturn payloadLength;\n\t\t}\n\n\t\tint32_t TraditionalReassemblyStreams::OrderedStream::AddData(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto queuedBytes        = static_cast<int32_t>(data.GetPayloadLength());\n\t\t\tconst Types::UnwrappedSsn ssn = this->ssnUnwrapper.Unwrap(data.GetStreamSequenceNumber());\n\n\t\t\tif (ssn == this->nextSsn)\n\t\t\t{\n\t\t\t\treturn queuedBytes - TryToAssembleMessagesFastpath(ssn, tsn, std::move(data));\n\t\t\t}\n\n\t\t\tconst auto [it, inserted] = this->chunksBySsn[ssn].emplace(tsn, std::move(data));\n\n\t\t\tif (!inserted)\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn queuedBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::OrderedStream::EraseTo(uint16_t ssn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tTypes::UnwrappedSsn unwrappedSsn = this->ssnUnwrapper.Unwrap(ssn);\n\n\t\t\tconst auto endIt = this->chunksBySsn.upper_bound(unwrappedSsn);\n\n\t\t\tsize_t removedBytes = std::accumulate(\n\t\t\t  this->chunksBySsn.begin(),\n\t\t\t  endIt,\n\t\t\t  0,\n\t\t\t  [](size_t acc1, const auto& i1)\n\t\t\t  {\n\t\t\t\t  return acc1 + std::accumulate(\n\t\t\t\t                  i1.second.begin(),\n\t\t\t\t                  i1.second.end(),\n\t\t\t\t                  0,\n\t\t\t\t                  [](size_t acc2, const auto& i2)\n\t\t\t\t                  {\n\t\t\t\t\t                  const auto& data = i2.second;\n\n\t\t\t\t\t                  return acc2 + data.GetPayloadLength();\n\t\t\t\t                  });\n\t\t\t  });\n\n\t\t\tthis->chunksBySsn.erase(this->chunksBySsn.begin(), endIt);\n\n\t\t\tif (unwrappedSsn >= this->nextSsn)\n\t\t\t{\n\t\t\t\tunwrappedSsn.Increment();\n\n\t\t\t\tthis->nextSsn = unwrappedSsn;\n\t\t\t}\n\n\t\t\tremovedBytes += TryToAssembleMessages();\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessage()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->chunksBySsn.empty() || this->chunksBySsn.begin()->first != this->nextSsn)\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tChunkMap& chunks = this->chunksBySsn.begin()->second;\n\n\t\t\tif (!chunks.begin()->second.IsBeginning() || !chunks.rbegin()->second.IsEnd())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst uint32_t tsnDiff =\n\t\t\t  Types::UnwrappedTsn::Difference(chunks.rbegin()->first, chunks.begin()->first);\n\n\t\t\tif (tsnDiff != chunks.size() - 1)\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst size_t assembledBytes = AssembleMessage(chunks.begin(), chunks.end());\n\n\t\t\tthis->chunksBySsn.erase(this->chunksBySsn.begin());\n\t\t\tthis->nextSsn.Increment();\n\n\t\t\treturn assembledBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessages()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t assembledBytes = 0;\n\n\t\t\tfor (;;)\n\t\t\t{\n\t\t\t\tconst size_t assembledBytesThisIter = TryToAssembleMessage();\n\n\t\t\t\tif (assembledBytesThisIter == 0)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tassembledBytes += assembledBytesThisIter;\n\t\t\t}\n\n\t\t\treturn assembledBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessagesFastpath(\n\t\t  Types::UnwrappedSsn ssn, Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(ssn == this->nextSsn, \"ssn != this->nextSsn\");\n\n\t\t\tsize_t assembledBytes = 0;\n\n\t\t\tif (data.IsBeginning() && data.IsEnd())\n\t\t\t{\n\t\t\t\tassembledBytes += AssembleMessage(tsn, std::move(data));\n\n\t\t\t\tthis->nextSsn.Increment();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tconst size_t queuedBytes = data.GetPayloadLength();\n\n\t\t\t\tauto [it, inserted] = this->chunksBySsn[ssn].emplace(tsn, std::move(data));\n\n\t\t\t\t// Not actually assembled, but deduplicated meaning queued size doesn't\n\t\t\t\t// include this message.\n\t\t\t\tif (!inserted)\n\t\t\t\t{\n\t\t\t\t\treturn queuedBytes;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn assembledBytes + TryToAssembleMessages();\n\t\t}\n\n\t\tint32_t TraditionalReassemblyStreams::UnorderedStream::AddData(Types::UnwrappedTsn tsn, UserData data)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Fastpath for already assembled chunks.\n\t\t\tif (data.IsBeginning() && data.IsEnd())\n\t\t\t{\n\t\t\t\tAssembleMessage(tsn, std::move(data));\n\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tauto queuedBytes = static_cast<int32_t>(data.GetPayloadLength());\n\n\t\t\tconst auto [it, inserted] = this->chunks.emplace(tsn, std::move(data));\n\n\t\t\tif (!inserted)\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tqueuedBytes -= TryToAssembleMessage(it);\n\n\t\t\treturn queuedBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::UnorderedStream::EraseTo(Types::UnwrappedTsn tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto endIt = this->chunks.upper_bound(tsn);\n\n\t\t\tconst size_t removedBytes = std::accumulate(\n\t\t\t  this->chunks.begin(),\n\t\t\t  endIt,\n\t\t\t  0,\n\t\t\t  [](size_t acc, const auto& i)\n\t\t\t  {\n\t\t\t\t  const auto& data = i.second;\n\n\t\t\t\t  return acc + data.GetPayloadLength();\n\t\t\t  });\n\n\t\t\tthis->chunks.erase(this->chunks.begin(), endIt);\n\n\t\t\treturn removedBytes;\n\t\t}\n\n\t\tsize_t TraditionalReassemblyStreams::UnorderedStream::TryToAssembleMessage(ChunkMap::iterator it)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// TODO: dcsctp: This method is O(N) with the number of fragments in a\n\t\t\t// message, which can be inefficient for very large values of N. This\n\t\t\t// could be optimized by e.g. only trying to assemble a message once _any_\n\t\t\t// beginning and _any_ end has been found.\n\t\t\tconst std::optional<ChunkMap::iterator> start = FindBeginning(it);\n\n\t\t\tif (!start.has_value())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst std::optional<ChunkMap::iterator> end = FindEnd(it);\n\n\t\t\tif (!end.has_value())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst size_t bytesAssembled = AssembleMessage(*start, *end);\n\n\t\t\tthis->chunks.erase(*start, *end);\n\n\t\t\treturn bytesAssembled;\n\t\t}\n\n\t\tstd::optional<std::map<Types::UnwrappedTsn, UserData>::iterator> TraditionalReassemblyStreams::\n\t\t  UnorderedStream::FindBeginning(std::map<Types::UnwrappedTsn, UserData>::iterator it)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tTypes::UnwrappedTsn prevTsn = it->first;\n\n\t\t\tfor (;;)\n\t\t\t{\n\t\t\t\tif (it->second.IsBeginning())\n\t\t\t\t{\n\t\t\t\t\treturn it;\n\t\t\t\t}\n\n\t\t\t\tif (it == this->chunks.begin())\n\t\t\t\t{\n\t\t\t\t\treturn std::nullopt;\n\t\t\t\t}\n\n\t\t\t\tit--;\n\n\t\t\t\tif (it->first.GetNextValue() != prevTsn)\n\t\t\t\t{\n\t\t\t\t\treturn std::nullopt;\n\t\t\t\t}\n\n\t\t\t\tprevTsn = it->first;\n\t\t\t}\n\t\t}\n\n\t\tstd::optional<std::map<Types::UnwrappedTsn, UserData>::iterator> TraditionalReassemblyStreams::\n\t\t  UnorderedStream::FindEnd(std::map<Types::UnwrappedTsn, UserData>::iterator it)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tTypes::UnwrappedTsn prevTsn = it->first;\n\n\t\t\tfor (;;)\n\t\t\t{\n\t\t\t\tif (it->second.IsEnd())\n\t\t\t\t{\n\t\t\t\t\treturn ++it;\n\t\t\t\t}\n\n\t\t\t\tit++;\n\n\t\t\t\tif (it == this->chunks.end())\n\t\t\t\t{\n\t\t\t\t\treturn std::nullopt;\n\t\t\t\t}\n\n\t\t\t\tif (it->first != prevTsn.GetNextValue())\n\t\t\t\t{\n\t\t\t\t\treturn std::nullopt;\n\t\t\t\t}\n\n\t\t\t\tprevTsn = it->first;\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/OutstandingData.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::OutstandingData\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/OutstandingData.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include <map>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Static. */\n\n\t\t// The number of times a packet must be NACKed before it's retransmitted.\n\t\t//\n\t\t// @see https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\tconstexpr uint8_t NumberOfNacksForRetransmission{ 3 };\n\n\t\t/* Instance methods. */\n\n\t\tOutstandingData::Item::Item(\n\t\t  uint32_t outgoingMessageId,\n\t\t  UserData data,\n\t\t  uint64_t timeSentMs,\n\t\t  uint16_t maxRetransmissions,\n\t\t  uint64_t expiresAtMs,\n\t\t  std::optional<uint64_t> lifecycleId)\n\t\t  : outgoingMessageId(outgoingMessageId),\n\t\t    data(std::move(data)),\n\t\t    timeSentMs(timeSentMs),\n\t\t    maxRetransmissions(maxRetransmissions),\n\t\t    expiresAtMs(expiresAtMs),\n\t\t    lifecycleId(lifecycleId)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid OutstandingData::Item::Ack()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->lifecycle != Lifecycle::ABANDONED)\n\t\t\t{\n\t\t\t\tthis->lifecycle = Lifecycle::ACTIVE;\n\t\t\t}\n\n\t\t\tthis->ackState = AckState::ACKED;\n\t\t}\n\n\t\tOutstandingData::Item::NackAction OutstandingData::Item::Nack(bool retransmitNow)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->ackState = AckState::NACKED;\n\t\t\t++this->nackCount;\n\n\t\t\tif (!ShouldBeRetransmitted() && !IsAbandoned() && (retransmitNow || this->nackCount >= NumberOfNacksForRetransmission))\n\t\t\t{\n\t\t\t\t// Nacked enough times, it's considered lost.\n\t\t\t\tif (this->numRetransmissions < this->maxRetransmissions)\n\t\t\t\t{\n\t\t\t\t\tthis->lifecycle = Lifecycle::TO_BE_RETRANSMITTED;\n\n\t\t\t\t\treturn NackAction::RETRANSMIT;\n\t\t\t\t}\n\n\t\t\t\tAbandon();\n\n\t\t\t\treturn NackAction::ABANDON;\n\t\t\t}\n\n\t\t\treturn NackAction::NOTHING;\n\t\t}\n\n\t\tvoid OutstandingData::Item::MarkAsRetransmitted()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->lifecycle = Lifecycle::ACTIVE;\n\t\t\tthis->ackState  = AckState::UNACKED;\n\t\t\tthis->nackCount = 0;\n\t\t\t++this->numRetransmissions;\n\t\t}\n\n\t\tvoid OutstandingData::Item::Abandon()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->expiresAtMs != Types::ExpiresAtMsInfinite ||\n\t\t\t    this->maxRetransmissions != Types::MaxRetransmitsNoLimit,\n\t\t\t  \"item should not have infinite expiration time or its retransmission times shouldn't be the maximum\");\n\n\t\t\tthis->lifecycle = Lifecycle::ABANDONED;\n\t\t}\n\n\t\tOutstandingData::OutstandingData(\n\t\t  size_t dataChunkHeaderLength,\n\t\t  Types::UnwrappedTsn lastCumulativeTsnAck,\n\t\t  std::function<bool(uint16_t /*streamId*/, uint32_t /*outgoingMessageId*/)> discardFromSendQueue)\n\t\t  : dataChunkHeaderLength(dataChunkHeaderLength),\n\t\t    lastCumulativeTsnAck(lastCumulativeTsnAck),\n\t\t    discardFromSendQueue(std::move(discardFromSendQueue))\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tOutstandingData::AckInfo OutstandingData::HandleSack(\n\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t  bool isInFastRecovery)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst bool cumulativeTsnAckAdvanced = cumulativeTsnAck > this->lastCumulativeTsnAck;\n\n\t\t\tOutstandingData::AckInfo ackInfo(cumulativeTsnAck);\n\n\t\t\t// Erase all items up to cumulativeTsnAck.\n\t\t\tRemoveAcked(cumulativeTsnAck, ackInfo);\n\n\t\t\t// ACK packets reported in the gap ack blocks.\n\t\t\tAckGapBlocks(cumulativeTsnAck, gapAckBlocks, ackInfo);\n\n\t\t\t// NACK and possibly mark for retransmit Chunks that weren't acked.\n\t\t\tNackBetweenAckBlocks(\n\t\t\t  cumulativeTsnAck, gapAckBlocks, isInFastRecovery, cumulativeTsnAckAdvanced, ackInfo);\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn ackInfo;\n\t\t}\n\n\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> OutstandingData::GetChunksToBeFastRetransmitted(\n\t\t  size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> result =\n\t\t\t  ExtractChunksThatCanFit(this->toBeFastRetransmitted, maxLength);\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"Those TSNs marked for retransmission due to the Fast-Retransmit\n\t\t\t// algorithm that did not fit in the sent datagram carrying K other TSNs\n\t\t\t// are also marked as ineligible for a subsequent Fast Retransmit.\n\t\t\t// However, as they are marked for retransmission they will be\n\t\t\t// retransmitted later on as soon as cwnd allows.\"\n\t\t\tif (!this->toBeFastRetransmitted.empty())\n\t\t\t{\n\t\t\t\tthis->toBeRetransmitted.insert(\n\t\t\t\t  this->toBeFastRetransmitted.begin(), this->toBeFastRetransmitted.end());\n\n\t\t\t\tthis->toBeFastRetransmitted.clear();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn result;\n\t\t}\n\n\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> OutstandingData::GetChunksToBeRetransmitted(\n\t\t  size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Chunks scheduled for fast retransmission must be sent first.\n\t\t\tMS_ASSERT(this->toBeFastRetransmitted.empty(), \"this->toBeFastRetransmitted is not empty\");\n\n\t\t\treturn ExtractChunksThatCanFit(this->toBeRetransmitted, maxLength);\n\t\t}\n\n\t\tvoid OutstandingData::ExpireOutstandingChunks(uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<Types::UnwrappedTsn> tsnsToExpire;\n\t\t\tTypes::UnwrappedTsn tsn = this->lastCumulativeTsnAck;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\t// Chunks that are nacked can be expired. Care should be taken not to\n\t\t\t\t// expire unacked (in-flight) Chunks as they might have been received,\n\t\t\t\t// but the SACK is either delayed or in-flight and may be received\n\t\t\t\t// later.\n\t\t\t\tif (item.IsAbandoned())\n\t\t\t\t{\n\t\t\t\t\t// Already abandoned.\n\t\t\t\t}\n\t\t\t\telse if (item.IsNacked() && item.HasExpired(nowMs))\n\t\t\t\t{\n\t\t\t\t\ttsnsToExpire.push_back(tsn);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// A non-expired Chunk. No need to iterate any further.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const Types::UnwrappedTsn tsnToExpire : tsnsToExpire)\n\t\t\t{\n\t\t\t\t// The item is retrieved by TSN, as AbandonAllFor() may have modified\n\t\t\t\t// `this->outstandingData` and invalidated iterators from the first\n\t\t\t\t// loop.\n\t\t\t\tconst Item& item = GetItem(tsnToExpire);\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"marking nacked Chunk %\" PRIu32 \" and message %\" PRIu32 \" as expired\",\n\t\t\t\t  tsnToExpire.Wrap(),\n\t\t\t\t  item.GetData().GetMessageId());\n\n\t\t\t\tAbandonAllFor(item);\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tTypes::UnwrappedTsn OutstandingData::GetHighestOutstandingTsn() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn Types::UnwrappedTsn::AddTo(this->lastCumulativeTsnAck, this->outstandingData.size());\n\t\t}\n\n\t\tstd::optional<Types::UnwrappedTsn> OutstandingData::Insert(\n\t\t  uint32_t outgoingMessageId,\n\t\t  const UserData& data,\n\t\t  uint64_t timeSentMs,\n\t\t  uint16_t maxRetransmissions,\n\t\t  uint64_t expiresAtMs,\n\t\t  std::optional<uint64_t> lifecycleId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// All Chunks are always padded to be even divisible by 4.\n\t\t\tconst size_t chunkLength = GetSerializedChunkLength(data);\n\n\t\t\tthis->unackedPayloadBytes += data.GetPayloadLength();\n\t\t\tthis->unackedPacketBytes += chunkLength;\n\t\t\t++this->unackedItems;\n\n\t\t\tconst Types::UnwrappedTsn tsn = GetNextTsn();\n\t\t\tconst Item& item              = this->outstandingData.emplace_back(\n\t\t\t  outgoingMessageId, data.Clone(), timeSentMs, maxRetransmissions, expiresAtMs, lifecycleId);\n\n\t\t\tif (item.HasExpired(timeSentMs))\n\t\t\t{\n\t\t\t\t// No need to send it, it was expired when it was in the send queue.\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"marking freshly produced Chunk %\" PRIu32 \" and message %\" PRIu32 \" as expired\",\n\t\t\t\t  tsn.Wrap(),\n\t\t\t\t  item.GetData().GetMessageId());\n\n\t\t\t\tAbandonAllFor(item);\n\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn std::nullopt;\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn tsn;\n\t\t}\n\n\t\tvoid OutstandingData::NackAll()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tTypes::UnwrappedTsn tsn = this->lastCumulativeTsnAck;\n\n\t\t\t// A two-pass algorithm is needed, as NackItem will invalidate iterators.\n\t\t\tstd::vector<Types::UnwrappedTsn> tsnsToNack;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tif (!item.IsAcked())\n\t\t\t\t{\n\t\t\t\t\ttsnsToNack.push_back(tsn);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const Types::UnwrappedTsn tsnToNack : tsnsToNack)\n\t\t\t{\n\t\t\t\tNackItem(\n\t\t\t\t  tsnToNack,\n\t\t\t\t  /*retransmitNow*/ true,\n\t\t\t\t  /*doFastRetransmit*/ false);\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tconst ForwardTsnChunk* OutstandingData::AddForwardTsn(Packet* packet) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::map<uint16_t /*streamId*/, uint16_t /*ssn*/> skippedPerOrderedStream;\n\t\t\tTypes::UnwrappedTsn newCumulativeAck = this->lastCumulativeTsnAck;\n\t\t\tTypes::UnwrappedTsn tsn              = this->lastCumulativeTsnAck;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tif (\n\t\t\t\t  this->streamResetBreakpointTsns.contains(tsn) ||\n\t\t\t\t  (tsn != newCumulativeAck.GetNextValue()) || !item.IsAbandoned())\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tnewCumulativeAck = tsn;\n\n\t\t\t\tif (\n\t\t\t\t  !item.GetData().IsUnordered() && item.GetData().GetStreamSequenceNumber() >\n\t\t\t\t                                     skippedPerOrderedStream[item.GetData().GetStreamId()])\n\t\t\t\t{\n\t\t\t\t\tskippedPerOrderedStream[item.GetData().GetStreamId()] =\n\t\t\t\t\t  item.GetData().GetStreamSequenceNumber();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tauto* forwardTsnChunk = packet->BuildChunkInPlace<ForwardTsnChunk>();\n\n\t\t\tforwardTsnChunk->SetNewCumulativeTsn(newCumulativeAck.Wrap());\n\n\t\t\tfor (const auto& [streamId, ssn] : skippedPerOrderedStream)\n\t\t\t{\n\t\t\t\tforwardTsnChunk->AddSkippedStream(AnyForwardTsnChunk::SkippedStream{ streamId, ssn });\n\t\t\t}\n\n\t\t\tforwardTsnChunk->Consolidate();\n\n\t\t\treturn forwardTsnChunk;\n\t\t}\n\n\t\tconst IForwardTsnChunk* OutstandingData::AddIForwardTsn(Packet* packet) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::map<std::pair<bool /*unordered*/, uint16_t /*streamId*/>, uint32_t /*mid*/> skippedPerStream;\n\t\t\tTypes::UnwrappedTsn newCumulativeAck = this->lastCumulativeTsnAck;\n\t\t\tTypes::UnwrappedTsn tsn              = this->lastCumulativeTsnAck;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tif (\n\t\t\t\t  this->streamResetBreakpointTsns.contains(tsn) ||\n\t\t\t\t  (tsn != newCumulativeAck.GetNextValue()) || !item.IsAbandoned())\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tnewCumulativeAck = tsn;\n\n\t\t\t\tconst std::pair<bool /*unordered*/, uint16_t /*streamId*/> stream =\n\t\t\t\t  std::make_pair(item.GetData().IsUnordered(), item.GetData().GetStreamId());\n\n\t\t\t\tskippedPerStream[stream] = std::max(item.GetData().GetMessageId(), skippedPerStream[stream]);\n\t\t\t}\n\n\t\t\tauto* iForwardTsnChunk = packet->BuildChunkInPlace<IForwardTsnChunk>();\n\n\t\t\tiForwardTsnChunk->SetNewCumulativeTsn(newCumulativeAck.Wrap());\n\n\t\t\tfor (const auto& [stream, mid] : skippedPerStream)\n\t\t\t{\n\t\t\t\tconst uint16_t streamId = stream.second;\n\t\t\t\tconst bool unordered    = stream.first;\n\n\t\t\t\tiForwardTsnChunk->AddSkippedStream(\n\t\t\t\t  AnyForwardTsnChunk::SkippedStream{ unordered, streamId, mid });\n\t\t\t}\n\n\t\t\tiForwardTsnChunk->Consolidate();\n\n\t\t\treturn iForwardTsnChunk;\n\t\t}\n\n\t\tstd::optional<uint64_t> OutstandingData::MeasureRtt(uint64_t nowMs, Types::UnwrappedTsn tsn) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (tsn > this->lastCumulativeTsnAck && tsn < GetNextTsn())\n\t\t\t{\n\t\t\t\tconst Item& item = GetItem(tsn);\n\n\t\t\t\tif (!item.HasBeenRetransmitted())\n\t\t\t\t{\n\t\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.1\n\t\t\t\t\t//\n\t\t\t\t\t// \"Karn's algorithm: RTT measurements MUST NOT be made using packets\n\t\t\t\t\t// that were retransmitted (and thus for which it is ambiguous\n\t\t\t\t\t// whether the reply was for the first instance of the Chunk or for a\n\t\t\t\t\t// later instance)\"\n\t\t\t\t\treturn nowMs - item.GetTimeSentMs();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn std::nullopt;\n\t\t}\n\n\t\tbool OutstandingData::ShouldSendForwardTsn() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->outstandingData.empty())\n\t\t\t{\n\t\t\t\treturn this->outstandingData.front().IsAbandoned();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tvoid OutstandingData::BeginResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->streamResetBreakpointTsns.insert(GetNextTsn());\n\t\t}\n\n#ifdef MS_TEST\n\t\tstd::vector<\n\t\t  std::pair<uint32_t /*tsn*/, OutstandingData::State>> OutstandingData::GetChunkStatesForTesting() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, State>> states;\n\n\t\t\tstates.emplace_back(this->lastCumulativeTsnAck.Wrap(), State::ACKED);\n\n\t\t\tTypes::UnwrappedTsn tsn = this->lastCumulativeTsnAck;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tState state;\n\n\t\t\t\tif (item.IsAbandoned())\n\t\t\t\t{\n\t\t\t\t\tstate = State::ABANDONED;\n\t\t\t\t}\n\t\t\t\telse if (item.ShouldBeRetransmitted())\n\t\t\t\t{\n\t\t\t\t\tstate = State::TO_BE_RETRANSMITTED;\n\t\t\t\t}\n\t\t\t\telse if (item.IsAcked())\n\t\t\t\t{\n\t\t\t\t\tstate = State::ACKED;\n\t\t\t\t}\n\t\t\t\telse if (item.IsNacked())\n\t\t\t\t{\n\t\t\t\t\tstate = State::NACKED;\n\t\t\t\t}\n\t\t\t\telse if (item.IsOutstanding())\n\t\t\t\t{\n\t\t\t\t\tstate = State::IN_FLIGHT;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"should not end here\");\n\t\t\t\t}\n\n\t\t\t\tstates.emplace_back(tsn.Wrap(), state);\n\t\t\t}\n\n\t\t\treturn states;\n\t\t}\n#endif\n\n\t\tsize_t OutstandingData::GetSerializedChunkLength(const UserData& data) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn Utils::Byte::PadTo4Bytes<size_t>(this->dataChunkHeaderLength + data.GetPayloadLength());\n\t\t}\n\n\t\tOutstandingData::Item& OutstandingData::GetItem(Types::UnwrappedTsn tsn)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  tsn > this->lastCumulativeTsnAck, \"tsn must be higher than this->lastCumulativeTsnAck\");\n\t\t\tMS_ASSERT(tsn < GetNextTsn(), \"tsn must be higher than GetNextTsn()\");\n\n\t\t\tconst size_t index = Types::UnwrappedTsn::Difference(tsn, this->lastCumulativeTsnAck) - 1;\n\n\t\t\tMS_ASSERT(index >= 0, \"index must be equal or higher than 0\");\n\t\t\tMS_ASSERT(\n\t\t\t  index < this->outstandingData.size(),\n\t\t\t  \"index must be lower than this->outstandingData.size()\");\n\n\t\t\treturn this->outstandingData[index];\n\t\t}\n\n\t\tconst OutstandingData::Item& OutstandingData::GetItem(Types::UnwrappedTsn tsn) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  tsn > this->lastCumulativeTsnAck, \"tsn must be higher than this->lastCumulativeTsnAck\");\n\t\t\tMS_ASSERT(tsn < GetNextTsn(), \"tsn must be higher than GetNextTsn()\");\n\n\t\t\tconst size_t index = Types::UnwrappedTsn::Difference(tsn, this->lastCumulativeTsnAck) - 1;\n\n\t\t\tMS_ASSERT(index >= 0, \"index must be equal or higher than 0\");\n\t\t\tMS_ASSERT(\n\t\t\t  index < this->outstandingData.size(),\n\t\t\t  \"index must be lower than this->outstandingData.size()\");\n\n\t\t\treturn this->outstandingData[index];\n\t\t}\n\n\t\tvoid OutstandingData::RemoveAcked(Types::UnwrappedTsn cumulativeTsnAck, AckInfo& ackInfo)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\twhile (!this->outstandingData.empty() && this->lastCumulativeTsnAck < cumulativeTsnAck)\n\t\t\t{\n\t\t\t\tconst Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck.GetNextValue();\n\n\t\t\t\tItem& item = this->outstandingData.front();\n\n\t\t\t\tAckChunk(ackInfo, tsn, item);\n\n\t\t\t\tif (item.GetLifecycleId().has_value())\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(item.GetData().IsEnd(), \"item.GetData().IsEnd() must be true\");\n\n\t\t\t\t\tif (item.IsAbandoned())\n\t\t\t\t\t{\n\t\t\t\t\t\tackInfo.abandonedLifecycleIds.push_back(item.GetLifecycleId().value());\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tackInfo.ackedLifecycleIds.push_back(item.GetLifecycleId().value());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->outstandingData.pop_front();\n\t\t\t\tthis->lastCumulativeTsnAck.Increment();\n\t\t\t}\n\n\t\t\tthis->streamResetBreakpointTsns.erase(\n\t\t\t  this->streamResetBreakpointTsns.begin(),\n\t\t\t  this->streamResetBreakpointTsns.upper_bound(cumulativeTsnAck.GetNextValue()));\n\t\t}\n\n\t\tvoid OutstandingData::AckGapBlocks(\n\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t  AckInfo& ackInfo)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Mark all non-gaps as ACKED (but they can't be removed) as (from RFC)\n\t\t\t// \"SCTP considers the information carried in the Gap Ack Blocks in the\n\t\t\t// SACK Chunk as advisory\". Note that when NR-SACK is supported, this can\n\t\t\t// be handled differently.\n\n\t\t\tfor (const auto& block : gapAckBlocks)\n\t\t\t{\n\t\t\t\tconst Types::UnwrappedTsn start = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.start);\n\t\t\t\tconst Types::UnwrappedTsn end   = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end);\n\n\t\t\t\tfor (Types::UnwrappedTsn tsn = start; tsn <= end; tsn = tsn.GetNextValue())\n\t\t\t\t{\n\t\t\t\t\tif (tsn > this->lastCumulativeTsnAck && tsn < GetNextTsn())\n\t\t\t\t\t{\n\t\t\t\t\t\tItem& item = GetItem(tsn);\n\n\t\t\t\t\t\tAckChunk(ackInfo, tsn, item);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid OutstandingData::NackBetweenAckBlocks(\n\t\t  Types::UnwrappedTsn cumulativeTsnAck,\n\t\t  std::span<const SackChunk::GapAckBlock> gapAckBlocks,\n\t\t  bool isInFastRecovery,\n\t\t  bool cumulativeTsnAckedAdvanced,\n\t\t  AckInfo& ackInfo)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Mark everything between the blocks as NACKED/TO_BE_RETRANSMITTED.\n\t\t\t//\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"Mark the DATA chunk(s) with three miss indications for retransmission.\"\n\t\t\t// \"For each incoming SACK, miss indications are incremented only for\n\t\t\t// missing TSNs prior to the highest TSN newly acknowledged in the SACK.\"\n\t\t\t//\n\t\t\t// What this means is that only when there is a increasing stream of data\n\t\t\t// received and there are new packets seen (since last time), packets that\n\t\t\t// are in-flight and between gaps should be nacked. This means that SCTP\n\t\t\t// relies on the T3-RTX-timer to re-send packets otherwise.\n\t\t\tTypes::UnwrappedTsn maxTsnToNack = ackInfo.highestTsnAcked;\n\n\t\t\tif (isInFastRecovery && cumulativeTsnAckedAdvanced)\n\t\t\t{\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t\t//\n\t\t\t\t// \"If an endpoint is in Fast Recovery and a SACK arrives that advances\n\t\t\t\t// the Cumulative TSN Ack Point, the miss indications are incremented\n\t\t\t\t// for all TSNs reported missing in the SACK.\"\n\t\t\t\tmaxTsnToNack = Types::UnwrappedTsn::AddTo(\n\t\t\t\t  cumulativeTsnAck, gapAckBlocks.empty() ? 0 : gapAckBlocks.rbegin()->end);\n\t\t\t}\n\n\t\t\tTypes::UnwrappedTsn prevBlockLastAcked = cumulativeTsnAck;\n\n\t\t\tfor (const auto& block : gapAckBlocks)\n\t\t\t{\n\t\t\t\tconst Types::UnwrappedTsn curBlockFirstAcked =\n\t\t\t\t  Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.start);\n\n\t\t\t\tfor (Types::UnwrappedTsn tsn = prevBlockLastAcked.GetNextValue();\n\t\t\t\t     tsn < curBlockFirstAcked && tsn <= maxTsnToNack && tsn < GetNextTsn();\n\t\t\t\t     tsn = tsn.GetNextValue())\n\t\t\t\t{\n\t\t\t\t\tackInfo.hasPacketLoss |= NackItem(\n\t\t\t\t\t  tsn,\n\t\t\t\t\t  /*retransmitNow*/ false,\n\t\t\t\t\t  /*doFastRetransmit*/ !isInFastRecovery);\n\t\t\t\t}\n\n\t\t\t\tprevBlockLastAcked = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end);\n\t\t\t}\n\n\t\t\t// Note that packets are not NACKED which are above the highest\n\t\t\t// gap-ack-block (or above the cumulative ack TSN if no gap-ack-blocks)\n\t\t\t// as only packets up until the `highestTsnAcked` (see above) should be\n\t\t\t// considered when NACKing.\n\t\t}\n\n\t\tvoid OutstandingData::AckChunk(AckInfo& ackInfo, Types::UnwrappedTsn tsn, Item& item)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!item.IsAcked())\n\t\t\t{\n\t\t\t\tconst size_t serializedLength = GetSerializedChunkLength(item.GetData());\n\n\t\t\t\tackInfo.bytesAcked += serializedLength;\n\n\t\t\t\tif (item.IsOutstanding())\n\t\t\t\t{\n\t\t\t\t\tthis->unackedPayloadBytes -= item.GetData().GetPayloadLength();\n\t\t\t\t\tthis->unackedPacketBytes -= serializedLength;\n\t\t\t\t\t--this->unackedItems;\n\t\t\t\t}\n\n\t\t\t\tif (item.ShouldBeRetransmitted())\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  !this->toBeFastRetransmitted.contains(tsn),\n\t\t\t\t\t  \"tsn should not be present in this->toBeFastRetransmitted\");\n\n\t\t\t\t\tthis->toBeRetransmitted.erase(tsn);\n\t\t\t\t}\n\n\t\t\t\titem.Ack();\n\n\t\t\t\tackInfo.highestTsnAcked = std::max(ackInfo.highestTsnAcked, tsn);\n\t\t\t}\n\t\t}\n\n\t\tbool OutstandingData::NackItem(Types::UnwrappedTsn tsn, bool retransmitNow, bool doFastRetransmit)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tItem& item = GetItem(tsn);\n\n\t\t\t// Ignore NACKs for chunks that have already been acknowledged.\n\t\t\tif (item.IsAcked())\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst bool wasOutstanding     = item.IsOutstanding();\n\t\t\tconst Item::NackAction action = item.Nack(retransmitNow);\n\n\t\t\tif (wasOutstanding && !item.IsOutstanding())\n\t\t\t{\n\t\t\t\tthis->unackedPayloadBytes -= item.GetData().GetPayloadLength();\n\t\t\t\tthis->unackedPacketBytes -= GetSerializedChunkLength(item.GetData());\n\t\t\t\t--this->unackedItems;\n\t\t\t}\n\n\t\t\tswitch (action)\n\t\t\t{\n\t\t\t\tcase Item::NackAction::NOTHING:\n\t\t\t\t{\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tcase Item::NackAction::RETRANSMIT:\n\t\t\t\t{\n\t\t\t\t\tif (doFastRetransmit)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->toBeFastRetransmitted.insert(tsn);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->toBeRetransmitted.insert(tsn);\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(sctp, \"tsn %\" PRIu32 \" marked for retransmission\", tsn.Wrap());\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Item::NackAction::ABANDON:\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(sctp, \"tsn %\" PRIu32 \" nacked, resulted in abandoning\", tsn.Wrap());\n\n\t\t\t\t\tAbandonAllFor(item);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid OutstandingData::AbandonAllFor(const OutstandingData::Item& item)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Erase all remaining chunks from the producer, if any.\n\t\t\tif (this->discardFromSendQueue(item.GetData().GetStreamId(), item.GetOutgoingMessageId()))\n\t\t\t{\n\t\t\t\t// There were remaining chunks to be produced for this message. Since the\n\t\t\t\t// receiver may have already received all chunks (up till now) for this\n\t\t\t\t// message, we can't just FORWARD-TSN to the last fragment in this\n\t\t\t\t// (abandoned) message and start sending a new message, as the receiver will\n\t\t\t\t// then see a new message before the end of the previous one was seen (or\n\t\t\t\t// skipped over). So create a new fragment, representing the end, that the\n\t\t\t\t// received will never see as it is abandoned immediately and used as cum\n\t\t\t\t// TSN in the sent FORWARD-TSN.\n\t\t\t\tUserData messageEnd(\n\t\t\t\t  item.GetData().GetStreamId(),\n\t\t\t\t  item.GetData().GetStreamSequenceNumber(),\n\t\t\t\t  item.GetData().GetMessageId(),\n\t\t\t\t  item.GetData().GetFragmentSequenceNumber(),\n\t\t\t\t  item.GetData().GetPayloadProtocolId(),\n\t\t\t\t  std::vector<uint8_t>(),\n\t\t\t\t  /*isBeginning*/ false,\n\t\t\t\t  /*isEnd*/ true,\n\t\t\t\t  /*isUnordered*/ item.GetData().IsUnordered());\n\n\t\t\t\tconst Types::UnwrappedTsn tsn = GetNextTsn();\n\n\t\t\t\tItem& addedItem = this->outstandingData.emplace_back(\n\t\t\t\t  item.GetOutgoingMessageId(),\n\t\t\t\t  std::move(messageEnd),\n\t\t\t\t  /*timeSentMs*/ 0,\n\t\t\t\t  /*maxRetransmissions*/ 0,\n\t\t\t\t  /*expiresAtMs*/ Types::ExpiresAtMsInfinite,\n\t\t\t\t  /*lifecycleId*/ std::nullopt);\n\n\t\t\t\t// The added Chunk shouldn't be included in `this->unackedPacketBytes`,\n\t\t\t\t// so set it as acked.\n\t\t\t\taddedItem.Ack();\n\n\t\t\t\tMS_DEBUG_TAG(sctp, \"adding unsent end placeholder for message at TSN %\" PRIu32, tsn.Wrap());\n\t\t\t}\n\n\t\t\tTypes::UnwrappedTsn tsn = this->lastCumulativeTsnAck;\n\n\t\t\tfor (Item& other : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tif (\n\t\t\t\t  !other.IsAbandoned() && other.GetData().GetStreamId() == item.GetData().GetStreamId() &&\n\t\t\t\t  other.GetOutgoingMessageId() == item.GetOutgoingMessageId())\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(sctp, \"marking Chunk %\" PRIu32 \" as abandoned\", tsn.Wrap());\n\n\t\t\t\t\tif (other.ShouldBeRetransmitted())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->toBeFastRetransmitted.erase(tsn);\n\t\t\t\t\t\tthis->toBeRetransmitted.erase(tsn);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst bool wasOutstanding = other.IsOutstanding();\n\n\t\t\t\t\tother.Abandon();\n\n\t\t\t\t\tif (wasOutstanding)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->unackedPayloadBytes -= other.GetData().GetPayloadLength();\n\t\t\t\t\t\tthis->unackedPacketBytes -= GetSerializedChunkLength(other.GetData());\n\t\t\t\t\t\t--this->unackedItems;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> OutstandingData::ExtractChunksThatCanFit(\n\t\t  std::set<Types::UnwrappedTsn>& chunks, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> result;\n\n\t\t\tfor (auto it = chunks.begin(); it != chunks.end();)\n\t\t\t{\n\t\t\t\tconst Types::UnwrappedTsn tsn = *it;\n\n\t\t\t\tItem& item = GetItem(tsn);\n\n\t\t\t\tMS_ASSERT(item.ShouldBeRetransmitted(), \"item should be retransmitted\");\n\t\t\t\tMS_ASSERT(!item.IsOutstanding(), \"item should not be outstanding\");\n\t\t\t\tMS_ASSERT(!item.IsAbandoned(), \"item should not be abandoned\");\n\t\t\t\tMS_ASSERT(!item.IsAcked(), \"item should not be acked\");\n\n\t\t\t\tconst size_t serializedLength = GetSerializedChunkLength(item.GetData());\n\n\t\t\t\tif (serializedLength <= maxLength)\n\t\t\t\t{\n\t\t\t\t\titem.MarkAsRetransmitted();\n\t\t\t\t\tresult.emplace_back(tsn.Wrap(), item.GetData().Clone());\n\t\t\t\t\tmaxLength -= serializedLength;\n\n\t\t\t\t\tthis->unackedPayloadBytes += item.GetData().GetPayloadLength();\n\t\t\t\t\tthis->unackedPacketBytes += serializedLength;\n\t\t\t\t\t++this->unackedItems;\n\n\t\t\t\t\tit = chunks.erase(it);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++it;\n\t\t\t\t}\n\n\t\t\t\t// No point in continuing if the packet is full.\n\t\t\t\tif (maxLength <= this->dataChunkHeaderLength)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tvoid OutstandingData::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t actualUnackedPayloadBytes{ 0 };\n\t\t\tsize_t actualUnackedPacketBytes{ 0 };\n\t\t\tsize_t actualUnackedItems{ 0 };\n\n\t\t\tstd::set<Types::UnwrappedTsn> combinedToBeRetransmitted;\n\n\t\t\tcombinedToBeRetransmitted.insert(this->toBeRetransmitted.begin(), this->toBeRetransmitted.end());\n\t\t\tcombinedToBeRetransmitted.insert(\n\t\t\t  this->toBeFastRetransmitted.begin(), this->toBeFastRetransmitted.end());\n\n\t\t\tstd::set<Types::UnwrappedTsn> actualCombinedToBeRetransmitted;\n\t\t\tTypes::UnwrappedTsn tsn = this->lastCumulativeTsnAck;\n\n\t\t\tfor (const Item& item : this->outstandingData)\n\t\t\t{\n\t\t\t\ttsn.Increment();\n\n\t\t\t\tif (item.IsOutstanding())\n\t\t\t\t{\n\t\t\t\t\tactualUnackedPayloadBytes += item.GetData().GetPayloadLength();\n\t\t\t\t\tactualUnackedPacketBytes += GetSerializedChunkLength(item.GetData());\n\t\t\t\t\t++actualUnackedItems;\n\t\t\t\t}\n\n\t\t\t\tif (item.ShouldBeRetransmitted())\n\t\t\t\t{\n\t\t\t\t\tactualCombinedToBeRetransmitted.insert(tsn);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  actualUnackedPayloadBytes == this->unackedPayloadBytes,\n\t\t\t  \"actualUnackedPayloadBytes != this->unackedPayloadBytes\");\n\t\t\tMS_ASSERT(\n\t\t\t  actualUnackedPacketBytes == this->unackedPacketBytes,\n\t\t\t  \"actualUnackedPacketBytes != this->unackedPacketBytes\");\n\t\t\tMS_ASSERT(actualUnackedItems == this->unackedItems, \"actualUnackedItems != this->unackedItems\");\n\t\t\tMS_ASSERT(\n\t\t\t  actualCombinedToBeRetransmitted == combinedToBeRetransmitted,\n\t\t\t  \"actualCombinedToBeRetransmitted != combinedToBeRetransmitted\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/RetransmissionErrorCounter.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::RetransmissionErrorCounter\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/RetransmissionErrorCounter.hpp\"\n#include \"Logger.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tRetransmissionErrorCounter::RetransmissionErrorCounter(const SctpOptions& sctpOptions)\n\t\t  : limit(sctpOptions.maxRetransmissions)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid RetransmissionErrorCounter::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::RetransmissionErrorCounter>\");\n\n\t\t\tMS_DUMP_CLEAN(\n\t\t\t  indentation,\n\t\t\t  \"  limit: %s\",\n\t\t\t  this->limit ? std::to_string(this->limit.value()).c_str() : \"Infinite\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  counter: %zu\", this->counter);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  exhausted: %s\", IsExhausted() ? \"yes\" : \"no\");\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::RetransmissionErrorCounter>\");\n\t\t}\n\n\t\tRetransmissionErrorCounter::~RetransmissionErrorCounter()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tbool RetransmissionErrorCounter::Increment(std::string_view reason)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->counter++;\n\n\t\t\tif (IsExhausted())\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"too many retransmissions [counter:%zu, limit:%s]: %.*s\",\n\t\t\t\t  this->counter,\n\t\t\t\t  this->limit ? std::to_string(this->limit.value()).c_str() : \"Infinite\",\n\t\t\t\t  static_cast<int>(reason.size()),\n\t\t\t\t  reason.data());\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"%.*s [counter:%zu, limit:%s]\",\n\t\t\t  static_cast<int>(reason.size()),\n\t\t\t  reason.data(),\n\t\t\t  this->counter,\n\t\t\t  this->limit ? std::to_string(*this->limit).c_str() : \"Infinite\");\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid RetransmissionErrorCounter::Clear()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->counter > 0)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"recovered from counter %zu\", this->counter);\n\n\t\t\t\tthis->counter = 0;\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/RetransmissionQueue.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::RetransmissionQueue\"\n// TODO: SCTP: Comment.\n#define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/RetransmissionQueue.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <cmath>   // std::min()\n#include <numeric> // std::accumulate()\n#include <string>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tRetransmissionQueue::RetransmissionQueue(\n\t\t  Listener* listener,\n\t\t  AssociationListenerInterface& associationListener,\n\t\t  uint32_t localInitialTsn,\n\t\t  uint32_t remoteAdvertisedReceiverWindowCredit,\n\t\t  SendQueueInterface& sendQueue,\n\t\t  BackoffTimerHandleInterface* t3RtxTimer,\n\t\t  const SctpOptions& sctpOptions,\n\t\t  // NOTE: I don't like default argument values in dcsctp (true and false),\n\t\t  // let's be explicit.\n\t\t  bool supportsPartialReliability,\n\t\t  bool useMessageInterleaving)\n\t\t  : listener(listener),\n\t\t    associationListener(associationListener),\n\t\t    sctpOptions(sctpOptions),\n\t\t    supportsPartialReliability(supportsPartialReliability),\n\t\t    dataChunkHeaderLength(\n\t\t      useMessageInterleaving ? IDataChunk::IDataChunkHeaderLength\n\t\t                             : DataChunk::DataChunkHeaderLength),\n\t\t    t3RtxTimer(t3RtxTimer),\n\t\t    cwnd(sctpOptions.initialCwndMtus * sctpOptions.mtu),\n\t\t    rwnd(remoteAdvertisedReceiverWindowCredit),\n\t\t    // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.1\n\t\t    //\n\t\t    // \"The initial value of ssthresh MAY be arbitrarily high (for example,\n\t\t    // implementations MAY use the size of the receiver advertised window).\"\n\t\t    ssthresh(this->rwnd),\n\t\t    sendQueue(sendQueue),\n\t\t    outstandingData(\n\t\t      this->dataChunkHeaderLength,\n\t\t      this->tsnUnwrapper.Unwrap(localInitialTsn - 1),\n\n\t\t      [this](uint16_t streamId, uint32_t outgoingMessageId)\n\t\t      {\n\t\t\t      return this->sendQueue.Discard(streamId, outgoingMessageId);\n\t\t      })\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tRetransmissionQueue::~RetransmissionQueue()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tbool RetransmissionQueue::HandleReceivedSackChunk(uint64_t nowMs, const SackChunk* receivedSackChunk)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!IsSackChunkValid(receivedSackChunk))\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst Types::UnwrappedTsn oldLastCumulativeTsnAck =\n\t\t\t  this->outstandingData.GetLastCumulativeTsnAck();\n\t\t\tconst size_t oldUnackedPacketBytes = this->outstandingData.GetUnackedPacketBytes();\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tconst size_t oldRwnd = this->rwnd;\n#endif\n\t\t\tconst Types::UnwrappedTsn cumulativeTsnAck =\n\t\t\t  this->tsnUnwrapper.Unwrap(receivedSackChunk->GetCumulativeTsnAck());\n\n\t\t\tif (receivedSackChunk->GetValidatedGapAckBlocks().empty())\n\t\t\t{\n\t\t\t\tUpdateRttMs(nowMs, cumulativeTsnAck);\n\t\t\t}\n\n\t\t\t// Exit fast recovery before continuing processing, in case it needs to go\n\t\t\t// into fast recovery again due to new reported packet loss.\n\t\t\tMayExitFastRecovery(cumulativeTsnAck);\n\n\t\t\tconst OutstandingData::AckInfo ackInfo = this->outstandingData.HandleSack(\n\t\t\t  cumulativeTsnAck, receivedSackChunk->GetValidatedGapAckBlocks(), IsInFastRecovery());\n\n\t\t\t// Add lifecycle events for delivered messages.\n\t\t\tfor (const uint64_t lifecycleId : ackInfo.ackedLifecycleIds)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"triggering OnAssociationLifecycleMessageDelivered() [lifecycleId:%\" PRIu64 \"]\",\n\t\t\t\t  lifecycleId);\n\n\t\t\t\tthis->associationListener.OnAssociationLifecycleMessageDelivered(lifecycleId);\n\t\t\t\tthis->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId);\n\t\t\t}\n\n\t\t\t// Add lifecycle events for abandoned messages.\n\t\t\tfor (const uint64_t lifecycleId : ackInfo.abandonedLifecycleIds)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"triggering OnLifecycleMessageExpired() [lifecycleId:%\" PRIu64 \", maybeDelivered:true]\",\n\t\t\t\t  lifecycleId);\n\n\t\t\t\tthis->associationListener.OnAssociationLifecycleMessageExpired(\n\t\t\t\t  lifecycleId, /*maybeDelivered*/ true);\n\t\t\t\tthis->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId);\n\t\t\t}\n\n\t\t\t// Update of this->outstandingData is now done. Congestion control remains.\n\t\t\tUpdateReceiverWindow(receivedSackChunk->GetAdvertisedReceiverWindowCredit());\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"received SACK [cumulativeTsnAck:%\" PRIu32 \", oldLastCumulativeTsnAck:%\" PRIu32\n\t\t\t  \", unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, rwnd:%zu, oldRwnd:%zu]\",\n\t\t\t  cumulativeTsnAck.Wrap(),\n\t\t\t  oldLastCumulativeTsnAck.Wrap(),\n\t\t\t  this->outstandingData.GetUnackedPacketBytes(),\n\t\t\t  oldUnackedPacketBytes,\n\t\t\t  this->rwnd,\n\t\t\t  oldRwnd);\n\n\t\t\tif (cumulativeTsnAck > oldLastCumulativeTsnAck)\n\t\t\t{\n\t\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2\n\t\t\t\t//\n\t\t\t\t// \"Whenever a SACK is received that acknowledges the DATA chunk with\n\t\t\t\t// the earliest outstanding TSN for that address, restart the T3-rtx\n\t\t\t\t// timer for that address with its current RTO (if there is still\n\t\t\t\t// outstanding data on that address).\"\n\t\t\t\tthis->t3RtxTimer->Stop();\n\n\t\t\t\tHandleIncreasedCumulativeTsnAck(oldUnackedPacketBytes, ackInfo.bytesAcked);\n\t\t\t}\n\n\t\t\tif (ackInfo.hasPacketLoss)\n\t\t\t{\n\t\t\t\tHandlePacketLoss(ackInfo.highestTsnAcked);\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-8.2\n\t\t\t//\n\t\t\t// \"When an outstanding TSN is acknowledged [...] the endpoint shall clear\n\t\t\t// the error counter ...\".\n\t\t\tif (ackInfo.bytesAcked > 0)\n\t\t\t{\n\t\t\t\tthis->listener->OnRetransmissionQueueClearRetransmissionCounter();\n\t\t\t}\n\n\t\t\tStartT3RtxTimerIfOutstandingData();\n\n\t\t\treturn true;\n\t\t}\n\n\t\tvoid RetransmissionQueue::HandleT3RtxTimerExpiry()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst size_t oldCwnd               = this->cwnd;\n\t\t\tconst size_t oldUnackedPacketBytes = GetUnackedPacketBytes();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3\n\t\t\t//\n\t\t\t// \"For the destination address for which the timer expires, adjust\n\t\t\t// its ssthresh with rules defined in Section 7.2.3 and set the cwnd\n\t\t\t// <- MTU.\"\n\t\t\tthis->ssthresh = std::max(this->cwnd / 2, 4 * this->sctpOptions.mtu);\n\t\t\tthis->cwnd     = 1 * this->sctpOptions.mtu;\n\n\t\t\t// Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.11\n\t\t\tthis->partialBytesAcked = 0;\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3\n\t\t\t//\n\t\t\t// \"For the destination address for which the timer expires, set RTO\n\t\t\t// <- RTO * 2 (\"back off the timer\").  The maximum value discussed in\n\t\t\t// rule C7 above (RTO.max) may be used to provide an upper bound to this\n\t\t\t// doubling operation.\"\n\n\t\t\t// Already done by the BackoffTimerHandle implementation.\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3\n\t\t\t//\n\t\t\t// \"Determine how many of the earliest (i.e., lowest TSN) outstanding\n\t\t\t// DATA chunks for the address for which the T3-rtx has expired will fit\n\t\t\t// into a single Packet\"\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3\n\t\t\t//\n\t\t\t// \"Note: Any DATA chunks that were sent to the address for which the\n\t\t\t// T3-rtx timer expired but did not fit in one MTU (rule E3 above) should\n\t\t\t// be marked for retransmission and sent as soon as cwnd allows (normally,\n\t\t\t// when a SACK arrives).\"\n\t\t\tthis->outstandingData.NackAll();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3\n\t\t\t//\n\t\t\t// \"Start the retransmission timer T3-rtx on the destination address to\n\t\t\t// which the retransmission is sent, if rule R1 above indicates to do so.\"\n\n\t\t\t// Already done by the BackoffTimerHandle implementation.\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  sctp,\n\t\t\t  \"%s timer has expired [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]\",\n\t\t\t  this->t3RtxTimer->GetLabel().c_str(),\n\t\t\t  this->cwnd,\n\t\t\t  oldCwnd,\n\t\t\t  this->ssthresh,\n\t\t\t  GetUnackedPacketBytes(),\n\t\t\t  oldUnackedPacketBytes);\n\t\t}\n\n\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> RetransmissionQueue::GetChunksForFastRetransmit(\n\t\t  size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->outstandingData.HasDataToBeFastRetransmitted(), \"no data to be fast-retransmitted\");\n\t\t\tMS_ASSERT(\n\t\t\t  Utils::Byte::IsPaddedTo4Bytes(maxLength),\n\t\t\t  \"given maxLength %zu is not divisible by 4\",\n\t\t\t  maxLength);\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> toBeSent;\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tconst size_t oldUnackedPacketBytes = GetUnackedPacketBytes();\n#endif\n\n\t\t\ttoBeSent = this->outstandingData.GetChunksToBeFastRetransmitted(maxLength);\n\n\t\t\tMS_ASSERT(!toBeSent.empty(), \"toBeSent cannot be empty\");\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"4)  Restart the T3-rtx timer only if ... the endpoint is retransmitting\n\t\t\t// the first outstanding DATA chunk sent to that address.\"\n\t\t\tif (toBeSent[0].first == this->outstandingData.GetLastCumulativeTsnAck().GetNextValue().Wrap())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"first outstanding data to be retransmitted, restarting T3-rtx timer\");\n\n\t\t\t\tthis->t3RtxTimer->Stop();\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2\n\t\t\t//\n\t\t\t// \"Every time a DATA chunk is sent to any address (including a\n\t\t\t// retransmission), if the T3-rtx timer of that address is not running,\n\t\t\t// start it running so that it will expire after the RTO of that address.\"\n\t\t\tif (!this->t3RtxTimer->IsRunning())\n\t\t\t{\n\t\t\t\tthis->t3RtxTimer->Start();\n\t\t\t}\n\n\t\t\tconst size_t bytesRetransmitted = std::accumulate(\n\t\t\t  toBeSent.begin(),\n\t\t\t  toBeSent.end(),\n\t\t\t  size_t{ 0 },\n\t\t\t  [&](size_t r, const std::pair<uint32_t /*tsn*/, UserData>& data)\n\t\t\t  {\n\t\t\t\t  return r + GetSerializedChunkLength(data.second);\n\t\t\t  });\n\n\t\t\t++this->rtxPacketsCount;\n\t\t\tthis->rtxBytesCount += bytesRetransmitted;\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tstd::string tsnList;\n\n\t\t\tfor (const auto& [tsn, data] : toBeSent)\n\t\t\t{\n\t\t\t\tif (!tsnList.empty())\n\t\t\t\t{\n\t\t\t\t\ttsnList += ',';\n\t\t\t\t}\n\n\t\t\t\ttsnList += std::to_string(tsn);\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"fast-retransmitting TSN %s (%zu bytes) [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]\",\n\t\t\t  tsnList.c_str(),\n\t\t\t  bytesRetransmitted,\n\t\t\t  GetUnackedPacketBytes(),\n\t\t\t  oldUnackedPacketBytes);\n#endif\n\n\t\t\treturn toBeSent;\n\t\t}\n\n\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> RetransmissionQueue::GetChunksToSend(\n\t\t  uint64_t nowMs, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  Utils::Byte::IsPaddedTo4Bytes(maxLength),\n\t\t\t  \"given maxLength %zu is not divisible by 4\",\n\t\t\t  maxLength);\n\n\t\t\tstd::vector<std::pair<uint32_t /*tsn*/, UserData>> toBeSent;\n\n\t\t\tconst size_t oldUnackedPacketBytes = GetUnackedPacketBytes();\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tconst size_t oldRwnd = this->rwnd;\n#endif\n\n\t\t\t// Calculate the bandwidth budget (how many bytes that is allowed to be\n\t\t\t// sent).\n\t\t\tconst size_t maxPacketBytesAllowedByCwnd =\n\t\t\t  oldUnackedPacketBytes >= this->cwnd ? 0 : this->cwnd - oldUnackedPacketBytes;\n\n\t\t\tsize_t maxPacketBytesAllowedByRwnd =\n\t\t\t  Utils::Byte::PadTo4Bytes(GetRwnd() + this->dataChunkHeaderLength);\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.1\n\t\t\t//\n\t\t\t// \"However, regardless of the value of rwnd (including if it is 0), the\n\t\t\t// data sender can always have one DATA chunk in flight to the receiver if\n\t\t\t// allowed by cwnd (see rule B, below).\"\n\t\t\tif (this->outstandingData.GetUnackedItems() == 0)\n\t\t\t{\n\t\t\t\tmaxPacketBytesAllowedByRwnd = this->sctpOptions.mtu;\n\t\t\t}\n\n\t\t\tsize_t maxBytes = Utils::Byte::PadDownTo4Bytes(\n\t\t\t  std::min({ maxPacketBytesAllowedByCwnd, maxPacketBytesAllowedByRwnd, maxLength }));\n\n\t\t\ttoBeSent = this->outstandingData.GetChunksToBeRetransmitted(maxBytes);\n\n\t\t\tconst size_t bytesRetransmitted = std::accumulate(\n\t\t\t  toBeSent.begin(),\n\t\t\t  toBeSent.end(),\n\t\t\t  size_t{ 0 },\n\t\t\t  [&](size_t r, const std::pair<uint32_t /*tsn*/, UserData>& data)\n\t\t\t  {\n\t\t\t\t  return r + GetSerializedChunkLength(data.second);\n\t\t\t  });\n\n\t\t\tmaxBytes -= bytesRetransmitted;\n\n\t\t\tif (!toBeSent.empty())\n\t\t\t{\n\t\t\t\t++this->rtxPacketsCount;\n\t\t\t\tthis->rtxBytesCount += bytesRetransmitted;\n\t\t\t}\n\n\t\t\twhile (maxBytes > this->dataChunkHeaderLength)\n\t\t\t{\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  Utils::Byte::IsPaddedTo4Bytes(maxBytes),\n\t\t\t\t  \"computed maxBytes %zu during the loop is not divisible by 4\",\n\t\t\t\t  maxBytes);\n\n\t\t\t\tstd::optional<SendQueueInterface::DataToSend> dataToSend =\n\t\t\t\t  this->sendQueue.Produce(nowMs, maxBytes - this->dataChunkHeaderLength);\n\n\t\t\t\tif (!dataToSend.has_value())\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tconst size_t chunkSize = GetSerializedChunkLength(dataToSend->data);\n\n\t\t\t\tmaxBytes -= chunkSize;\n\n\t\t\t\tthis->rwnd -= dataToSend->data.GetPayloadLength();\n\n\t\t\t\tconst std::optional<Types::UnwrappedTsn> tsn = this->outstandingData.Insert(\n\t\t\t\t  dataToSend->outgoingMessageId,\n\t\t\t\t  dataToSend->data,\n\t\t\t\t  nowMs,\n\t\t\t\t  this->supportsPartialReliability ? dataToSend->maxRetransmissions\n\t\t\t\t                                   : Types::MaxRetransmitsNoLimit,\n\t\t\t\t  this->supportsPartialReliability ? dataToSend->expiresAtMs : Types::ExpiresAtMsInfinite,\n\t\t\t\t  dataToSend->lifecycleId);\n\n\t\t\t\tif (tsn.has_value())\n\t\t\t\t{\n\t\t\t\t\tif (dataToSend->lifecycleId.has_value())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_ASSERT(dataToSend->data.IsEnd(), \"data.IsEnd() should return true\");\n\n\t\t\t\t\t\tthis->associationListener.OnAssociationLifecycleMessageFullySent(\n\t\t\t\t\t\t  dataToSend->lifecycleId.value());\n\t\t\t\t\t}\n\n\t\t\t\t\ttoBeSent.emplace_back(tsn->Wrap(), std::move(dataToSend->data));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.3.2\n\t\t\t//\n\t\t\t// \"Every time a DATA chunk is sent to any address (including a\n\t\t\t// retransmission), if the T3-rtx timer of that address is not running,\n\t\t\t// start it running so that it will expire after the RTO of that address.\"\n\t\t\tif (!toBeSent.empty())\n\t\t\t{\n\t\t\t\tif (!this->t3RtxTimer->IsRunning())\n\t\t\t\t{\n\t\t\t\t\tthis->t3RtxTimer->Start();\n\t\t\t\t}\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t\tstd::string tsnList;\n\n\t\t\t\tfor (const auto& [tsn, data] : toBeSent)\n\t\t\t\t{\n\t\t\t\t\tif (!tsnList.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\ttsnList += ',';\n\t\t\t\t\t}\n\n\t\t\t\t\ttsnList += std::to_string(tsn);\n\t\t\t\t}\n\n\t\t\t\tconst size_t bytesRetransmitted = std::accumulate(\n\t\t\t\t  toBeSent.begin(),\n\t\t\t\t  toBeSent.end(),\n\t\t\t\t  size_t{ 0 },\n\t\t\t\t  [&](size_t r, const std::pair<uint32_t, UserData>& d)\n\t\t\t\t  {\n\t\t\t\t\t  return r + GetSerializedChunkLength(d.second);\n\t\t\t\t  });\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"sending TSN %s (%zu bytes) [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, cwnd:%zu, rwnd:%zu, oldRwnd:%zu]\",\n\t\t\t\t  tsnList.c_str(),\n\t\t\t\t  bytesRetransmitted,\n\t\t\t\t  GetUnackedPacketBytes(),\n\t\t\t\t  oldUnackedPacketBytes,\n\t\t\t\t  cwnd,\n\t\t\t\t  rwnd,\n\t\t\t\t  oldRwnd);\n#endif\n\t\t\t}\n\n\t\t\treturn toBeSent;\n\t\t}\n\n\t\tbool RetransmissionQueue::ShouldSendForwardTsn(uint64_t nowMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (!this->supportsPartialReliability)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis->outstandingData.ExpireOutstandingChunks(nowMs);\n\n\t\t\treturn this->outstandingData.ShouldSendForwardTsn();\n\t\t}\n\n\t\tvoid RetransmissionQueue::PrepareResetStream(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// TODO: As per TODO comment in same method in dcsctp:\n\t\t\t//\n\t\t\t// These calls are now only affecting the send queue. The packet buffer\n\t\t\t// can also change behavior - for example draining the chunk producer and\n\t\t\t// eagerly assign TSNs so that an \"Outgoing SSN Reset Request\" can be sent\n\t\t\t// quickly, with a known sender last assigned TSN.\n\n\t\t\tthis->sendQueue.PrepareResetStream(streamId);\n\t\t}\n\n\t\tbool RetransmissionQueue::HasStreamsReadyToBeReset() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->sendQueue.HasStreamsReadyToBeReset();\n\t\t}\n\n\t\tstd::vector<uint16_t /*streamId*/> RetransmissionQueue::BeginResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->outstandingData.BeginResetStreams();\n\n\t\t\treturn this->sendQueue.GetStreamsReadyToBeReset();\n\t\t}\n\n\t\tvoid RetransmissionQueue::CommitResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sendQueue.CommitResetStreams();\n\t\t}\n\n\t\tvoid RetransmissionQueue::RollbackResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->sendQueue.RollbackResetStreams();\n\t\t}\n\n\t\tsize_t RetransmissionQueue::GetSerializedChunkLength(const UserData& data) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn Utils::Byte::PadTo4Bytes(this->dataChunkHeaderLength + data.GetPayloadLength());\n\t\t}\n\n\t\tbool RetransmissionQueue::IsSackChunkValid(const SackChunk* sackChunk) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.2.1\n\t\t\t//\n\t\t\t// \"If Cumulative TSN Ack is less than the Cumulative TSN Ack Point, then\n\t\t\t// drop the SACK. Since Cumulative TSN Ack is monotonically increasing, a\n\t\t\t// SACK whose Cumulative TSN Ack is less than the Cumulative TSN Ack Point\n\t\t\t// indicates an out-of- order SACK.\"\n\t\t\t//\n\t\t\t// @remarks\n\t\t\t// - Important not to drop SACKs with identical TSN to that previously\n\t\t\t//   received, as the gap-ack-blocks or dup tsn fields may have changed.\n\t\t\tconst Types::UnwrappedTsn cumulativeTsnAck =\n\t\t\t  this->tsnUnwrapper.PeekUnwrap(sackChunk->GetCumulativeTsnAck());\n\n\t\t\tif (cumulativeTsnAck < this->outstandingData.GetLastCumulativeTsnAck())\n\t\t\t{\n\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.2.1\n\t\t\t\t//\n\t\t\t\t// \"If Cumulative TSN Ack is less than the Cumulative TSN Ack Point,\n\t\t\t\t// then drop the SACK.  Since Cumulative TSN Ack is monotonically\n\t\t\t\t// increasing, a SACK whose Cumulative TSN Ack is less than the\n\t\t\t\t// Cumulative TSN Ack Point indicates an out-of- order SACK.\"\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\telse if (cumulativeTsnAck > this->outstandingData.GetHighestOutstandingTsn())\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn std::ranges::all_of(\n\t\t\t  sackChunk->GetGapAckBlocks(),\n\t\t\t  [&](const auto& block)\n\t\t\t  {\n\t\t\t\t  return Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end) <=\n\t\t\t\t         this->outstandingData.GetHighestOutstandingTsn();\n\t\t\t  });\n\t\t}\n\n\t\tvoid RetransmissionQueue::UpdateRttMs(uint64_t nowMs, Types::UnwrappedTsn cumulativeTsnAck)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// RTT updating is flawed in SCTP, as explained in e.g. Pedersen J, Griwodz C,\n\t\t\t// Halvorsen P (2006) Considerations of SCTP retransmission delays for thin\n\t\t\t// streams.\n\t\t\t// Due to delayed acknowledgement, the SACK may be sent much later which\n\t\t\t// increases the calculated RTT.\n\t\t\t//\n\t\t\t// TODO: As per TODO comment in same method in dcsctp:\n\t\t\t//\n\t\t\t// Consider occasionally sending DATA chunks with I-bit set and use only\n\t\t\t// those packets for measurement.\n\n\t\t\tconst auto rttMs = this->outstandingData.MeasureRtt(nowMs, cumulativeTsnAck);\n\n\t\t\tif (rttMs.has_value())\n\t\t\t{\n\t\t\t\tthis->listener->OnRetransmissionQueueNewRttMs(rttMs.value());\n\t\t\t}\n\t\t}\n\n\t\tvoid RetransmissionQueue::MayExitFastRecovery(Types::UnwrappedTsn cumulativeTsnAck)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"When a SACK acknowledges all TSNs up to and including this [fast\n\t\t\t// recovery] exit point, Fast Recovery is exited.\"\n\t\t\tif (this->fastRecoveryExitTsn.has_value() && cumulativeTsnAck >= this->fastRecoveryExitTsn.value())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"exit point %\" PRIu32 \" reached, exiting fast recovery\",\n\t\t\t\t  this->fastRecoveryExitTsn.value().Wrap());\n\n\t\t\t\tthis->fastRecoveryExitTsn = std::nullopt;\n\t\t\t}\n\t\t}\n\n\t\tvoid RetransmissionQueue::StopT3RtxTimerOnIncreasedCumulativeTsnAck(\n\t\t  Types::UnwrappedTsn /*cumulativeTsnAck*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// TODO: This method is NOT implemented in dcsctp!\n\t\t\t//\n\t\t\t// @see https://issues.webrtc.org/issues/505751236\n\t\t}\n\n\t\tvoid RetransmissionQueue::HandleIncreasedCumulativeTsnAck(\n\t\t  size_t unackedPacketBytes, size_t totalBytesAcked)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Allow some margin for classifying as fully utilized, due to e.g. that\n\t\t\t// too small packets (less than kMinimumFragmentedPayload) are not sent +\n\t\t\t// overhead.\n\t\t\tconst bool isFullyUtilized = unackedPacketBytes + this->sctpOptions.mtu >= this->cwnd;\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\tconst size_t oldCwnd = this->cwnd;\n#endif\n\n\t\t\tif (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::SLOW_START)\n\t\t\t{\n\t\t\t\tif (isFullyUtilized && !IsInFastRecovery())\n\t\t\t\t{\n\t\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.1\n\t\t\t\t\t//\n\t\t\t\t\t// \"Only when these three conditions are met can the cwnd be\n\t\t\t\t\t// increased; otherwise, the cwnd MUST not be increased. If these\n\t\t\t\t\t// conditions are met, then cwnd MUST be increased by, at most, the\n\t\t\t\t\t// lesser of 1) the total size of the previously outstanding DATA\n\t\t\t\t\t// chunk(s) acknowledged, and 2) the destination's path MTU.\"\n\t\t\t\t\tthis->cwnd += std::min(totalBytesAcked, this->sctpOptions.mtu);\n\n\t\t\t\t\tMS_DEBUG_DEV(\"SS increase [cwnd:%zu, oldCwnd:%zu]\", this->cwnd, oldCwnd);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::CONGESTION_AVOIDANCE)\n\t\t\t{\n// https://tools.ietf.org/html/rfc9260#section-7.2.2\n//\n// \"Whenever cwnd is greater than ssthresh, upon each SACK arrival\n// that advances the Cumulative TSN Ack Point, increase\n// partial_bytes_acked by the total number of bytes of all new chunks\n// acknowledged in that SACK including chunks acknowledged by the new\n// Cumulative TSN Ack and by Gap Ack Blocks.\"\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t\tconst size_t oldPba = this->partialBytesAcked;\n#endif\n\n\t\t\t\tthis->partialBytesAcked += totalBytesAcked;\n\n\t\t\t\tif (this->partialBytesAcked >= this->cwnd && isFullyUtilized)\n\t\t\t\t{\n\t\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.2\n\t\t\t\t\t//\n\t\t\t\t\t// \"When partial_bytes_acked is equal to or greater than cwnd and\n\t\t\t\t\t// before the arrival of the SACK the sender had cwnd or more bytes of\n\t\t\t\t\t// data outstanding (i.e., before arrival of the SACK, flightsize was\n\t\t\t\t\t// greater than or equal to cwnd), increase cwnd by MTU, and reset\n\t\t\t\t\t// partial_bytes_acked to (partial_bytes_acked - cwnd).\"\n\n\t\t\t\t\t// Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.12\n\t\t\t\t\tthis->partialBytesAcked -= this->cwnd;\n\t\t\t\t\tthis->cwnd += this->sctpOptions.mtu;\n\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"CA increase [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]\",\n\t\t\t\t\t  this->cwnd,\n\t\t\t\t\t  oldCwnd,\n\t\t\t\t\t  this->ssthresh,\n\t\t\t\t\t  this->partialBytesAcked,\n\t\t\t\t\t  oldPba);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"CA unchanged [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]\",\n\t\t\t\t\t  this->cwnd,\n\t\t\t\t\t  oldCwnd,\n\t\t\t\t\t  this->ssthresh,\n\t\t\t\t\t  this->partialBytesAcked,\n\t\t\t\t\t  oldPba);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid RetransmissionQueue::HandlePacketLoss(Types::UnwrappedTsn /*highestTsnAcked*/)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"If not in Fast Recovery, adjust the ssthresh and cwnd of the\n\t\t\t// destination address(es) to which the missing DATA chunks were last\n\t\t\t// sent, according to the formula described in Section 7.2.3.\"\n\t\t\tif (!IsInFastRecovery())\n\t\t\t{\n#if MS_LOG_DEV_LEVEL == 3\n\t\t\t\tconst size_t oldCwnd = this->cwnd;\n\t\t\t\tconst size_t oldPba  = this->partialBytesAcked;\n#endif\n\n\t\t\t\tthis->ssthresh =\n\t\t\t\t  std::max(this->cwnd / 2, this->sctpOptions.minCwndMtus * this->sctpOptions.mtu);\n\t\t\t\tthis->cwnd              = this->ssthresh;\n\t\t\t\tthis->partialBytesAcked = 0;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"packet loss detected (not fast recovery) [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]\",\n\t\t\t\t  this->cwnd,\n\t\t\t\t  oldCwnd,\n\t\t\t\t  this->ssthresh,\n\t\t\t\t  this->partialBytesAcked,\n\t\t\t\t  oldPba);\n\n\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.4\n\t\t\t\t//\n\t\t\t\t// \"If not in Fast Recovery, enter Fast Recovery and mark the highest\n\t\t\t\t// outstanding TSN as the Fast Recovery exit point.\"\n\t\t\t\tthis->fastRecoveryExitTsn = this->outstandingData.GetHighestOutstandingTsn();\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"fast recovery initiated with exit point %\" PRIu32,\n\t\t\t\t  this->fastRecoveryExitTsn.value().Wrap());\n\t\t\t}\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-7.2.4\n\t\t\t//\n\t\t\t// \"While in Fast Recovery, the ssthresh and cwnd SHOULD NOT change for\n\t\t\t// any destinations due to a subsequent Fast Recovery event (i.e., one\n\t\t\t// SHOULD NOT reduce the cwnd further due to a subsequent Fast\n\t\t\t// Retransmit).\"\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"packet loss detected (fast recovery), no changes\");\n\t\t\t}\n\t\t}\n\n\t\tvoid RetransmissionQueue::UpdateReceiverWindow(uint32_t aRwnd)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->rwnd = this->outstandingData.GetUnackedPayloadBytes() >= aRwnd\n\t\t\t               ? 0\n\t\t\t               : aRwnd - this->outstandingData.GetUnackedPayloadBytes();\n\t\t}\n\n\t\tvoid RetransmissionQueue::StartT3RtxTimerIfOutstandingData()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Note: Can't use `GetUnackedPacketBytes()` as that one doesn't count\n\t\t\t// chunks to be retransmitted.\n\n\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.3.2\n\t\t\t// \"Whenever all outstanding data sent to an address have been\n\t\t\t// acknowledged, turn off the T3-rtx timer of that address.\n\t\t\tif (this->outstandingData.IsEmpty())\n\t\t\t{\n\t\t\t\t// Note: Already stopped in `StopT3RtxTimerOnIncreasedCumulativeTsnAck()`.\"\n\t\t\t\t//\n\t\t\t\t// TODO: As said above, `StopT3RtxTimerOnIncreasedCumulativeTsnAck()`\n\t\t\t\t// is NOT implemented in dcsctp and of course it's never called from\n\t\t\t\t// anywhere.\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// https://tools.ietf.org/html/rfc9260#section-6.3.2\n\t\t\t\t//\n\t\t\t\t// \"Whenever a SACK is received that acknowledges the DATA chunk with\n\t\t\t\t// the earliest outstanding TSN for that address, restart the T3-rtx\n\t\t\t\t// timer for that address with its current RTO (if there is still\n\t\t\t\t// outstanding data on that address).\"\n\t\t\t\t// \"Whenever a SACK is received missing a TSN that was previously\n\t\t\t\t// acknowledged via a Gap Ack Block, start the T3-rtx for the\n\t\t\t\t// destination address to which the DATA chunk was originally\n\t\t\t\t// transmitted if it is not already running.\"\n\t\t\t\tif (!this->t3RtxTimer->IsRunning())\n\t\t\t\t{\n\t\t\t\t\tthis->t3RtxTimer->Start();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/RetransmissionTimeout.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::RetransmissionTimeout\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/RetransmissionTimeout.hpp\"\n#include \"Logger.hpp\"\n#include <cmath> // std::abs(), std::max(), std::round()\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Static. */\n\n\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-16\n\t\tstatic constexpr double RtoAlpha{ 1.0 / 8.0 };\n\t\tstatic constexpr double RtoBeta{ 1.0 / 4.0 };\n\n\t\t// A factor that the `minRttVarianceMs` configuration option will be divided\n\t\t// by (before later multiplied with K, which is 4 according to RFC6298). When\n\t\t// this value was introduced, it was unintentionally divided by 8 since that\n\t\t// code worked with scaled numbers (to avoid floating point math). That\n\t\t// behavior is kept as downstream users have measured good values for their\n\t\t// use-cases.\n\t\tstatic constexpr double HeuristicVarianceAdjustment{ 8.0 };\n\n\t\t/* Instance methods. */\n\n\t\tRetransmissionTimeout::RetransmissionTimeout(const SctpOptions& sctpOptions)\n\t\t  : minRtoMs(sctpOptions.minRtoMs),\n\t\t    maxRtoMs(sctpOptions.maxRtoMs),\n\t\t    maxRttMs(sctpOptions.maxRttMs),\n\t\t    minRttVarianceMs(sctpOptions.minRttVarianceMs / HeuristicVarianceAdjustment),\n\t\t    srttMs(sctpOptions.initialRtoMs),\n\t\t    rtoMs(sctpOptions.initialRtoMs),\n\t\t    firstMeasurement(true)\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tRetransmissionTimeout::~RetransmissionTimeout()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid RetransmissionTimeout::Dump(int indentation) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DUMP_CLEAN(indentation, \"<SCTP::RetransmissionTimeout>\");\n\t\t\tMS_DUMP_CLEAN(indentation, \"  min rto (ms): %\" PRIu64, this->minRtoMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  max rto (ms): %\" PRIu64, this->maxRtoMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  max rtt (ms): %\" PRIu64, this->maxRttMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  min rtt variance (ms): %\" PRIu64, this->minRttVarianceMs);\n\t\t\tMS_DUMP_CLEAN(indentation, \"  rto (ms): %\" PRIu64, GetRtoMs());\n\t\t\tMS_DUMP_CLEAN(indentation, \"  srtt (ms): %\" PRIu64, GetSrttMs());\n\t\t\tMS_DUMP_CLEAN(indentation, \"</SCTP::RetransmissionTimeout>\");\n\t\t}\n\n\t\tvoid RetransmissionTimeout::ObserveRttMs(uint64_t rttMs)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Unrealistic values will be skipped. If a wrongly measured (or otherwise\n\t\t\t// corrupt) value was processed, it could change the state in a way that\n\t\t\t// would take a very long time to recover.\n\t\t\tif (rttMs == 0 || rttMs > this->maxRttMs)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(sctp, \"skipping given unrealistic rttMs value %\" PRIu64, rttMs);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.1\n\t\t\tif (this->firstMeasurement)\n\t\t\t{\n\t\t\t\tthis->srttMs           = rttMs;\n\t\t\t\tthis->rttVarMs         = rttMs / 2.0;\n\t\t\t\tthis->firstMeasurement = false;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tconst double rttDiffMs = std::abs(this->srttMs - static_cast<double>(rttMs));\n\n\t\t\t\tthis->rttVarMs = ((1.0 - RtoBeta) * this->rttVarMs) + (RtoBeta * rttDiffMs);\n\t\t\t\tthis->srttMs   = ((1.0 - RtoAlpha) * this->srttMs) + (RtoAlpha * rttMs);\n\t\t\t}\n\n\t\t\tthis->rttVarMs = std::max(this->rttVarMs, static_cast<double>(this->minRttVarianceMs));\n\t\t\tthis->rtoMs    = this->srttMs + (4.0 * this->rttVarMs);\n\t\t\tthis->rtoMs    = std::round(\n\t\t\t  std::clamp(\n\t\t\t    this->rtoMs, static_cast<double>(this->minRtoMs), static_cast<double>(this->maxRtoMs)));\n\n\t\t\tMS_DEBUG_DEV(\"new computed RTO: %\" PRIu64 \" ms\", this->rtoMs);\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/RoundRobinSendQueue.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::RoundRobinSendQueue\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/RoundRobinSendQueue.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <set>\n#include <span>\n#include <string>\n#include <tuple> // std::forward_as_tuple()\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tRoundRobinSendQueue::RoundRobinSendQueue(\n\t\t  AssociationListenerInterface& associationListener,\n\t\t  size_t mtu,\n\t\t  uint16_t defaultPriority,\n\t\t  size_t totalBufferedAmountLowThreshold)\n\t\t  : associationListener(associationListener),\n\t\t    defaultPriority(defaultPriority),\n\t\t    scheduler(mtu),\n\t\t    totalBufferedAmountThresholdWatcher(\n\t\t      [this]()\n\t\t      {\n\t\t\t      this->associationListener.OnAssociationTotalBufferedAmountLow();\n\t\t      })\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->totalBufferedAmountThresholdWatcher.SetLowThreshold(totalBufferedAmountLowThreshold);\n\t\t}\n\n\t\tRoundRobinSendQueue::~RoundRobinSendQueue()\n\t\t{\n\t\t\tMS_TRACE();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::AddMessage(\n\t\t  uint64_t nowMs, Message message, const SendMessageOptions& sendMessageOptions)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(message.GetPayloadLength(), \"message payload cannot be empty\");\n\n\t\t\t// Any limited lifetime should start counting from now - when the message\n\t\t\t// has been added to the queue.\n\t\t\t// `expiresAtMs` is the time when it expires. Which is slightly larger\n\t\t\t// than the message's lifetime, as the message is alive during its entire\n\t\t\t// lifetime (which may be zero).\n\t\t\tconst MessageAttributes attributes = {\n\t\t\t\t.isUnordered        = sendMessageOptions.unordered,\n\t\t\t\t.maxRetransmissions = sendMessageOptions.maxRetransmissions.has_value()\n\t\t\t\t                        ? sendMessageOptions.maxRetransmissions.value()\n\t\t\t\t                        : Types::MaxRetransmitsNoLimit,\n\t\t\t\t.expiresAtMs        = sendMessageOptions.lifetimeMs.has_value()\n\t\t\t\t                        ? nowMs + sendMessageOptions.lifetimeMs.value() + 1\n\t\t\t\t                        : Types::ExpiresAtMsInfinite,\n\t\t\t\t.lifecycleId        = sendMessageOptions.lifecycleId,\n\t\t\t};\n\n\t\t\tconst uint16_t streamId = message.GetStreamId();\n\n\t\t\tGetOrCreateStreamInfo(streamId).AddMessage(std::move(message), attributes);\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tuint16_t RoundRobinSendQueue::GetStreamPriority(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto it = this->streams.find(streamId);\n\n\t\t\tif (it == this->streams.end())\n\t\t\t{\n\t\t\t\treturn this->defaultPriority;\n\t\t\t}\n\n\t\t\treturn it->second.GetPriority();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::SetStreamPriority(uint16_t streamId, uint16_t priority)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tOutgoingStream& stream = GetOrCreateStreamInfo(streamId);\n\n\t\t\tstream.SetPriority(priority);\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tstd::optional<SendQueueInterface::DataToSend> RoundRobinSendQueue::Produce(\n\t\t  uint64_t nowMs, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn this->scheduler.Produce(nowMs, maxLength);\n\t\t}\n\n\t\tbool RoundRobinSendQueue::Discard(uint16_t streamId, uint32_t outgoingMessageId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst bool hasDiscarded = GetOrCreateStreamInfo(streamId).Discard(outgoingMessageId);\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn hasDiscarded;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::PrepareResetStream(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetOrCreateStreamInfo(streamId).Pause();\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tbool RoundRobinSendQueue::HasStreamsReadyToBeReset() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\treturn std::ranges::any_of(\n\t\t\t  this->streams,\n\t\t\t  [](const auto& entry)\n\t\t\t  {\n\t\t\t\t  const auto& stream = entry.second;\n\n\t\t\t\t  return stream.IsReadyToBeReset();\n\t\t\t  });\n\t\t}\n\n\t\tstd::vector<uint16_t> RoundRobinSendQueue::GetStreamsReadyToBeReset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  std::ranges::count_if(\n\t\t\t    this->streams,\n\t\t\t    [](const auto& s)\n\t\t\t    {\n\t\t\t\t    const auto& stream = s.second;\n\n\t\t\t\t    return stream.IsResetting();\n\t\t\t    }) == 0,\n\t\t\t  \"none of the streams can be resetting\");\n\n\t\t\tstd::vector<uint16_t /*streamId*/> readyStreams;\n\n\t\t\tfor (auto& [streamId, stream] : this->streams)\n\t\t\t{\n\t\t\t\tif (stream.IsReadyToBeReset())\n\t\t\t\t{\n\t\t\t\t\tstream.SetAsResetting();\n\t\t\t\t\treadyStreams.push_back(streamId);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn readyStreams;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::CommitResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  std::ranges::count_if(\n\t\t\t    this->streams,\n\t\t\t    [](const auto& s)\n\t\t\t    {\n\t\t\t\t    const auto& stream = s.second;\n\n\t\t\t\t    return stream.IsResetting();\n\t\t\t    }) > 0,\n\t\t\t  \"at least one stream must be resetting\");\n\n\t\t\tfor (auto& [unused, stream] : this->streams)\n\t\t\t{\n\t\t\t\tif (stream.IsResetting())\n\t\t\t\t{\n\t\t\t\t\tstream.Reset();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::RollbackResetStreams()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  std::ranges::count_if(\n\t\t\t    this->streams,\n\t\t\t    [](const auto& s)\n\t\t\t    {\n\t\t\t\t    const auto& stream = s.second;\n\n\t\t\t\t    return stream.IsResetting();\n\t\t\t    }) > 0,\n\t\t\t  \"at least one stream must be resetting\");\n\n\t\t\tfor (auto& [unused, stream] : this->streams)\n\t\t\t{\n\t\t\t\tif (stream.IsResetting())\n\t\t\t\t{\n\t\t\t\t\tstream.Resume();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::Reset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Recalculate buffered amount, as partially sent messages may have been\n\t\t\t// put fully back in the queue.\n\t\t\tfor (auto& [unused, stream] : this->streams)\n\t\t\t{\n\t\t\t\tstream.Reset();\n\t\t\t}\n\n\t\t\tthis->scheduler.ForceReschedule();\n\t\t}\n\n\t\tsize_t RoundRobinSendQueue::GetStreamBufferedAmount(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto it = this->streams.find(streamId);\n\n\t\t\tif (it == this->streams.end())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst auto& stream = it->second;\n\n\t\t\treturn stream.GetBufferedAmount().GetValue();\n\t\t}\n\n\t\tsize_t RoundRobinSendQueue::GetStreamBufferedAmountLowThreshold(uint16_t streamId) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto it = this->streams.find(streamId);\n\n\t\t\tif (it == this->streams.end())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tconst auto& stream = it->second;\n\n\t\t\treturn stream.GetBufferedAmount().GetLowThreshold();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tGetOrCreateStreamInfo(streamId).GetBufferedAmount().SetLowThreshold(bytes);\n\t\t}\n\n\t\tRoundRobinSendQueue::OutgoingStream& RoundRobinSendQueue::GetOrCreateStreamInfo(uint16_t streamId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst auto it = this->streams.find(streamId);\n\n\t\t\tif (it != this->streams.end())\n\t\t\t{\n\t\t\t\tauto& stream = it->second;\n\n\t\t\t\treturn stream;\n\t\t\t}\n\n\t\t\tconst auto [it2, inserted] = this->streams.emplace(\n\t\t\t  std::piecewise_construct,\n\t\t\t  std::forward_as_tuple(streamId),\n\t\t\t  std::forward_as_tuple(\n\t\t\t    this,\n\t\t\t    std::addressof(this->scheduler),\n\t\t\t    streamId,\n\t\t\t    this->defaultPriority,\n\t\t\t    [this, streamId]()\n\t\t\t    {\n\t\t\t\t    this->associationListener.OnAssociationStreamBufferedAmountLow(streamId);\n\t\t\t    }));\n\n\t\t\treturn it2->second;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::set<uint16_t> expectedActiveStreamIds;\n\t\t\tconst std::set<uint16_t> actualActiveStreamIds = this->scheduler.GetActiveStreamsForTesting();\n\n\t\t\tsize_t totalBufferedAmount{ 0 };\n\n\t\t\tfor (const auto& [streamId, stream] : this->streams)\n\t\t\t{\n\t\t\t\ttotalBufferedAmount += stream.GetBufferedAmount().GetValue();\n\n\t\t\t\tif (stream.GetBytesToSendInNextMessage() > 0)\n\t\t\t\t{\n\t\t\t\t\texpectedActiveStreamIds.emplace(streamId);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (expectedActiveStreamIds != actualActiveStreamIds)\n\t\t\t{\n\t\t\t\tconst auto join = [](const auto& set)\n\t\t\t\t{\n\t\t\t\t\tstd::string out;\n\n\t\t\t\t\tsize_t i{ 0 };\n\t\t\t\t\tfor (const auto& v : set)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (i++ != 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tout += \",\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tout += std::to_string(v);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn out;\n\t\t\t\t};\n\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  expectedActiveStreamIds == actualActiveStreamIds,\n\t\t\t\t  \"active streams mismatch [actual=[%s], expected=[%s]]\",\n\t\t\t\t  join(actualActiveStreamIds).c_str(),\n\t\t\t\t  join(expectedActiveStreamIds).c_str());\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  totalBufferedAmount == this->totalBufferedAmountThresholdWatcher.GetValue(),\n\t\t\t  \"total buffered amount mismatch [actual=[%zu], expected=[%zu]]\",\n\t\t\t  totalBufferedAmount,\n\t\t\t  this->totalBufferedAmountThresholdWatcher.GetValue());\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::ThresholdWatcher::Decrease(size_t bytes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  bytes <= this->value,\n\t\t\t  \"bytes (%zu) must be smaller or equal than this->value (%zu)\",\n\t\t\t  bytes,\n\t\t\t  this->value);\n\n\t\t\tconst size_t oldValue = this->value;\n\n\t\t\tthis->value -= bytes;\n\n\t\t\tif (oldValue > this->lowThreshold && this->value <= this->lowThreshold)\n\t\t\t{\n\t\t\t\tthis->onThresholdReached();\n\t\t\t}\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::ThresholdWatcher::SetLowThreshold(size_t lowThreshold)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// Betting on https://github.com/w3c/webrtc-pc/issues/2654 being accepted.\n\t\t\tif (this->lowThreshold < this->value && lowThreshold >= this->value)\n\t\t\t{\n\t\t\t\tthis->onThresholdReached();\n\t\t\t}\n\n\t\t\tthis->lowThreshold = lowThreshold;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::AddMessage(Message message, MessageAttributes attributes)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tconst bool wasActive = GetBytesToSendInNextMessage() > 0;\n\n\t\t\tthis->bufferedAmountThresholdWatcher.Increase(message.GetPayloadLength());\n\t\t\tthis->parent.totalBufferedAmountThresholdWatcher.Increase(message.GetPayloadLength());\n\n\t\t\tconst uint32_t outgoingMessageId = this->parent.currentOutgoingMessageId;\n\n\t\t\tthis->parent.currentOutgoingMessageId = this->parent.currentOutgoingMessageId + 1;\n\n\t\t\tthis->items.emplace_back(outgoingMessageId, std::move(message), attributes);\n\n\t\t\tif (!wasActive)\n\t\t\t{\n\t\t\t\tthis->schedulerStream->MayMakeActive();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tstd::optional<SendQueueInterface::DataToSend> RoundRobinSendQueue::OutgoingStream::Produce(\n\t\t  uint64_t nowMs, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(\n\t\t\t  this->pauseState != PauseState::PAUSED && this->pauseState != PauseState::RESETTING,\n\t\t\t  \"pause state must not be PAUSED or RESETTING\");\n\n\t\t\twhile (!this->items.empty())\n\t\t\t{\n\t\t\t\tItem& item       = this->items.front();\n\t\t\t\tMessage& message = item.message;\n\n\t\t\t\t// Allocate Message ID and SSN when the first fragment is sent.\n\t\t\t\tif (!item.mid.has_value())\n\t\t\t\t{\n\t\t\t\t\t// This entire message has already expired. Try the next one.\n\t\t\t\t\tif (item.attributes.expiresAtMs != Types::ExpiresAtMsInfinite && item.attributes.expiresAtMs <= nowMs)\n\t\t\t\t\t{\n\t\t\t\t\t\tHandleMessageExpired(item);\n\n\t\t\t\t\t\tthis->items.pop_front();\n\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tuint32_t& mid = item.attributes.isUnordered ? this->nextUnorderedMid : this->nextOrderedMid;\n\n\t\t\t\t\titem.mid = mid;\n\t\t\t\t\tmid += 1;\n\t\t\t\t}\n\t\t\t\tif (!item.attributes.isUnordered && !item.ssn.has_value())\n\t\t\t\t{\n\t\t\t\t\titem.ssn = this->nextSsn;\n\n\t\t\t\t\tthis->nextSsn += 1;\n\t\t\t\t}\n\n\t\t\t\t// Grab the next `maxLength` fragment from this message and calculate\n\t\t\t\t// flags.\n\t\t\t\tconst std::span<const uint8_t> messagePayload = message.GetPayload();\n\t\t\t\tconst std::span<const uint8_t> chunkPayload   = messagePayload.subspan(\n\t\t\t\t  item.remainingOffset, std::min(messagePayload.size() - item.remainingOffset, maxLength));\n\n\t\t\t\tconst bool isBeginning = chunkPayload.data() == messagePayload.data();\n\t\t\t\tconst bool isEnd       = (chunkPayload.data() + chunkPayload.size()) ==\n\t\t\t\t                         (messagePayload.data() + messagePayload.size());\n\n\t\t\t\tconst uint16_t streamId = message.GetStreamId();\n\t\t\t\tconst uint32_t ppid     = message.GetPayloadProtocolId();\n\n\t\t\t\t// Zero-copy the payload if the message fits in a single chunk.\n\t\t\t\tstd::vector<uint8_t> payload =\n\t\t\t\t  isBeginning && isEnd ? std::move(message).ReleasePayload()\n\t\t\t\t                       : std::vector<uint8_t>(chunkPayload.begin(), chunkPayload.end());\n\n\t\t\t\tconst uint32_t fsn = item.currentFsn;\n\n\t\t\t\titem.currentFsn += 1;\n\n\t\t\t\tthis->bufferedAmountThresholdWatcher.Decrease(payload.size());\n\t\t\t\tthis->parent.totalBufferedAmountThresholdWatcher.Decrease(payload.size());\n\n\t\t\t\tSendQueueInterface::DataToSend dataToSend(\n\t\t\t\t  item.outgoingMessageId,\n\t\t\t\t  UserData(\n\t\t\t\t    streamId,\n\t\t\t\t    item.ssn.value_or(0),\n\t\t\t\t    *item.mid,\n\t\t\t\t    fsn,\n\t\t\t\t    ppid,\n\t\t\t\t    std::move(payload),\n\t\t\t\t    isBeginning,\n\t\t\t\t    isEnd,\n\t\t\t\t    item.attributes.isUnordered));\n\n\t\t\t\tdataToSend.maxRetransmissions = item.attributes.maxRetransmissions;\n\t\t\t\tdataToSend.expiresAtMs        = item.attributes.expiresAtMs;\n\t\t\t\tdataToSend.lifecycleId        = isEnd ? item.attributes.lifecycleId : std::nullopt;\n\n\t\t\t\tif (isEnd)\n\t\t\t\t{\n\t\t\t\t\t// The entire message has been sent, and its last data copied to\n\t\t\t\t\t// `dataToSend`, so it can safely be discarded.\n\t\t\t\t\tthis->items.pop_front();\n\n\t\t\t\t\tif (this->pauseState == PauseState::PENDING)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"pause state on stream %\" PRIu16 \" is moving from PENDING to PAUSED\", streamId);\n\n\t\t\t\t\t\tthis->pauseState = PauseState::PAUSED;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\titem.remainingOffset += chunkPayload.size();\n\t\t\t\t\titem.remainingLength -= chunkPayload.size();\n\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  item.remainingOffset + item.remainingLength == item.message.GetPayloadLength(),\n\t\t\t\t\t  \"item.remainingOffset + item.remainingLength != item.message.GetPayloadLength()\");\n\t\t\t\t\tMS_ASSERT(item.remainingLength > 0, \"item.remainingLength <= 0\");\n\t\t\t\t}\n\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn dataToSend;\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn std::nullopt;\n\t\t}\n\n\t\tsize_t RoundRobinSendQueue::OutgoingStream::GetBytesToSendInNextMessage() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->pauseState == PauseState::PAUSED || this->pauseState == PauseState::RESETTING)\n\t\t\t{\n\t\t\t\t// The stream has paused (and there is no partially sent message).\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (this->items.empty())\n\t\t\t{\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn this->items.front().remainingLength;\n\t\t}\n\n\t\tbool RoundRobinSendQueue::OutgoingStream::Discard(uint32_t outgoingMessageId)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tbool result = false;\n\n\t\t\tif (!this->items.empty())\n\t\t\t{\n\t\t\t\tItem& item = this->items.front();\n\n\t\t\t\tif (item.outgoingMessageId == outgoingMessageId)\n\t\t\t\t{\n\t\t\t\t\tHandleMessageExpired(item);\n\n\t\t\t\t\tthis->items.pop_front();\n\n\t\t\t\t\t// Only partially sent messages are discarded, so if a message was\n\t\t\t\t\t// discarded, then it was the currently sent message.\n\t\t\t\t\tthis->schedulerStream->ForceReschedule();\n\n\t\t\t\t\tif (this->pauseState == PauseState::PENDING)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->pauseState = PauseState::PAUSED;\n\t\t\t\t\t\tthis->schedulerStream->MakeInactive();\n\t\t\t\t\t}\n\t\t\t\t\telse if (GetBytesToSendInNextMessage() == 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->schedulerStream->MakeInactive();\n\t\t\t\t\t}\n\n\t\t\t\t\t// As the item still existed, it had unsent data.\n\t\t\t\t\tresult = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn result;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::Pause()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->pauseState != PauseState::NOT_PAUSED)\n\t\t\t{\n\t\t\t\t// Already in progress.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst bool hadPendingItems = !this->items.empty();\n\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc8831#section-6.7\n\t\t\t//\n\t\t\t// \"Closing of a data channel MUST be signaled by resetting the corresponding\n\t\t\t// outgoing streams [RFC6525].  This means that if one side decides to close\n\t\t\t// the data channel, it resets the corresponding outgoing stream.\"\n\t\t\t// ... \"[RFC6525] also guarantees that all the messages are delivered (or\n\t\t\t// abandoned) before the stream is reset.\"\n\n\t\t\t// A stream is paused when it's about to be reset. In this implementation,\n\t\t\t// it will throw away all non-partially send messages - they will be\n\t\t\t// abandoned as noted above. This is subject to change. It will however\n\t\t\t// not discard any partially sent messages - only whole messages. Partially\n\t\t\t// delivered messages (at the time of receiving a Stream Reset command) will\n\t\t\t// always deliver all the fragments before actually resetting the stream.\n\t\t\tfor (auto it = this->items.begin(); it != this->items.end();)\n\t\t\t{\n\t\t\t\tif (it->remainingOffset == 0)\n\t\t\t\t{\n\t\t\t\t\tauto& item = *it;\n\n\t\t\t\t\tHandleMessageExpired(item);\n\n\t\t\t\t\tit = this->items.erase(it);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t++it;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->pauseState = (this->items.empty() || this->items.front().remainingOffset == 0)\n\t\t\t                     ? PauseState::PAUSED\n\t\t\t                     : PauseState::PENDING;\n\n\t\t\tif (hadPendingItems && this->pauseState == PauseState::PAUSED)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"stream %\" PRIu16 \" was previously active, but is now paused\", GetStreamId());\n\n\t\t\t\tthis->schedulerStream->MakeInactive();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::Resume()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->pauseState == PauseState::RESETTING, \"pause state must be RESETTING\");\n\n\t\t\tthis->pauseState = PauseState::NOT_PAUSED;\n\t\t\tthis->schedulerStream->MayMakeActive();\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::SetAsResetting()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->pauseState == PauseState::PAUSED, \"pause state must be PAUSED\");\n\n\t\t\tthis->pauseState = PauseState::RESETTING;\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::Reset()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// This can be called both when an outgoing stream reset has been responded\n\t\t\t// to, or when the entire SendQueue is reset due to detecting the peer having\n\t\t\t// restarted. The stream may be in any state at this time.\n\t\t\tconst PauseState oldPauseState = this->pauseState;\n\n\t\t\tthis->pauseState       = PauseState::NOT_PAUSED;\n\t\t\tthis->nextOrderedMid   = 0;\n\t\t\tthis->nextUnorderedMid = 0;\n\t\t\tthis->nextSsn          = 0;\n\n\t\t\tif (!this->items.empty())\n\t\t\t{\n\t\t\t\t// If this message has been partially sent, reset it so that it will be\n\t\t\t\t// re-sent.\n\t\t\t\tauto& item = this->items.front();\n\n\t\t\t\tthis->bufferedAmountThresholdWatcher.Increase(\n\t\t\t\t  item.message.GetPayloadLength() - item.remainingLength);\n\t\t\t\tthis->parent.totalBufferedAmountThresholdWatcher.Increase(\n\t\t\t\t  item.message.GetPayloadLength() - item.remainingLength);\n\t\t\t\titem.remainingOffset = 0;\n\t\t\t\titem.remainingLength = item.message.GetPayloadLength();\n\t\t\t\titem.mid             = std::nullopt;\n\t\t\t\titem.ssn             = std::nullopt;\n\t\t\t\titem.currentFsn      = 0;\n\n\t\t\t\tif (oldPauseState == PauseState::PAUSED || oldPauseState == PauseState::RESETTING)\n\t\t\t\t{\n\t\t\t\t\tthis->schedulerStream->MayMakeActive();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\t\t}\n\n\t\tbool RoundRobinSendQueue::OutgoingStream::HasPartiallySentMessage() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->items.empty())\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn this->items.front().mid.has_value();\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::HandleMessageExpired(\n\t\t  RoundRobinSendQueue::OutgoingStream::Item& item)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->bufferedAmountThresholdWatcher.Decrease(item.remainingLength);\n\n\t\t\tthis->parent.totalBufferedAmountThresholdWatcher.Decrease(item.remainingLength);\n\n\t\t\tif (item.attributes.lifecycleId.has_value())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"triggering OnAssociationLifecycleMessageExpired(%\" PRIu64 \"), /*maybeDelivered*/ false)\",\n\t\t\t\t  item.attributes.lifecycleId);\n\n\t\t\t\tthis->parent.associationListener.OnAssociationLifecycleMessageExpired(\n\t\t\t\t  item.attributes.lifecycleId.value(), /*maybeDelivered*/ false);\n\t\t\t\tthis->parent.associationListener.OnAssociationLifecycleMessageEnd(\n\t\t\t\t  item.attributes.lifecycleId.value());\n\t\t\t}\n\t\t}\n\n\t\tvoid RoundRobinSendQueue::OutgoingStream::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tsize_t bytes{ 0 };\n\n\t\t\tfor (const auto& item : this->items)\n\t\t\t{\n\t\t\t\tbytes += item.remainingLength;\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  bytes == this->bufferedAmountThresholdWatcher.GetValue(),\n\t\t\t  \"bytes != this->bufferedAmountThresholdWatcher.GetValue()\");\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SCTP/tx/StreamScheduler.cpp",
    "content": "#define MS_CLASS \"RTC::SCTP::StreamScheduler\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SCTP/tx/StreamScheduler.hpp\"\n#include \"Logger.hpp\"\n#include <ranges>\n\nnamespace RTC\n{\n\tnamespace SCTP\n\t{\n\t\t/* Instance methods. */\n\n\t\tstd::optional<SendQueueInterface::DataToSend> StreamScheduler::Produce(uint64_t nowMs, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\t// For non-interleaved streams, avoid rescheduling while still sending a\n\t\t\t// message as it needs to be sent in full. For interleaved messaging,\n\t\t\t// reschedule for every I-DATA chunk sent.\n\t\t\tconst bool rescheduling = this->enableMessageInterleaving || !this->currentlySendingAMessage;\n\n\t\t\tMS_DEBUG_DEV(\"producing data, rescheduling\");\n\n\t\t\tMS_ASSERT(\n\t\t\t  rescheduling || this->currentStream,\n\t\t\t  \"it must be rescheduling or there should be current stream\");\n\n\t\t\tstd::optional<SendQueueInterface::DataToSend> dataToSend;\n\n\t\t\twhile (!dataToSend.has_value() && !this->activeStreams.empty())\n\t\t\t{\n\t\t\t\tif (rescheduling)\n\t\t\t\t{\n\t\t\t\t\tconst auto it = this->activeStreams.begin();\n\n\t\t\t\t\tthis->currentStream = *it;\n\n\t\t\t\t\tMS_DEBUG_DEV(\"rescheduling to stream %\" PRIu16, this->currentStream->GetStreamId());\n\n\t\t\t\t\tthis->activeStreams.erase(it);\n\t\t\t\t\tthis->currentStream->ForceMarkInactive();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"producing from previous stream %\" PRIu16, this->currentStream->GetStreamId());\n\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  std::ranges::any_of(\n\t\t\t\t\t    this->activeStreams,\n\t\t\t\t\t    [this](const auto* stream)\n\t\t\t\t\t    {\n\t\t\t\t\t\t    return stream == this->currentStream;\n\t\t\t\t\t    }),\n\t\t\t\t\t  \"current stream should be in active streams\");\n\t\t\t\t}\n\n\t\t\t\tdataToSend = this->currentStream->Produce(nowMs, maxLength);\n\t\t\t}\n\n\t\t\tif (!dataToSend.has_value())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"there is no stream with data, cannot produce any data\");\n\n\t\t\t\tAssertIsConsistent();\n\n\t\t\t\treturn std::nullopt;\n\t\t\t}\n\n\t\t\tMS_ASSERT(\n\t\t\t  dataToSend->data.GetStreamId() == this->currentStream->GetStreamId(),\n\t\t\t  \"no matching stream id\");\n\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"producing data [type:%s, beginning:%s, end:%s, streamId:%\" PRIu16 \", ppid:%\" PRIu32\n\t\t\t  \", length:%zu]\",\n\t\t\t  dataToSend->data.IsUnordered() ? \"unordered\" : \"ordered\",\n\t\t\t  dataToSend->data.IsBeginning() ? \"yes\" : \"no\",\n\t\t\t  dataToSend->data.IsEnd() ? \"yes\" : \"no\",\n\t\t\t  dataToSend->data.GetStreamId(),\n\t\t\t  dataToSend->data.GetPayloadProtocolId(),\n\t\t\t  dataToSend->data.GetPayloadLength());\n\n\t\t\tthis->currentlySendingAMessage = !dataToSend->data.IsEnd();\n\t\t\tthis->virtualTime              = this->currentStream->GetCurrentTime();\n\n\t\t\t// One side-effect of rescheduling is that the new stream will not be\n\t\t\t// present in `this->activeStreams`.\n\t\t\tconst size_t bytesToSendNext = this->currentStream->GetBytesToSendInNextMessage();\n\n\t\t\tif (rescheduling && bytesToSendNext > 0)\n\t\t\t{\n\t\t\t\tthis->currentStream->MakeActive(bytesToSendNext);\n\t\t\t}\n\t\t\telse if (!rescheduling && bytesToSendNext == 0)\n\t\t\t{\n\t\t\t\tthis->currentStream->MakeInactive();\n\t\t\t}\n\n\t\t\tAssertIsConsistent();\n\n\t\t\treturn dataToSend;\n\t\t}\n\n\t\tstd::set<uint16_t> StreamScheduler::GetActiveStreamsForTesting() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::set<uint16_t> streamIds;\n\n\t\t\tfor (const auto& stream : this->activeStreams)\n\t\t\t{\n\t\t\t\tstreamIds.insert(stream->GetStreamId());\n\t\t\t}\n\n\t\t\treturn streamIds;\n\t\t}\n\n\t\tvoid StreamScheduler::AssertIsConsistent() const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tfor (const Stream* stream : this->activeStreams)\n\t\t\t{\n\t\t\t\tif (stream->GetNextFinishTime() == 0)\n\t\t\t\t{\n\t\t\t\t\tMS_ASSERT(\n\t\t\t\t\t  stream->GetNextFinishTime() > 0,\n\t\t\t\t\t  \"stream %\" PRIu16 \" is active but has no next-finish-time\",\n\t\t\t\t\t  stream->GetStreamId());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tvoid StreamScheduler::Stream::SetPriority(uint16_t priority)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->priority      = priority;\n\t\t\tthis->inverseWeight = 1.0 / std::max(static_cast<double>(priority), 1e-6);\n\t\t}\n\n\t\tvoid StreamScheduler::Stream::MayMakeActive()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_ASSERT(this->nextFinishTime == 0, \"next-finish-time must be 0\");\n\n\t\t\tconst size_t bytesToSendNext = GetBytesToSendInNextMessage();\n\n\t\t\tif (bytesToSendNext == 0)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMakeActive(bytesToSendNext);\n\t\t}\n\n\t\tvoid StreamScheduler::Stream::MakeActive(size_t bytesToSendNext)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tthis->currentVirtualTime = this->parent.virtualTime;\n\n\t\t\tMS_ASSERT(bytesToSendNext > 0, \"bytesToSendNext must be higher than 0\");\n\n\t\t\tconst double nextFinishTime =\n\t\t\t  CalculateFinishTime(std::min(bytesToSendNext, this->parent.maxPayloadBytes));\n\n\t\t\tMS_ASSERT(nextFinishTime > 0, \"nextFinishTime must be higher than 0\");\n\n\t\t\tMS_DEBUG_DEV(\"making stream %\" PRIu16 \" active, expiring at %f\", GetStreamId(), nextFinishTime);\n\n\t\t\tMS_ASSERT(this->nextFinishTime == 0, \"this->nextFinishTime must be 0\");\n\n\t\t\tthis->nextFinishTime = nextFinishTime;\n\n\t\t\tMS_ASSERT(\n\t\t\t  !std::ranges::any_of(\n\t\t\t    this->parent.activeStreams,\n\t\t\t    [this](const auto* stream)\n\t\t\t    {\n\t\t\t\t    return stream == this;\n\t\t\t    }),\n\t\t\t  \"this stream must not be in active streams\");\n\n\t\t\tthis->parent.activeStreams.emplace(this);\n\t\t}\n\n\t\tvoid StreamScheduler::Stream::ForceMarkInactive()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tMS_DEBUG_DEV(\"making stream %\" PRIu16 \" inactive\", GetStreamId());\n\n\t\t\tMS_ASSERT(this->nextFinishTime != 0, \"this->nextFinishTime must be different than 0\");\n\n\t\t\tthis->nextFinishTime = 0;\n\t\t}\n\n\t\tvoid StreamScheduler::Stream::MakeInactive()\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tForceMarkInactive();\n\n\t\t\tstd::erase_if(\n\t\t\t  this->parent.activeStreams,\n\t\t\t  [this](const auto* stream)\n\t\t\t  {\n\t\t\t\t  return stream == this;\n\t\t\t  });\n\t\t}\n\n\t\tdouble StreamScheduler::Stream::CalculateFinishTime(size_t bytesToSendNext) const\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tif (this->parent.enableMessageInterleaving)\n\t\t\t{\n\t\t\t\t// Perform weighted fair queuing scheduling.\n\t\t\t\treturn this->currentVirtualTime +\n\t\t\t\t       (static_cast<double>(bytesToSendNext) * this->inverseWeight);\n\t\t\t}\n\n\t\t\t// Perform round-robin scheduling by letting the stream have its next\n\t\t\t// virtual finish time in the future. It doesn't matter how far into the\n\t\t\t// future, just any positive number so that any other stream that has the\n\t\t\t// same virtual finish time as this stream gets to produce their data\n\t\t\t// before revisiting this stream.\n\t\t\treturn this->currentVirtualTime + 1;\n\t\t}\n\n\t\tstd::optional<SendQueueInterface::DataToSend> StreamScheduler::Stream::Produce(\n\t\t  uint64_t nowMs, size_t maxLength)\n\t\t{\n\t\t\tMS_TRACE();\n\n\t\t\tstd::optional<SendQueueInterface::DataToSend> dataToSend =\n\t\t\t  this->producer.Produce(nowMs, maxLength);\n\n\t\t\tif (dataToSend.has_value())\n\t\t\t{\n\t\t\t\tconst double newCurrentVirtualTime = CalculateFinishTime(dataToSend->data.GetPayloadLength());\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"virtual time changed [from:%f, to:%f]\", this->currentVirtualTime, newCurrentVirtualTime);\n\n\t\t\t\tthis->currentVirtualTime = newCurrentVirtualTime;\n\t\t\t}\n\n\t\t\treturn dataToSend;\n\t\t}\n\t} // namespace SCTP\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SctpAssociation.cpp",
    "content": "#define MS_CLASS \"RTC::SctpAssociation\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SctpAssociation.hpp\"\n#include \"DepUsrSCTP.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstdio>  // std::snprintf()\n#include <cstdlib> // std::malloc(), std::free()\n#include <cstring> // std::memset(), std::memcpy()\n#include <string>\n\n// Free send buffer threshold (in bytes) upon which send_cb will be executed.\nstatic const uint32_t SendBufferThreshold{ 256u };\n\n/* SCTP events to which we are subscribing. */\n\n// clang-format off\nconst uint16_t EventTypes[] =\n{\n\tSCTP_ADAPTATION_INDICATION,\n\tSCTP_ASSOC_CHANGE,\n\tSCTP_ASSOC_RESET_EVENT,\n\tSCTP_REMOTE_ERROR,\n\tSCTP_SHUTDOWN_EVENT,\n\tSCTP_SEND_FAILED_EVENT,\n\tSCTP_STREAM_RESET_EVENT,\n\tSCTP_STREAM_CHANGE_EVENT\n};\n// clang-format on\n\n/* Static methods for usrsctp callbacks. */\n\ninline static int onRecvSctpData(\n  struct socket* /*sock*/,\n  union sctp_sockstore /*addr*/,\n  void* data,\n  size_t len,\n  struct sctp_rcvinfo rcv,\n  int flags,\n  void* ulpInfo)\n{\n\tauto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast<uintptr_t>(ulpInfo));\n\n\tif (!sctpAssociation)\n\t{\n\t\tMS_WARN_TAG(sctp, \"no SctpAssociation found\");\n\n\t\tstd::free(data);\n\n\t\treturn 0;\n\t}\n\n\tif (flags & MSG_NOTIFICATION)\n\t{\n\t\tsctpAssociation->OnUsrSctpReceiveSctpNotification(\n\t\t  static_cast<union sctp_notification*>(data), len);\n\t}\n\telse\n\t{\n\t\tconst uint16_t streamId = rcv.rcv_sid;\n\t\tconst uint32_t ppid     = ntohl(rcv.rcv_ppid);\n\t\tconst uint16_t ssn      = rcv.rcv_ssn;\n\n\t\tMS_DEBUG_TAG(\n\t\t  sctp,\n\t\t  \"data chunk received [length:%zu, streamId:%\" PRIu16 \", SSN:%\" PRIu16 \", TSN:%\" PRIu32\n\t\t  \", PPID:%\" PRIu32 \", context:%\" PRIu32 \", flags:%d]\",\n\t\t  len,\n\t\t  rcv.rcv_sid,\n\t\t  rcv.rcv_ssn,\n\t\t  rcv.rcv_tsn,\n\t\t  ntohl(rcv.rcv_ppid),\n\t\t  rcv.rcv_context,\n\t\t  flags);\n\n\t\tsctpAssociation->OnUsrSctpReceiveSctpData(\n\t\t  streamId, ssn, ppid, flags, static_cast<uint8_t*>(data), len);\n\t}\n\n\tstd::free(data);\n\n\treturn 1;\n}\n\ninline static int onSendSctpData(struct socket* /*sock*/, uint32_t freeBuffer, void* ulpInfo)\n{\n\tauto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast<uintptr_t>(ulpInfo));\n\n\tif (!sctpAssociation)\n\t{\n\t\tMS_WARN_TAG(sctp, \"no SctpAssociation found\");\n\n\t\treturn 0;\n\t}\n\n\tsctpAssociation->OnUsrSctpSentData(freeBuffer);\n\n\treturn 1;\n}\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t SctpMtu{ 1200 };\n\tstatic constexpr uint16_t MaxSctpStreams{ 65535 };\n\n\t/* Instance methods. */\n\n\tSctpAssociation::SctpAssociation(\n\t  Listener* listener,\n\t  uint16_t os,\n\t  uint16_t mis,\n\t  size_t maxSctpMessageSize,\n\t  size_t sctpSendBufferSize,\n\t  bool isDataChannel)\n\t  : id(DepUsrSCTP::GetNextSctpAssociationId()),\n\t    listener(listener),\n\t    os(os),\n\t    mis(mis),\n\t    maxSctpMessageSize(maxSctpMessageSize),\n\t    sctpSendBufferSize(std::max(sctpSendBufferSize, maxSctpMessageSize)),\n\t    isDataChannel(isDataChannel)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Register ourselves in usrsctp.\n\t\t// NOTE: This must be done before calling usrsctp_bind().\n\t\tusrsctp_register_address(reinterpret_cast<void*>(this->id));\n\n\t\tint ret;\n\n\t\t// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)\n\t\tthis->socket = usrsctp_socket(\n\t\t  AF_CONN,\n\t\t  SOCK_STREAM,\n\t\t  IPPROTO_SCTP,\n\t\t  onRecvSctpData,\n\t\t  onSendSctpData,\n\t\t  SendBufferThreshold,\n\t\t  reinterpret_cast<void*>(this->id));\n\n\t\tif (!this->socket)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_socket() failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\tusrsctp_set_ulpinfo(this->socket, reinterpret_cast<void*>(this->id));\n\n\t\t// Make the socket non-blocking.\n\t\tret = usrsctp_set_non_blocking(this->socket, 1);\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_set_non_blocking() failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Set SO_LINGER.\n\t\t// This ensures that the usrsctp close call deletes the association. This\n\t\t// prevents usrsctp from calling the global send callback with references to\n\t\t// this class as the address.\n\t\tstruct linger lingerOpt{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tlingerOpt.l_onoff  = 1;\n\t\tlingerOpt.l_linger = 0;\n\n\t\tret = usrsctp_setsockopt(this->socket, SOL_SOCKET, SO_LINGER, &lingerOpt, sizeof(lingerOpt));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SO_LINGER) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Set SCTP_ENABLE_STREAM_RESET.\n\t\tstruct sctp_assoc_value av{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tav.assoc_value =\n\t\t  SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_RESET_ASSOC_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ;\n\n\t\tret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SCTP_ENABLE_STREAM_RESET) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Set SCTP_NODELAY.\n\t\tconst uint32_t noDelay = 1;\n\n\t\tret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_NODELAY, &noDelay, sizeof(noDelay));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SCTP_NODELAY) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Enable events.\n\t\tstruct sctp_event event{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tstd::memset(&event, 0, sizeof(event));\n\t\tevent.se_on = 1;\n\n\t\tfor (size_t i{ 0 }; i < sizeof(EventTypes) / sizeof(uint16_t); ++i)\n\t\t{\n\t\t\tevent.se_type = EventTypes[i];\n\n\t\t\tret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event));\n\n\t\t\tif (ret < 0)\n\t\t\t{\n\t\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SCTP_EVENT) failed: %s\", std::strerror(errno));\n\t\t\t}\n\t\t}\n\n\t\t// Init message.\n\t\tstruct sctp_initmsg initmsg{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tstd::memset(&initmsg, 0, sizeof(initmsg));\n\t\tinitmsg.sinit_num_ostreams  = this->os;\n\t\tinitmsg.sinit_max_instreams = this->mis;\n\n\t\tret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SCTP_INITMSG) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Server side.\n\t\tstruct sockaddr_conn sconn{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tstd::memset(&sconn, 0, sizeof(sconn));\n\t\tsconn.sconn_family = AF_CONN;\n\t\tsconn.sconn_port   = htons(5000);\n\t\tsconn.sconn_addr   = reinterpret_cast<void*>(this->id);\n#ifdef HAVE_SCONN_LEN\n\t\tsconn.sconn_len = sizeof(sconn);\n#endif\n\n\t\tret = usrsctp_bind(this->socket, reinterpret_cast<struct sockaddr*>(&sconn), sizeof(sconn));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_bind() failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\tauto bufferSize = static_cast<int>(sctpSendBufferSize);\n\n\t\tif (usrsctp_setsockopt(this->socket, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(int)) < 0)\n\t\t{\n\t\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SO_SNDBUF) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\t// Register the SctpAssociation into the global map.\n\t\tDepUsrSCTP::RegisterSctpAssociation(this);\n\t}\n\n\tSctpAssociation::~SctpAssociation()\n\t{\n\t\tMS_TRACE();\n\n\t\tusrsctp_set_ulpinfo(this->socket, nullptr);\n\t\tusrsctp_close(this->socket);\n\n\t\t// Deregister ourselves from usrsctp.\n\t\tusrsctp_deregister_address(reinterpret_cast<void*>(this->id));\n\n\t\t// Register the SctpAssociation from the global map.\n\t\tDepUsrSCTP::DeregisterSctpAssociation(this);\n\n\t\tdelete[] this->messageBuffer;\n\t}\n\n\tvoid SctpAssociation::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportConnected = true;\n\n\t\tMayConnect();\n\t}\n\n\tvoid SctpAssociation::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportConnected = false;\n\t}\n\n\tflatbuffers::Offset<FBS::SctpParameters::SctpParameters> SctpAssociation::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::SctpParameters::CreateSctpParameters(\n\t\t  builder,\n\t\t  // Add port (always 5000).\n\t\t  5000,\n\t\t  // Add OS.\n\t\t  this->os,\n\t\t  // Add MIS.\n\t\t  this->mis,\n\t\t  // Add maxMessageSize.\n\t\t  this->maxSctpMessageSize,\n\t\t  // Add sendBufferSize.\n\t\t  this->sctpSendBufferSize,\n\t\t  // Add sctpBufferedAmountLowThreshold.\n\t\t  this->sctpBufferedAmount,\n\t\t  // Add isDataChannel.\n\t\t  this->isDataChannel);\n\t}\n\n\tvoid SctpAssociation::ProcessSctpData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->sctpDataReceived = true;\n\n\t\tMayConnect();\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t// NOTE: Only uncomment this during local debugging if needed.\n\t\t// MS_DUMP_DATA(data, len);\n#endif\n\n\t\tusrsctp_conninput(reinterpret_cast<void*>(this->id), data, len, 0);\n\t}\n\n\tvoid SctpAssociation::SendSctpMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\t// This must be controlled by the DataConsumer.\n\t\tMS_ASSERT(\n\t\t  len <= this->maxSctpMessageSize,\n\t\t  \"given message exceeds max allowed message size [message size:%zu, max message size:%zu]\",\n\t\t  len,\n\t\t  this->maxSctpMessageSize);\n\n\t\tconst auto& parameters = dataConsumer->GetSctpStreamParameters();\n\n\t\t// Fill sctp_sendv_spa.\n\t\tstruct sctp_sendv_spa spa{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tstd::memset(&spa, 0, sizeof(spa));\n\t\tspa.sendv_flags             = SCTP_SEND_SNDINFO_VALID;\n\t\tspa.sendv_sndinfo.snd_sid   = parameters.streamId;\n\t\tspa.sendv_sndinfo.snd_ppid  = htonl(ppid);\n\t\tspa.sendv_sndinfo.snd_flags = SCTP_EOR;\n\n\t\t// If ordered it must be reliable.\n\t\tif (parameters.ordered)\n\t\t{\n\t\t\tspa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_NONE;\n\t\t\tspa.sendv_prinfo.pr_value  = 0;\n\t\t}\n\t\t// Configure reliability: https://tools.ietf.org/html/rfc3758\n\t\telse\n\t\t{\n\t\t\tspa.sendv_flags |= SCTP_SEND_PRINFO_VALID;\n\t\t\tspa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;\n\n\t\t\tif (parameters.maxPacketLifeTime != 0)\n\t\t\t{\n\t\t\t\tspa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL;\n\t\t\t\tspa.sendv_prinfo.pr_value  = parameters.maxPacketLifeTime;\n\t\t\t}\n\t\t\telse if (parameters.maxRetransmits != 0)\n\t\t\t{\n\t\t\t\tspa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX;\n\t\t\t\tspa.sendv_prinfo.pr_value  = parameters.maxRetransmits;\n\t\t\t}\n\t\t}\n\n\t\tthis->sctpBufferedAmount += len;\n\n\t\t// Notify the listener about the buffered amount increase regardless\n\t\t// usrsctp_sendv result.\n\t\t// In case of failure the correct value will be later provided by usrsctp\n\t\t// via onSendSctpData.\n\t\tthis->listener->OnSctpAssociationBufferedAmount(this, this->sctpBufferedAmount);\n\n\t\tconst ssize_t ret = usrsctp_sendv(\n\t\t  this->socket, msg, len, nullptr, 0, &spa, static_cast<socklen_t>(sizeof(spa)), SCTP_SENDV_SPA, 0);\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tconst bool sctpSendBufferFull = errno == EWOULDBLOCK || errno == EAGAIN;\n\n\t\t\t// SCTP send buffer being full is legit, not an error.\n\t\t\tif (sctpSendBufferFull)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\n\t\t\t\t  \"error sending SCTP message [sid:%\" PRIu16 \", ppid:%\" PRIu32 \", message size:%zu]: %s\",\n\t\t\t\t  parameters.streamId,\n\t\t\t\t  ppid,\n\t\t\t\t  len,\n\t\t\t\t  std::strerror(errno));\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"error sending SCTP message [sid:%\" PRIu16 \", ppid:%\" PRIu32 \", message size:%zu]: %s\",\n\t\t\t\t  parameters.streamId,\n\t\t\t\t  ppid,\n\t\t\t\t  len,\n\t\t\t\t  std::strerror(errno));\n\t\t\t}\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, sctpSendBufferFull);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\tif (sctpSendBufferFull)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationSendBufferFull();\n\t\t\t}\n\t\t}\n\t\telse if (cb)\n\t\t{\n\t\t\t(*cb)(true, false);\n\t\t\tdelete cb;\n\t\t}\n\t}\n\n\tvoid SctpAssociation::HandleDataProducer(RTC::DataProducer* /*dataProducer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->firstStreamCreated = true;\n\n\t\tMayConnect();\n\t}\n\n\tvoid SctpAssociation::HandleDataConsumer(RTC::DataConsumer* dataConsumer)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->firstStreamCreated = true;\n\n\t\tMayConnect();\n\n\t\tauto streamId = dataConsumer->GetSctpStreamParameters().streamId;\n\n\t\t// We need more OS.\n\t\tif (streamId > this->os - 1)\n\t\t{\n\t\t\tAddOutgoingStreams(/*force*/ false);\n\t\t}\n\t}\n\n\tvoid SctpAssociation::DataProducerClosed(RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto streamId = dataProducer->GetSctpStreamParameters().streamId;\n\n\t\t// Send SCTP_RESET_STREAMS to the remote.\n\t\t// https://tools.ietf.org/html/rfc8831#section-6.7\n\t\tif (this->isDataChannel)\n\t\t{\n\t\t\tResetSctpStream(streamId, StreamDirection::OUTGOING);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tResetSctpStream(streamId, StreamDirection::INCOMING);\n\t\t}\n\t}\n\n\tvoid SctpAssociation::DataConsumerClosed(RTC::DataConsumer* dataConsumer)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto streamId = dataConsumer->GetSctpStreamParameters().streamId;\n\n\t\t// Send SCTP_RESET_STREAMS to the remote.\n\t\tResetSctpStream(streamId, StreamDirection::OUTGOING);\n\t}\n\n\tvoid SctpAssociation::MayConnect()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Just run the SCTP stack if our state is 'new'.\n\t\t// Notice that once MayConnect() is called (and the code below is executed),\n\t\t// SCTP state will no longer be \"NEW\".\n\t\tif (this->state != SctpState::NEW)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"SCTP state is not NEW, ignoring\");\n\n\t\t\treturn;\n\t\t}\n\t\t// If the transport is not connected, don't do anything.\n\t\telse if (!this->transportConnected)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"transport is not connected, ignoring\");\n\n\t\t\treturn;\n\t\t}\n\t\t// If there are no SCTP streams yet and no SCTP data has been yet received\n\t\t// from the remote peer, don't do anything.\n\t\t// This is because the peer may never create a DataChannel so we shouldn't\n\t\t// try to connect SCTP (SCTP INIT chunk, etc) since it will timeout and\n\t\t// trigger \"SCTP failed\".\n\t\telse if (!this->firstStreamCreated && !this->sctpDataReceived)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"no SCTP stream has been created yet and no SCTP data has been received yet, ignoring\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tMS_DEBUG_TAG(sctp, \"connecting SCTP\");\n\n\t\ttry\n\t\t{\n\t\t\tint ret;\n\t\t\tstruct sockaddr_conn rconn{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\t\tstd::memset(&rconn, 0, sizeof(rconn));\n\t\t\trconn.sconn_family = AF_CONN;\n\t\t\trconn.sconn_port   = htons(5000);\n\t\t\trconn.sconn_addr   = reinterpret_cast<void*>(this->id);\n#ifdef HAVE_SCONN_LEN\n\t\t\trconn.sconn_len = sizeof(rconn);\n#endif\n\n\t\t\tret = usrsctp_connect(this->socket, reinterpret_cast<struct sockaddr*>(&rconn), sizeof(rconn));\n\n\t\t\tif (ret < 0 && errno != EINPROGRESS)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"usrsctp_connect() failed: %s\", std::strerror(errno));\n\t\t\t}\n\n\t\t\t// Disable MTU discovery.\n\t\t\tsctp_paddrparams peerAddrParams{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\t\tstd::memset(&peerAddrParams, 0, sizeof(peerAddrParams));\n\t\t\tstd::memcpy(&peerAddrParams.spp_address, &rconn, sizeof(rconn));\n\t\t\tpeerAddrParams.spp_flags = SPP_PMTUD_DISABLE;\n\n\t\t\t// The MTU value provided specifies the space available for chunks in the\n\t\t\t// packet, so let's subtract the SCTP header size.\n\t\t\tpeerAddrParams.spp_pathmtu = SctpMtu - sizeof(struct sctp_common_header);\n\n\t\t\tret = usrsctp_setsockopt(\n\t\t\t  this->socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peerAddrParams, sizeof(peerAddrParams));\n\n\t\t\tif (ret < 0)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"usrsctp_setsockopt(SCTP_PEER_ADDR_PARAMS) failed: %s\", std::strerror(errno));\n\t\t\t}\n\n\t\t\t// Announce connecting state.\n\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CONNECTING (in MayConnect())\");\n\n\t\t\tthis->state = SctpState::CONNECTING;\n\t\t\tthis->listener->OnSctpAssociationConnecting(this);\n\t\t}\n\t\tcatch (const MediaSoupError& /*error*/)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"SCTP state switched to FAILED (in MayConnect())\");\n\n\t\t\tthis->state = SctpState::FAILED;\n\t\t\tthis->listener->OnSctpAssociationFailed(this);\n\t\t}\n\t}\n\n\tvoid SctpAssociation::ResetSctpStream(uint16_t streamId, StreamDirection direction) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing if an outgoing stream that could not be allocated by us.\n\t\tif (direction == StreamDirection::OUTGOING && streamId > this->os - 1)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tint ret;\n\t\tstruct sctp_assoc_value av{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\t\tsocklen_t len = sizeof(av);\n\n\t\tret = usrsctp_getsockopt(this->socket, IPPROTO_SCTP, SCTP_RECONFIG_SUPPORTED, &av, &len);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tif (av.assoc_value != 1)\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(sctp, \"stream reconfiguration not negotiated\");\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"could not retrieve whether stream reconfiguration has been negotiated: %s\\n\",\n\t\t\t  std::strerror(errno));\n\n\t\t\treturn;\n\t\t}\n\n\t\t// As per spec: https://tools.ietf.org/html/rfc6525#section-4.1\n\t\tlen = sizeof(sctp_assoc_t) + ((2 + 1) * sizeof(uint16_t));\n\n\t\tauto* srs = static_cast<struct sctp_reset_streams*>(std::malloc(len));\n\n\t\tswitch (direction)\n\t\t{\n\t\t\tcase StreamDirection::INCOMING:\n\t\t\t\tsrs->srs_flags = SCTP_STREAM_RESET_INCOMING;\n\t\t\t\tbreak;\n\n\t\t\tcase StreamDirection::OUTGOING:\n\t\t\t\tsrs->srs_flags = SCTP_STREAM_RESET_OUTGOING;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tsrs->srs_number_streams = 1;\n\t\tsrs->srs_stream_list[0] = streamId; // No need for htonl().\n\n\t\tret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_RESET_STREAMS, srs, len);\n\n\t\tif (ret == 0)\n\t\t{\n\t\t\tMS_DEBUG_TAG(sctp, \"SCTP_RESET_STREAMS sent [streamId:%\" PRIu16 \"]\", streamId);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"usrsctp_setsockopt(SCTP_RESET_STREAMS) failed: %s\", std::strerror(errno));\n\t\t}\n\n\t\tstd::free(srs);\n\t}\n\n\tvoid SctpAssociation::AddOutgoingStreams(bool force)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint16_t additionalOs{ 0 };\n\n\t\tif (MaxSctpStreams - this->os >= 32)\n\t\t{\n\t\t\tadditionalOs = 32;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tadditionalOs = MaxSctpStreams - this->os;\n\t\t}\n\n\t\tif (additionalOs == 0)\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"cannot add more outgoing streams [OS:%\" PRIu16 \"]\", this->os);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto nextDesiredOs = this->os + additionalOs;\n\n\t\t// Already in progress, ignore (unless forced).\n\t\tif (!force && nextDesiredOs == this->desiredOs)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Update desired value.\n\t\tthis->desiredOs = nextDesiredOs;\n\n\t\t// If not connected, defer it.\n\t\tif (this->state != SctpState::CONNECTED)\n\t\t{\n\t\t\tMS_DEBUG_TAG(sctp, \"SCTP not connected, deferring OS increase\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tstruct sctp_add_streams sas{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\tstd::memset(&sas, 0, sizeof(sas));\n\t\tsas.sas_instrms  = 0;\n\t\tsas.sas_outstrms = additionalOs;\n\n\t\tMS_DEBUG_TAG(sctp, \"adding %\" PRIu16 \" outgoing streams\", additionalOs);\n\n\t\tconst int ret = usrsctp_setsockopt(\n\t\t  this->socket, IPPROTO_SCTP, SCTP_ADD_STREAMS, &sas, static_cast<socklen_t>(sizeof(sas)));\n\n\t\tif (ret < 0)\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"usrsctp_setsockopt(SCTP_ADD_STREAMS) failed: %s\", std::strerror(errno));\n\t\t}\n\t}\n\n\tvoid SctpAssociation::OnUsrSctpSendSctpData(void* buffer, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint8_t* data = static_cast<uint8_t*>(buffer);\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\t// NOTE: Only uncomment this during local debugging if needed.\n\t\t// MS_DUMP_DATA(data, len);\n#endif\n\n\t\tthis->listener->OnSctpAssociationSendData(this, data, len);\n\t}\n\n\tvoid SctpAssociation::OnUsrSctpReceiveSctpData(\n\t  uint16_t streamId, uint16_t ssn, uint32_t ppid, int flags, const uint8_t* data, size_t len)\n\t{\n\t\t// Ignore WebRTC DataChannel Control DATA chunks.\n\t\tif (ppid == 50)\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"ignoring SCTP data with ppid:50 (WebRTC DataChannel Control)\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->messageBufferLen != 0 && ssn != this->lastSsnReceived)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"message chunk received with different SSN while buffer not empty, buffer discarded [ssn:%\" PRIu16\n\t\t\t  \", last ssn received:%\" PRIu16 \"]\",\n\t\t\t  ssn,\n\t\t\t  this->lastSsnReceived);\n\n\t\t\tthis->messageBufferLen = 0;\n\t\t}\n\n\t\t// Update last SSN received.\n\t\tthis->lastSsnReceived = ssn;\n\n\t\tauto eor = static_cast<bool>(flags & MSG_EOR);\n\n\t\tif (this->messageBufferLen + len > this->maxSctpMessageSize)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"ongoing received message exceeds max allowed message size [message size:%zu, max message size:%zu, eor:%u]\",\n\t\t\t  this->messageBufferLen + len,\n\t\t\t  this->maxSctpMessageSize,\n\t\t\t  eor ? 1 : 0);\n\n\t\t\tthis->lastSsnReceived = 0;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If end of message and there is no buffered data, notify it directly.\n\t\tif (eor && this->messageBufferLen == 0)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"directly notifying listener [eor:1, buffer len:0]\");\n\n\t\t\tthis->listener->OnSctpAssociationMessageReceived(this, streamId, data, len, ppid);\n\t\t}\n\t\t// If end of message and there is buffered data, append data and notify buffer.\n\t\telse if (eor && this->messageBufferLen != 0)\n\t\t{\n\t\t\tstd::memcpy(this->messageBuffer + this->messageBufferLen, data, len);\n\t\t\tthis->messageBufferLen += len;\n\n\t\t\tMS_DEBUG_DEV(\"notifying listener [eor:1, buffer len:%zu]\", this->messageBufferLen);\n\n\t\t\tthis->listener->OnSctpAssociationMessageReceived(\n\t\t\t  this, streamId, this->messageBuffer, this->messageBufferLen, ppid);\n\n\t\t\tthis->messageBufferLen = 0;\n\t\t}\n\t\t// If non end of message, append data to the buffer.\n\t\telse if (!eor)\n\t\t{\n\t\t\t// Allocate the buffer if not already done.\n\t\t\tif (!this->messageBuffer)\n\t\t\t{\n\t\t\t\tthis->messageBuffer = new uint8_t[this->maxSctpMessageSize];\n\t\t\t}\n\n\t\t\tstd::memcpy(this->messageBuffer + this->messageBufferLen, data, len);\n\t\t\tthis->messageBufferLen += len;\n\n\t\t\tMS_DEBUG_DEV(\"data buffered [eor:0, buffer len:%zu]\", this->messageBufferLen);\n\t\t}\n\t}\n\n\tvoid SctpAssociation::OnUsrSctpReceiveSctpNotification(union sctp_notification* notification, size_t len)\n\t{\n\t\tif (notification->sn_header.sn_length != (uint32_t)len)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (notification->sn_header.sn_type)\n\t\t{\n\t\t\tcase SCTP_ADAPTATION_INDICATION:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"SCTP adaptation indication [%x]\",\n\t\t\t\t  notification->sn_adaptation_event.sai_adaptation_ind);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SCTP_ASSOC_CHANGE:\n\t\t\t{\n\t\t\t\tswitch (notification->sn_assoc_change.sac_state)\n\t\t\t\t{\n\t\t\t\t\tcase SCTP_COMM_UP:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  sctp,\n\t\t\t\t\t\t  \"SCTP association connected, streams [out:%\" PRIu16 \", in:%\" PRIu16 \"]\",\n\t\t\t\t\t\t  notification->sn_assoc_change.sac_outbound_streams,\n\t\t\t\t\t\t  notification->sn_assoc_change.sac_inbound_streams);\n\n\t\t\t\t\t\t// Update our OS.\n\t\t\t\t\t\tthis->os = notification->sn_assoc_change.sac_outbound_streams;\n\n\t\t\t\t\t\t// Increase if requested before connected.\n\t\t\t\t\t\tif (this->desiredOs > this->os)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAddOutgoingStreams(/*force*/ true);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this->state != SctpState::CONNECTED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CONNECTED (in SCTP_ASSOC_CHANGE)\");\n\n\t\t\t\t\t\t\tthis->state = SctpState::CONNECTED;\n\t\t\t\t\t\t\tthis->listener->OnSctpAssociationConnected(this);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SCTP_COMM_LOST:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (notification->sn_header.sn_length > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstatic const size_t BufferSize{ 1024 };\n\t\t\t\t\t\t\tstatic thread_local char buffer[BufferSize];\n\n\t\t\t\t\t\t\tconst uint32_t len =\n\t\t\t\t\t\t\t  notification->sn_assoc_change.sac_length - sizeof(struct sctp_assoc_change);\n\n\t\t\t\t\t\t\tfor (uint32_t i{ 0 }; i < len; ++i)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstd::snprintf(\n\t\t\t\t\t\t\t\t  buffer, BufferSize, \" 0x%02x\", notification->sn_assoc_change.sac_info[i]);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tMS_DEBUG_TAG(sctp, \"SCTP communication lost [info:%s]\", buffer);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(sctp, \"SCTP communication lost\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this->state != SctpState::CLOSED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CLOSED (in SCTP_COMM_LOST)\");\n\n\t\t\t\t\t\t\tthis->state = SctpState::CLOSED;\n\t\t\t\t\t\t\tthis->listener->OnSctpAssociationClosed(this);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SCTP_RESTART:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  sctp,\n\t\t\t\t\t\t  \"SCTP remote association restarted, streams [out:%\" PRIu16 \", int:%\" PRIu16 \"]\",\n\t\t\t\t\t\t  notification->sn_assoc_change.sac_outbound_streams,\n\t\t\t\t\t\t  notification->sn_assoc_change.sac_inbound_streams);\n\n\t\t\t\t\t\t// Update our OS.\n\t\t\t\t\t\tthis->os = notification->sn_assoc_change.sac_outbound_streams;\n\n\t\t\t\t\t\t// Increase if requested before connected.\n\t\t\t\t\t\tif (this->desiredOs > this->os)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tAddOutgoingStreams(/*force*/ true);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this->state != SctpState::CONNECTED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CONNECTED (in SCTP_RESTART)\");\n\n\t\t\t\t\t\t\tthis->state = SctpState::CONNECTED;\n\t\t\t\t\t\t\tthis->listener->OnSctpAssociationConnected(this);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SCTP_SHUTDOWN_COMP:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(sctp, \"SCTP association gracefully closed\");\n\n\t\t\t\t\t\tif (this->state != SctpState::CLOSED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CLOSED (in SCTP_SHUTDOWN_COMP)\");\n\n\t\t\t\t\t\t\tthis->state = SctpState::CLOSED;\n\t\t\t\t\t\t\tthis->listener->OnSctpAssociationClosed(this);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase SCTP_CANT_STR_ASSOC:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (notification->sn_header.sn_length > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstatic const size_t BufferSize{ 1024 };\n\t\t\t\t\t\t\tstatic thread_local char buffer[BufferSize];\n\n\t\t\t\t\t\t\tconst uint32_t len =\n\t\t\t\t\t\t\t  notification->sn_assoc_change.sac_length - sizeof(struct sctp_assoc_change);\n\n\t\t\t\t\t\t\tfor (uint32_t i{ 0 }; i < len; ++i)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstd::snprintf(\n\t\t\t\t\t\t\t\t  buffer, BufferSize, \" 0x%02x\", notification->sn_assoc_change.sac_info[i]);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tMS_WARN_TAG(sctp, \"SCTP setup failed: %s\", buffer);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (this->state != SctpState::FAILED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to FAILED (in SCTP_CANT_STR_ASSOC)\");\n\n\t\t\t\t\t\t\tthis->state = SctpState::FAILED;\n\t\t\t\t\t\t\tthis->listener->OnSctpAssociationFailed(this);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// https://tools.ietf.org/html/rfc6525#section-6.1.2.\n\t\t\tcase SCTP_ASSOC_RESET_EVENT:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(sctp, \"SCTP association reset event received\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// An Operation Error is not considered fatal in and of itself, but may be\n\t\t\t// used with an ABORT chunk to report a fatal condition.\n\t\t\tcase SCTP_REMOTE_ERROR:\n\t\t\t{\n\t\t\t\tstatic const size_t BufferSize{ 1024 };\n\t\t\t\tstatic thread_local char buffer[BufferSize];\n\n\t\t\t\tconst uint32_t len =\n\t\t\t\t  notification->sn_remote_error.sre_length - sizeof(struct sctp_remote_error);\n\n\t\t\t\tfor (uint32_t i{ 0 }; i < len; i++)\n\t\t\t\t{\n\t\t\t\t\tstd::snprintf(buffer, BufferSize, \"0x%02x\", notification->sn_remote_error.sre_data[i]);\n\t\t\t\t}\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"remote SCTP association error [type:0x%04x, data:%s]\",\n\t\t\t\t  notification->sn_remote_error.sre_error,\n\t\t\t\t  buffer);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// When a peer sends a SHUTDOWN, SCTP delivers this notification to\n\t\t\t// inform the application that it should cease sending data.\n\t\t\tcase SCTP_SHUTDOWN_EVENT:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(sctp, \"remote SCTP association shutdown\");\n\n\t\t\t\tif (this->state != SctpState::CLOSED)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"SCTP state switched to CLOSED (in SCTP_SHUTDOWN_EVENT)\");\n\n\t\t\t\t\tthis->state = SctpState::CLOSED;\n\t\t\t\t\tthis->listener->OnSctpAssociationClosed(this);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SCTP_SEND_FAILED_EVENT:\n\t\t\t{\n\t\t\t\tstatic const size_t BufferSize{ 1024 };\n\t\t\t\tstatic thread_local char buffer[BufferSize];\n\n\t\t\t\tconst uint32_t len =\n\t\t\t\t  notification->sn_send_failed_event.ssfe_length - sizeof(struct sctp_send_failed_event);\n\n\t\t\t\tfor (uint32_t i{ 0 }; i < len; ++i)\n\t\t\t\t{\n\t\t\t\t\tstd::snprintf(buffer, BufferSize, \"0x%02x\", notification->sn_send_failed_event.ssfe_data[i]);\n\t\t\t\t}\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"SCTP message sent failure [streamId:%\" PRIu16 \", ppid:%\" PRIu32\n\t\t\t\t  \", sent:%s, error:0x%08x, info:%s]\",\n\t\t\t\t  notification->sn_send_failed_event.ssfe_info.snd_sid,\n\t\t\t\t  ntohl(notification->sn_send_failed_event.ssfe_info.snd_ppid),\n\t\t\t\t  (notification->sn_send_failed_event.ssfe_flags & SCTP_DATA_SENT) ? \"yes\" : \"no\",\n\t\t\t\t  notification->sn_send_failed_event.ssfe_error,\n\t\t\t\t  buffer);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SCTP_STREAM_RESET_EVENT:\n\t\t\t{\n\t\t\t\tbool incoming{ false };\n\t\t\t\tbool outgoing{ false };\n\t\t\t\tconst uint16_t numStreams =\n\t\t\t\t  (notification->sn_strreset_event.strreset_length - sizeof(struct sctp_stream_reset_event)) /\n\t\t\t\t  sizeof(uint16_t);\n\n\t\t\t\tif (notification->sn_strreset_event.strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN)\n\t\t\t\t{\n\t\t\t\t\tincoming = true;\n\t\t\t\t}\n\n\t\t\t\tif (notification->sn_strreset_event.strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN)\n\t\t\t\t{\n\t\t\t\t\toutgoing = true;\n\t\t\t\t}\n\n\t\t\t\tif (MS_HAS_DEBUG_TAG(sctp))\n\t\t\t\t{\n\t\t\t\t\tstd::string streamIds;\n\n\t\t\t\t\tfor (uint16_t i{ 0 }; i < numStreams; ++i)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto streamId = notification->sn_strreset_event.strreset_stream_list[i];\n\n\t\t\t\t\t\t// Don't log more than 5 stream ids.\n\t\t\t\t\t\tif (i > 4)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstreamIds.append(\"...\");\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (i > 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstreamIds.append(\",\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstreamIds.append(std::to_string(streamId));\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"SCTP stream reset event [flags:%x, i|o:%s|%s, num streams:%\" PRIu16 \", stream ids:%s]\",\n\t\t\t\t\t  notification->sn_strreset_event.strreset_flags,\n\t\t\t\t\t  incoming ? \"true\" : \"false\",\n\t\t\t\t\t  outgoing ? \"true\" : \"false\",\n\t\t\t\t\t  numStreams,\n\t\t\t\t\t  streamIds.c_str());\n\t\t\t\t}\n\n\t\t\t\t// Special case for WebRTC DataChannels in which we must also reset our\n\t\t\t\t// outgoing SCTP stream.\n\t\t\t\tif (incoming && !outgoing && this->isDataChannel)\n\t\t\t\t{\n\t\t\t\t\tfor (uint16_t i{ 0 }; i < numStreams; ++i)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto streamId = notification->sn_strreset_event.strreset_stream_list[i];\n\n\t\t\t\t\t\tResetSctpStream(streamId, StreamDirection::OUTGOING);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase SCTP_STREAM_CHANGE_EVENT:\n\t\t\t{\n\t\t\t\tif (notification->sn_strchange_event.strchange_flags == 0)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"SCTP stream changed, streams [out:%\" PRIu16 \", in:%\" PRIu16 \", flags:%x]\",\n\t\t\t\t\t  notification->sn_strchange_event.strchange_outstrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_instrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_flags);\n\t\t\t\t}\n\t\t\t\telse if (notification->sn_strchange_event.strchange_flags & SCTP_STREAM_RESET_DENIED)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"SCTP stream change denied, streams [out:%\" PRIu16 \", in:%\" PRIu16 \", flags:%x]\",\n\t\t\t\t\t  notification->sn_strchange_event.strchange_outstrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_instrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_flags);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\telse if (notification->sn_strchange_event.strchange_flags & SCTP_STREAM_RESET_FAILED)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  sctp,\n\t\t\t\t\t  \"SCTP stream change failed, streams [out:%\" PRIu16 \", in:%\" PRIu16 \", flags:%x]\",\n\t\t\t\t\t  notification->sn_strchange_event.strchange_outstrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_instrms,\n\t\t\t\t\t  notification->sn_strchange_event.strchange_flags);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Update OS.\n\t\t\t\tthis->os = notification->sn_strchange_event.strchange_outstrms;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp, \"unhandled SCTP event received [type:%\" PRIu16 \"]\", notification->sn_header.sn_type);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SctpAssociation::OnUsrSctpSentData(uint32_t freeBuffer)\n\t{\n\t\tauto previousSctpBufferedAmount = this->sctpBufferedAmount;\n\n\t\tthis->sctpBufferedAmount = this->sctpSendBufferSize - freeBuffer;\n\n\t\tif (this->sctpBufferedAmount != previousSctpBufferedAmount)\n\t\t{\n\t\t\tthis->listener->OnSctpAssociationBufferedAmount(this, this->sctpBufferedAmount);\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SctpDictionaries/SctpStreamParameters.cpp",
    "content": "#define MS_CLASS \"RTC::SctpStreamParameters\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/SctpDictionaries.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tSctpStreamParameters::SctpStreamParameters(const FBS::SctpParameters::SctpStreamParameters* data)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->streamId = data->streamId();\n\n\t\tif (this->streamId > 65534)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"streamId must not be greater than 65534\");\n\t\t}\n\n\t\t// ordered is optional.\n\t\tbool orderedGiven = false;\n\n\t\tif (auto ordered = data->ordered(); ordered.has_value())\n\t\t{\n\t\t\torderedGiven  = true;\n\t\t\tthis->ordered = ordered.value();\n\t\t}\n\n\t\t// maxPacketLifeTime is optional.\n\t\tif (auto maxPacketLifeTime = data->maxPacketLifeTime(); maxPacketLifeTime.has_value())\n\t\t{\n\t\t\tthis->maxPacketLifeTime = maxPacketLifeTime.value();\n\t\t}\n\n\t\t// maxRetransmits is optional.\n\t\tif (auto maxRetransmits = data->maxRetransmits(); maxRetransmits.has_value())\n\t\t{\n\t\t\tthis->maxRetransmits = maxRetransmits.value();\n\t\t}\n\n\t\tif (this->maxPacketLifeTime && this->maxRetransmits)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"cannot provide both maxPacketLifeTime and maxRetransmits\");\n\t\t}\n\n\t\tif (orderedGiven && this->ordered && (this->maxPacketLifeTime || this->maxRetransmits))\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"cannot be ordered with maxPacketLifeTime or maxRetransmits\");\n\t\t}\n\t\telse if (!orderedGiven && (this->maxPacketLifeTime || this->maxRetransmits))\n\t\t{\n\t\t\tthis->ordered = false;\n\t\t}\n\t}\n\n\tflatbuffers::Offset<FBS::SctpParameters::SctpStreamParameters> SctpStreamParameters::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn FBS::SctpParameters::CreateSctpStreamParameters(\n\t\t  builder,\n\t\t  this->streamId,\n\t\t  this->ordered,\n\t\t  this->maxPacketLifeTime ? flatbuffers::Optional<uint16_t>(this->maxPacketLifeTime)\n\t\t                          : flatbuffers::nullopt,\n\t\t  this->maxRetransmits ? flatbuffers::Optional<uint16_t>(this->maxRetransmits)\n\t\t                       : flatbuffers::nullopt);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SctpListener.cpp",
    "content": "#define MS_CLASS \"RTC::SctpListener\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SctpListener.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"RTC/DataProducer.hpp\"\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tflatbuffers::Offset<FBS::Transport::SctpListener> SctpListener::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add streamIdTable.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::Uint16String>> streamIdTable;\n\n\t\tfor (const auto& kv : this->streamIdTable)\n\t\t{\n\t\t\tauto streamId      = kv.first;\n\t\t\tauto* dataProducer = kv.second;\n\n\t\t\tstreamIdTable.emplace_back(\n\t\t\t  FBS::Common::CreateUint16StringDirect(builder, streamId, dataProducer->id.c_str()));\n\t\t}\n\n\t\treturn FBS::Transport::CreateSctpListenerDirect(builder, &streamIdTable);\n\t}\n\n\tvoid SctpListener::AddDataProducer(RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto& sctpParameters = dataProducer->GetSctpStreamParameters();\n\n\t\t// Add entries into the streamIdTable.\n\t\tauto streamId = sctpParameters.streamId;\n\n\t\tif (this->streamIdTable.find(streamId) == this->streamIdTable.end())\n\t\t{\n\t\t\tthis->streamIdTable[streamId] = dataProducer;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_THROW_ERROR(\"streamId already exists in SCTP listener [streamId:%\" PRIu16 \"]\", streamId);\n\t\t}\n\t}\n\n\tvoid SctpListener::RemoveDataProducer(RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove from the listener table all entries pointing to the DataProducer.\n\t\tfor (auto it = this->streamIdTable.begin(); it != this->streamIdTable.end();)\n\t\t{\n\t\t\tif (it->second == dataProducer)\n\t\t\t{\n\t\t\t\tit = this->streamIdTable.erase(it);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t++it;\n\t\t\t}\n\t\t}\n\t}\n\n\tRTC::DataProducer* SctpListener::GetDataProducer(uint16_t streamId)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->streamIdTable.find(streamId);\n\n\t\tif (it != this->streamIdTable.end())\n\t\t{\n\t\t\tauto* dataProducer = it->second;\n\n\t\t\treturn dataProducer;\n\t\t}\n\n\t\treturn nullptr;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SenderBandwidthEstimator.cpp",
    "content": "#define MS_CLASS \"RTC::SenderBandwidthEstimator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SenderBandwidthEstimator.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\t/* Static. */\n\n\t// static constexpr uint64_t AvailableBitrateEventInterval{ 2000u }; // In ms.\n\tstatic constexpr uint16_t MaxSentInfoAge{ 2000u }; // TODO: Let's see.\n\tstatic constexpr float DefaultRtt{ 100 };\n\n\t/* Instance methods. */\n\n\tSenderBandwidthEstimator::SenderBandwidthEstimator(\n\t  RTC::SenderBandwidthEstimator::Listener* listener,\n\t  SharedInterface* shared,\n\t  uint32_t initialAvailableBitrate)\n\t  : listener(listener),\n\t    shared(shared),\n\t    initialAvailableBitrate(initialAvailableBitrate),\n\t    rtt(DefaultRtt),\n\t    sendTransmission(1000u),\n\t    sendTransmissionTrend(0.15f)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tSenderBandwidthEstimator::~SenderBandwidthEstimator()\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid SenderBandwidthEstimator::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->availableBitrate              = this->initialAvailableBitrate;\n\t\tthis->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs();\n\t}\n\n\tvoid SenderBandwidthEstimator::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->availableBitrate = 0u;\n\n\t\tthis->sentInfos.clear();\n\t\tthis->cummulativeResult.Reset();\n\t}\n\n\tvoid SenderBandwidthEstimator::RtpPacketSent(const SentInfo& sentInfo)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto nowMs = sentInfo.sentAtMs;\n\n\t\t// Remove old sent infos.\n\t\tauto it = this->sentInfos.lower_bound(sentInfo.wideSeq - MaxSentInfoAge + 1);\n\n\t\tthis->sentInfos.erase(this->sentInfos.begin(), it);\n\n\t\t// Insert the sent info into the map.\n\t\tthis->sentInfos[sentInfo.wideSeq] = sentInfo;\n\n\t\t// Fill the send transmission counter.\n\t\tthis->sendTransmission.Update(sentInfo.size, nowMs);\n\n\t\t// Update the send transmission trend.\n\t\tauto sendBitrate = this->sendTransmission.GetRate(nowMs);\n\n\t\tthis->sendTransmissionTrend.Update(sendBitrate, nowMs);\n\n\t\t// TODO: Remove.\n\t\t// MS_DEBUG_DEV(\n\t\t// \t\"[wideSeq:%\" PRIu16 \", size:%zu, isProbation:%s, bitrate:%\" PRIu32 \"]\",\n\t\t// \tsentInfo.wideSeq,\n\t\t// \tsentInfo.size,\n\t\t// \tsentInfo.isProbation ? \"true\" : \"false\",\n\t\t// \tsendBitrate);\n\t}\n\n\tvoid SenderBandwidthEstimator::ReceiveRtcpTransportFeedback(\n\t  const RTC::RTCP::FeedbackRtpTransportPacket* feedback)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto nowMs               = this->shared->GetTimeMs();\n\t\tconst uint64_t elapsedMs = nowMs - this->cummulativeResult.GetStartedAtMs();\n\n\t\t// Drop ongoing cummulative result if too old.\n\t\tif (elapsedMs > 1000u)\n\t\t{\n\t\t\tthis->cummulativeResult.Reset();\n\t\t}\n\n\t\tfor (auto& result : feedback->GetPacketResults())\n\t\t{\n\t\t\tif (!result.received)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst uint16_t wideSeq = result.sequenceNumber;\n\t\t\tauto it                = this->sentInfos.find(wideSeq);\n\n\t\t\tif (it == this->sentInfos.end())\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"received packet not present in sent infos [wideSeq:%\" PRIu16 \"]\", wideSeq);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tauto& sentInfo = it->second;\n\n\t\t\tif (!sentInfo.isProbation)\n\t\t\t{\n\t\t\t\tthis->cummulativeResult.AddPacket(\n\t\t\t\t  sentInfo.size, static_cast<int64_t>(sentInfo.sentAtMs), result.receivedAtMs);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->probationCummulativeResult.AddPacket(\n\t\t\t\t  sentInfo.size, static_cast<int64_t>(sentInfo.sentAtMs), result.receivedAtMs);\n\t\t\t}\n\t\t}\n\n\t\t// Handle probation packets separately.\n\t\tif (this->probationCummulativeResult.GetNumPackets() >= 2u)\n\t\t{\n\t\t\t// MS_DEBUG_DEV(\n\t\t\t//   \"probation [packets:%zu, size:%zu] \"\n\t\t\t//   \"[send bps:%\" PRIu32 \", recv bps:%\" PRIu32 \"] \",\n\t\t\t//   this->probationCummulativeResult.GetNumPackets(),\n\t\t\t//   this->probationCummulativeResult.GetTotalSize(),\n\t\t\t//   this->probationCummulativeResult.GetSendBitrate(),\n\t\t\t//   this->probationCummulativeResult.GetReceiveBitrate());\n\n\t\t\tEstimateAvailableBitrate(this->probationCummulativeResult);\n\n\t\t\t// Reset probation cummulative result.\n\t\t\tthis->probationCummulativeResult.Reset();\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Reset probation cummulative result.\n\t\t\tthis->probationCummulativeResult.Reset();\n\t\t}\n\n\t\t// Handle real and probation packets all together.\n\t\tif (elapsedMs >= 100u && this->cummulativeResult.GetNumPackets() >= 20u)\n\t\t{\n\t\t\t// auto sendBitrate      = this->sendTransmission.GetRate(nowMs);\n\t\t\t// auto sendBitrateTrend = this->sendTransmissionTrend.GetValue();\n\n\t\t\t// MS_DEBUG_DEV(\n\t\t\t//   \"real+prob [packets:%zu, size:%zu] \"\n\t\t\t//   \"[send bps:%\" PRIu32 \", recv bps:%\" PRIu32\n\t\t\t//   \"] \"\n\t\t\t//   \"[real bps:%\" PRIu32 \", trend bps:%\" PRIu32 \"]\",\n\t\t\t//   this->cummulativeResult.GetNumPackets(),\n\t\t\t//   this->cummulativeResult.GetTotalSize(),\n\t\t\t//   this->cummulativeResult.GetSendBitrate(),\n\t\t\t//   this->cummulativeResult.GetReceiveBitrate(),\n\t\t\t//   sendBitrate,\n\t\t\t//   sendBitrateTrend);\n\n\t\t\tEstimateAvailableBitrate(this->cummulativeResult);\n\n\t\t\t// Reset cummulative result.\n\t\t\tthis->cummulativeResult.Reset();\n\t\t}\n\t}\n\n\tvoid SenderBandwidthEstimator::EstimateAvailableBitrate(CummulativeResult& cummulativeResult)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto previousAvailableBitrate = this->availableBitrate;\n\n\t\tconst double ratio = static_cast<double>(cummulativeResult.GetReceiveBitrate()) /\n\t\t                     static_cast<double>(cummulativeResult.GetSendBitrate());\n\t\tauto bitrate =\n\t\t  std::min<uint32_t>(cummulativeResult.GetReceiveBitrate(), cummulativeResult.GetSendBitrate());\n\n\t\tif (0.75f <= ratio && ratio <= 1.25f)\n\t\t{\n\t\t\tif (bitrate > this->availableBitrate)\n\t\t\t{\n\t\t\t\tthis->availableBitrate = bitrate;\n\n\t\t\t\tMS_DEBUG_DEV(\"BWE UP [ratio:%f, availableBitrate:%\" PRIu32 \"]\", ratio, this->availableBitrate);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (bitrate < this->availableBitrate)\n\t\t\t{\n\t\t\t\tthis->availableBitrate = bitrate;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"BWE DOWN [ratio:%f, availableBitrate:%\" PRIu32 \"]\", ratio, this->availableBitrate);\n\t\t\t}\n\t\t}\n\n\t\t// TODO: No, should wait for AvailableBitrateEventInterval and so on.\n\t\tthis->listener->OnSenderBandwidthEstimatorAvailableBitrate(\n\t\t  this, this->availableBitrate, previousAvailableBitrate);\n\t}\n\n\tvoid SenderBandwidthEstimator::UpdateRtt(float rtt)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtt = rtt;\n\t}\n\n\tuint32_t SenderBandwidthEstimator::GetAvailableBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->availableBitrate;\n\t}\n\n\tvoid SenderBandwidthEstimator::RescheduleNextAvailableBitrateEvent()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs();\n\t}\n\n\tvoid SenderBandwidthEstimator::CummulativeResult::AddPacket(\n\t  size_t size, int64_t sentAtMs, int64_t receivedAtMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->numPackets == 0u)\n\t\t{\n\t\t\tthis->firstPacketSentAtMs     = sentAtMs;\n\t\t\tthis->firstPacketReceivedAtMs = receivedAtMs;\n\t\t\tthis->lastPacketSentAtMs      = sentAtMs;\n\t\t\tthis->lastPacketReceivedAtMs  = receivedAtMs;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->firstPacketSentAtMs     = std::min(sentAtMs, this->firstPacketSentAtMs);\n\t\t\tthis->firstPacketReceivedAtMs = std::min(receivedAtMs, this->firstPacketReceivedAtMs);\n\t\t\tthis->lastPacketSentAtMs      = std::max(sentAtMs, this->lastPacketSentAtMs);\n\t\t\tthis->lastPacketReceivedAtMs  = std::max(receivedAtMs, this->lastPacketReceivedAtMs);\n\t\t}\n\n\t\tthis->numPackets++;\n\t\tthis->totalSize += size;\n\t}\n\n\tvoid SenderBandwidthEstimator::CummulativeResult::Reset()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->numPackets              = 0u;\n\t\tthis->totalSize               = 0u;\n\t\tthis->firstPacketSentAtMs     = 0u;\n\t\tthis->lastPacketSentAtMs      = 0u;\n\t\tthis->firstPacketReceivedAtMs = 0u;\n\t\tthis->lastPacketReceivedAtMs  = 0u;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SeqManager.cpp",
    "content": "#define MS_CLASS \"RTC::SeqManager\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SeqManager.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <iterator>\n\nnamespace RTC\n{\n\ttemplate<typename T, uint8_t N>\n\tbool SeqManager<T, N>::SeqLowerThan::operator()(T lhs, T rhs) const\n\t{\n\t\treturn Utils::Number::IsLowerThan<T, N>(lhs, rhs);\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tbool SeqManager<T, N>::SeqHigherThan::operator()(T lhs, T rhs) const\n\t{\n\t\treturn Utils::Number::IsHigherThan<T, N>(lhs, rhs);\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tbool SeqManager<T, N>::IsSeqHigherThan(T lhs, T rhs)\n\t{\n\t\treturn Utils::Number::IsHigherThan<T, N>(lhs, rhs);\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tbool SeqManager<T, N>::IsSeqLowerThan(T lhs, T rhs)\n\t{\n\t\treturn Utils::Number::IsLowerThan<T, N>(lhs, rhs);\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tSeqManager<T, N>::SeqManager(T initialOutput) : initialOutput(initialOutput)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tvoid SeqManager<T, N>::Sync(T input)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Update base.\n\t\tthis->base = (this->maxOutput - input) & SeqManager::MaxValue;\n\n\t\t// Update maxInput.\n\t\tthis->maxInput = input;\n\n\t\t// Clear dropped set.\n\t\tthis->dropped.clear();\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tvoid SeqManager<T, N>::Drop(T input)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Mark as dropped if 'input' is higher than anyone already processed.\n\t\tif (SeqManager<T, N>::IsSeqHigherThan(input, this->maxInput))\n\t\t{\n\t\t\tthis->maxInput   = input;\n\t\t\tthis->maxDropped = input;\n\t\t\t// Insert input in the last position.\n\t\t\t// Explicitly insert at the end, which is more performant.\n\t\t\tthis->dropped.insert(this->dropped.end(), input);\n\n\t\t\tClearDropped();\n\t\t}\n\t\t// Mark as dropped if no input was forwarded after the last dropped one and\n\t\t// 'input' is higher than the last forwarded input 'this->maxForwarded'.\n\t\t// Allows for properly accounting for out of order drops until an input is forwarded.\n\t\telse if (this->maxInput == this->maxDropped && SeqManager<T, N>::IsSeqHigherThan(input, this->maxForwarded))\n\t\t{\n\t\t\tthis->dropped.insert(input);\n\n\t\t\tClearDropped();\n\t\t}\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tbool SeqManager<T, N>::Input(T input, T& output)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto base = this->base;\n\n\t\t// No dropped inputs to consider.\n\t\tif (this->dropped.empty())\n\t\t{\n\t\t\tgoto done;\n\t\t}\n\t\t// Dropped inputs present, cleanup and update base.\n\t\telse\n\t\t{\n\t\t\t// Set 'maxInput' here if needed before calling ClearDropped().\n\t\t\tif (this->started && SeqManager<T, N>::IsSeqHigherThan(input, this->maxInput))\n\t\t\t{\n\t\t\t\tthis->maxInput     = input;\n\t\t\t\tthis->maxForwarded = input;\n\t\t\t}\n\n\t\t\tClearDropped();\n\n\t\t\tbase = this->base;\n\t\t}\n\n\t\t// No dropped inputs to consider after cleanup.\n\t\tif (this->dropped.empty())\n\t\t{\n\t\t\tgoto done;\n\t\t}\n\t\t// This input was dropped.\n\t\telse if (this->dropped.find(input) != this->dropped.end())\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"trying to send a dropped input\");\n\n\t\t\treturn false;\n\t\t}\n\t\t// There are dropped inputs, calculate 'base' for this input.\n\t\telse\n\t\t{\n\t\t\tauto droppedCount = this->dropped.size();\n\n\t\t\t// Get the first dropped input which is higher than or equal 'input'.\n\t\t\tauto it = this->dropped.lower_bound(input);\n\n\t\t\tdroppedCount -= std::distance(it, this->dropped.end());\n\t\t\tbase = (this->base - droppedCount) & SeqManager::MaxValue;\n\t\t}\n\n\tdone:\n\t\toutput = (input + base) & SeqManager::MaxValue;\n\n\t\tif (!this->started)\n\t\t{\n\t\t\tthis->started      = true;\n\t\t\tthis->maxInput     = input;\n\t\t\tthis->maxForwarded = input;\n\t\t\tthis->maxOutput    = output;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// New input is higher than the maximum seen.\n\t\t\tif (SeqManager<T, N>::IsSeqHigherThan(input, this->maxInput))\n\t\t\t{\n\t\t\t\tthis->maxInput     = input;\n\t\t\t\tthis->maxForwarded = input;\n\t\t\t}\n\n\t\t\t// New output is higher than the maximum seen.\n\t\t\tif (SeqManager<T, N>::IsSeqHigherThan(output, this->maxOutput))\n\t\t\t{\n\t\t\t\tthis->maxOutput = output;\n\t\t\t}\n\t\t}\n\n\t\toutput = (output + this->initialOutput) & SeqManager::MaxValue;\n\n\t\treturn true;\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tT SeqManager<T, N>::GetMaxInput() const\n\t{\n\t\treturn this->maxInput;\n\t}\n\n\ttemplate<typename T, uint8_t N>\n\tT SeqManager<T, N>::GetMaxOutput() const\n\t{\n\t\treturn this->maxOutput;\n\t}\n\n\t/*\n\t * Delete droped inputs greater than maxInput, which belong to a previous\n\t * cycle.\n\t */\n\ttemplate<typename T, uint8_t N>\n\tvoid SeqManager<T, N>::ClearDropped()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Cleanup dropped values.\n\t\tif (this->dropped.empty())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst size_t previousDroppedSize = this->dropped.size();\n\n\t\tfor (auto it = this->dropped.begin(); it != this->dropped.end();)\n\t\t{\n\t\t\tauto value = *it;\n\n\t\t\tif (SeqManager<T, N>::IsSeqHigherThan(value, this->maxInput))\n\t\t\t{\n\t\t\t\tit = this->dropped.erase(it);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Adapt base.\n\t\tthis->base = (this->base - (previousDroppedSize - this->dropped.size())) & SeqManager::MaxValue;\n\t}\n\n\t// Explicit instantiation to have all SeqManager definitions in this file.\n\ttemplate class SeqManager<uint8_t>;      // For codecs.\n\ttemplate class SeqManager<uint8_t, 3>;   // For testing.\n\ttemplate class SeqManager<uint16_t>;     // For RTP sequence numbers.\n\ttemplate class SeqManager<uint16_t, 15>; // For PictureID (15 bits).\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/Serializable.cpp",
    "content": "#define MS_CLASS \"RTC::Serializable\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Serializable.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memmove(), std::memset()\n\nnamespace RTC\n{\n\tSerializable::Serializable(const uint8_t* buffer, size_t bufferLength)\n\t  : buffer(const_cast<uint8_t*>(buffer)), bufferLength(bufferLength)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tSerializable::~Serializable()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->bufferReleasedListener)\n\t\t{\n\t\t\t(*this->bufferReleasedListener)(this, this->buffer);\n\t\t}\n\t}\n\n\tvoid Serializable::Serialize(uint8_t* buffer, size_t bufferLength)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (bufferLength < this->length)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"bufferLength (%zu bytes) is lower than current length (%zu bytes)\",\n\t\t\t  bufferLength,\n\t\t\t  this->length);\n\t\t}\n\n\t\tstd::memmove(buffer, this->buffer, this->length);\n\n\t\tif (buffer != this->buffer && this->bufferReleasedListener)\n\t\t{\n\t\t\t(*this->bufferReleasedListener)(this, this->buffer);\n\t\t}\n\n\t\tthis->buffer       = buffer;\n\t\tthis->bufferLength = bufferLength;\n\t}\n\n\tvoid Serializable::SetBufferReleasedListener(Serializable::BufferReleasedListener* listener)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->bufferReleasedListener = listener;\n\t}\n\n\tvoid Serializable::Consolidate() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->consolidatedListener)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"consolidated listener not set\");\n\t\t}\n\n\t\tthis->consolidatedListener();\n\t}\n\n\tvoid Serializable::SetBuffer(uint8_t* buffer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (buffer == this->buffer)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->bufferReleasedListener)\n\t\t{\n\t\t\t(*this->bufferReleasedListener)(this, this->buffer);\n\t\t}\n\n\t\tthis->buffer = buffer;\n\t}\n\n\tvoid Serializable::SetBufferLength(size_t bufferLength)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (bufferLength < this->length)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"buffer length (%zu bytes) is lower than current length (%zu bytes)\",\n\t\t\t  bufferLength,\n\t\t\t  this->length);\n\t\t}\n\n\t\tif (bufferLength == 0)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"bufferLength cannot be 0\");\n\t\t}\n\n\t\tthis->bufferLength = bufferLength;\n\t}\n\n\tvoid Serializable::SetLength(size_t length)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (length > this->bufferLength)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"length (%zu bytes) is larger than internal buffer maximum length (%zu bytes)\",\n\t\t\t  length,\n\t\t\t  this->bufferLength);\n\t\t}\n\n\t\tif (length == 0)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"length cannot be 0\");\n\t\t}\n\n\t\tthis->length = length;\n\t}\n\n\tvoid Serializable::CloneInto(Serializable* serializable) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (serializable->GetBufferLength() < this->length)\n\t\t{\n\t\t\tconst auto bufferLength = serializable->GetBufferLength();\n\n\t\t\tdelete serializable;\n\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"bufferLength (%zu bytes) is lower than current length (%zu bytes)\",\n\t\t\t  bufferLength,\n\t\t\t  this->length);\n\t\t}\n\n\t\tstd::memmove(const_cast<uint8_t*>(serializable->GetBuffer()), this->buffer, this->length);\n\n\t\t// Need to manually set Serializable length.\n\t\tserializable->SetLength(this->length);\n\t}\n\n\tvoid Serializable::FillPadding(uint8_t padding)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (padding > this->length)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"padding (%\" PRIu8 \" bytes) cannot be greater than length (%zu bytes)\", padding, this->length);\n\t\t}\n\n\t\tstd::memset(this->buffer + this->length - padding, 0x00, padding);\n\t}\n\n\tvoid Serializable::SetConsolidatedListener(ConsolidatedListener&& listener)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->consolidatedListener = std::move(listener);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SimpleConsumer.cpp",
    "content": "#include \"FBS/consumer.h\"\n#define MS_CLASS \"RTC::SimpleConsumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#include \"RTC/SimpleConsumer.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <limits> // std::numeric_limits\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t TargetLayerRetransmissionBufferSize{ 15u };\n\n\t/* Instance methods. */\n\n\tSimpleConsumer::SimpleConsumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& producerId,\n\t  RTC::Consumer::Listener* listener,\n\t  const FBS::Transport::ConsumeRequest* data)\n\t  : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMPLE)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure there is a single encoding.\n\t\tif (this->consumableRtpEncodings.size() != 1u)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid consumableRtpEncodings with size != 1\");\n\t\t}\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tthis->keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType);\n\n\t\t// Create RtpStreamSend instance for sending a single stream to the remote.\n\t\tCreateRtpStream();\n\n\t\t// Let's chosee an initial output seq number between 1000 and 32768 to avoid\n\t\t// libsrtp bug:\n\t\t// https://github.com/versatica/mediasoup/issues/1437\n\t\tconst uint16_t initialOutputSeq =\n\t\t  Utils::Crypto::GetRandomUInt<uint16_t>(1000u, std::numeric_limits<uint16_t>::max() / 2);\n\n\t\tthis->rtpSeqManager = RTC::SeqManager<uint16_t>(initialOutputSeq);\n\n\t\t// Create the encoding context for Opus.\n\t\tif (\n\t\t  mediaCodec->mimeType.type == RTC::RtpCodecMimeType::Type::AUDIO &&\n\t\t  (mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::OPUS ||\n\t\t   mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::MULTIOPUS))\n\t\t{\n\t\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\n\t\t\tthis->encodingContext.reset(\n\t\t\t  RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params));\n\n\t\t\t// ignoreDtx is set to false by default.\n\t\t\tthis->encodingContext->SetIgnoreDtx(data->ignoreDtx());\n\t\t}\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tSimpleConsumer::~SimpleConsumer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->rtpStream;\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::DumpResponse> SimpleConsumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Call the parent method.\n\t\tauto base = RTC::Consumer::FillBuffer(builder);\n\t\t// Add rtpStream.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Dump>> rtpStreams;\n\t\trtpStreams.emplace_back(this->rtpStream->FillBuffer(builder));\n\n\t\tauto dump = FBS::Consumer::CreateConsumerDumpDirect(builder, base, &rtpStreams);\n\n\t\treturn FBS::Consumer::CreateDumpResponse(builder, dump);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> SimpleConsumer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Stats>> rtpStreams;\n\n\t\t// Add stats of our send stream.\n\t\trtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder));\n\n\t\t// Add stats of our recv stream.\n\t\tif (this->producerRtpStream)\n\t\t{\n\t\t\trtpStreams.emplace_back(this->producerRtpStream->FillBufferStats(builder));\n\t\t}\n\n\t\treturn FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> SimpleConsumer::FillBufferScore(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->producerRtpStreamScores, \"producerRtpStreamScores not set\");\n\n\t\tuint8_t producerScore{ 0 };\n\n\t\tif (this->producerRtpStream)\n\t\t{\n\t\t\tproducerScore = this->producerRtpStream->GetScore();\n\t\t}\n\n\t\treturn FBS::Consumer::CreateConsumerScoreDirect(\n\t\t  builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores);\n\t}\n\n\tvoid SimpleConsumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME:\n\t\t\t{\n\t\t\t\tif (IsActive())\n\t\t\t\t{\n\t\t\t\t\tRequestKeyFrame();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS:\n\t\t\t{\n\t\t\t\t// Accept with empty preferred layers object.\n\n\t\t\t\tauto responseOffset =\n\t\t\t\t  FBS::Consumer::CreateSetPreferredLayersResponse(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Consumer::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->producerRtpStream = rtpStream;\n\t}\n\n\tvoid SimpleConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->producerRtpStream = rtpStream;\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\t}\n\n\tvoid SimpleConsumer::ProducerRtpStreamScore(\n\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\t}\n\n\tvoid SimpleConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tuint8_t SimpleConsumer::GetBitratePriority() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\t// Audio SimpleConsumer does not play the BWE game.\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->priority;\n\t}\n\n\tuint32_t SimpleConsumer::IncreaseLayer(uint32_t bitrate, bool /*considerLoss*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, \"should be video\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\t// If this is not the first time this method is called within the same iteration,\n\t\t// return 0 since a video SimpleConsumer does not keep state about this.\n\t\tif (this->managingBitrate)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tthis->managingBitrate = true;\n\n\t\t// Video SimpleConsumer does not really play the BWE game when. However, let's\n\t\t// be honest and try to be nice.\n\t\tauto nowMs          = this->shared->GetTimeMs();\n\t\tauto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs);\n\n\t\tif (desiredBitrate < bitrate)\n\t\t{\n\t\t\treturn desiredBitrate;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn bitrate;\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::ApplyLayers()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, \"should be video\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\tthis->managingBitrate = false;\n\n\t\t// SimpleConsumer does not play the BWE game (even if video kind).\n\t}\n\n\tuint32_t SimpleConsumer::GetDesiredBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\t// Audio SimpleConsumer does not play the BWE game.\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tauto nowMs          = this->shared->GetTimeMs();\n\t\tauto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs);\n\n\t\t// If consumer.rtpParameters.encodings[0].maxBitrate was given and it's\n\t\t// greater than computed one, then use it.\n\t\tauto maxBitrate = this->rtpParameters.encodings[0].maxBitrate;\n\n\t\tdesiredBitrate = std::max(maxBitrate, desiredBitrate);\n\n\t\treturn desiredBitrate;\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tvoid SimpleConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.consumerId = this->id;\n#endif\n\n\t\tif (!IsActive())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If we need to sync, support key frames and this is not a key frame, ignore\n\t\t// the packet.\n\t\tif (this->syncRequired && this->keyFrameSupported && !packet->IsKeyFrame())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);\n#endif\n\n\t\t\t// NOTE: No need to drop the packet in the RTP sequence manager since here\n\t\t\t// we are blocking all packets but the key frame that would trigger sync\n\t\t\t// below.\n\n\t\t\t// Store the packet for the scenario in which this packet is part of the\n\t\t\t// key frame and it arrived before the first packet of the key frame.\n\t\t\tStorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto payloadType = packet->GetPayloadType();\n\n\t\t// NOTE: This may happen if this Consumer supports just some codecs of those\n\t\t// in the corresponding Producer.\n\t\tif (!this->supportedCodecPayloadTypes[payloadType])\n\t\t{\n\t\t\tMS_WARN_DEV(\"payload type not supported [payloadType:%\" PRIu8 \"]\", payloadType);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Packets with only padding are not forwarded.\n\t\tif (packet->GetPayloadLength() == 0)\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\tbool marker;\n\n\t\t// Process the payload if needed. Drop packet if necessary.\n\t\tif (this->encodingContext && !packet->ProcessPayload(this->encodingContext.get(), marker))\n\t\t{\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"discarding packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp());\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Whether this is the first packet after re-sync.\n\t\tconst bool isSyncPacket = this->syncRequired;\n\n\t\t// Whether packets stored in the target layer retransmission buffer must be\n\t\t// sent once this packet is sent.\n\t\tbool sendPacketsInTargetLayerRetransmissionBuffer{ false };\n\n\t\t// Sync sequence number and timestamp if required.\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tif (packet->IsKeyFrame())\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"sync key frame received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\tsendPacketsInTargetLayerRetransmissionBuffer = true;\n\t\t\t}\n\n\t\t\tthis->rtpSeqManager.Sync(packet->GetSequenceNumber() - 1);\n\n\t\t\tthis->syncRequired = false;\n\t\t}\n\n\t\t// Update RTP seq number and timestamp.\n\t\tuint16_t seq;\n\n\t\tthis->rtpSeqManager.Input(packet->GetSequenceNumber(), seq);\n\n\t\t// Save original packet fields.\n\t\tauto origSsrc = packet->GetSsrc();\n\t\tauto origSeq  = packet->GetSequenceNumber();\n\n\t\t// Rewrite packet.\n\t\tpacket->SetSsrc(this->rtpParameters.encodings[0].ssrc);\n\t\tpacket->SetSequenceNumber(seq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.sendRtpTimestamp = packet->GetTimestamp();\n\t\tpacket->logger.sendSeqNumber    = seq;\n#endif\n\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp,\n\t\t\t  \"sending sync packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSeq);\n\t\t}\n\n\t\tconst RTC::RTP::RtpStreamSend::ReceivePacketResult result =\n\t\t  this->rtpStream->ReceivePacket(packet, sharedPacket);\n\n\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t{\n\t\t\t// Send the packet.\n\t\t\tthis->listener->OnConsumerSendRtpPacket(this, packet);\n\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventRtpAndKeyFrameTypes(packet);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  rtp,\n\t\t\t  \"failed to send packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSeq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED);\n#endif\n\t\t}\n\n\t\t// Restore packet fields.\n\t\tpacket->SetSsrc(origSsrc);\n\t\tpacket->SetSequenceNumber(origSeq);\n\n\t\t// If sharedPacket doesn't have a packet inside and it has been stored we\n\t\t// need to clone the packet into it.\n\t\tif (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED)\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\n\t\t// If sent packet was the first packet of a key frame, let's send buffered\n\t\t// packets belonging to the same key frame that arrived earlier due to\n\t\t// packet misorder.\n\t\tif (sendPacketsInTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\t// NOTE: Only send buffered packets if the first packet containing the key\n\t\t\t// frame was sent.\n\t\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t\t{\n\t\t\t\tfor (auto& kv : this->targetLayerRetransmissionBuffer)\n\t\t\t\t{\n\t\t\t\t\tauto& bufferedSharedPacket = kv.second;\n\t\t\t\t\tauto* bufferedPacket       = bufferedSharedPacket.GetPacket();\n\n\t\t\t\t\tif (bufferedPacket->GetSequenceNumber() > origSeq)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"sending packet buffered in the target layer retransmission buffer [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t\t\t\t  \"] after sending first packet of the key frame [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  bufferedPacket->GetSsrc(),\n\t\t\t\t\t\t  bufferedPacket->GetSequenceNumber(),\n\t\t\t\t\t\t  bufferedPacket->GetTimestamp(),\n\t\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\t\t\tSendRtpPacket(bufferedPacket, bufferedSharedPacket);\n\n\t\t\t\t\t\t// Be sure that the target layer retransmission buffer has not been\n\t\t\t\t\t\t// emptied as a result of sending this packet. If so, exit the loop.\n\t\t\t\t\t\tif (this->targetLayerRetransmissionBuffer.empty())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"target layer retransmission buffer emptied while iterating it, exiting the loop\");\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tbool SimpleConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (static_cast<float>((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tauto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs);\n\n\t\tif (!senderReport)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Build SDES chunk for this sender.\n\t\tauto* sdesChunk = this->rtpStream->GetRtcpSdesChunk();\n\n\t\tauto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs);\n\n\t\t// RTCP Compound packet buffer cannot hold the data.\n\t\tif (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->lastRtcpSentTime = nowMs;\n\n\t\treturn true;\n\t}\n\n\tvoid SimpleConsumer::NeedWorstRemoteFractionLost(\n\t  uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto fractionLost = this->rtpStream->GetFractionLost();\n\n\t\t// If our fraction lost is worse than the given one, update it.\n\t\tworstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost);\n\t}\n\n\tvoid SimpleConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventNackType();\n\n\t\tthis->rtpStream->ReceiveNack(nackPacket);\n\t}\n\n\tvoid SimpleConsumer::ReceiveKeyFrameRequest(\n\t  RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (messageType)\n\t\t{\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t{\n\t\t\t\tEmitTraceEventPliType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t{\n\t\t\t\tEmitTraceEventFirType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tthis->rtpStream->ReceiveKeyFrameRequest(messageType);\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpReceiverReport(report);\n\t}\n\n\tvoid SimpleConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report);\n\t}\n\n\tuint32_t SimpleConsumer::GetTransmissionRate(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->rtpStream->GetBitrate(nowMs);\n\t}\n\n\tfloat SimpleConsumer::GetRtt() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->rtpStream->GetRtt();\n\t}\n\n\tvoid SimpleConsumer::UserOnTransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired = true;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::UserOnTransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t}\n\n\tvoid SimpleConsumer::UserOnPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\n\t\tif (this->externallyManagedBitrate && this->kind == RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\tthis->listener->OnConsumerNeedZeroBitrate(this);\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::UserOnResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired = true;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::CreateRtpStream()\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tMS_DEBUG_TAG(\n\t\t  rtp, \"[ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \"]\", encoding.ssrc, mediaCodec->payloadType);\n\n\t\t// Set stream params.\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc        = encoding.ssrc;\n\t\tparams.payloadType = mediaCodec->payloadType;\n\t\tparams.mimeType    = mediaCodec->mimeType;\n\t\tparams.clockRate   = mediaCodec->clockRate;\n\t\tparams.cname       = this->rtpParameters.rtcp.cname;\n\n\t\t// Check in band FEC in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"useinbandfec\") && mediaCodec->parameters.GetInteger(\"useinbandfec\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"in band FEC enabled\");\n\n\t\t\tparams.useInBandFec = true;\n\t\t}\n\n\t\t// Check DTX in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"usedtx\") && mediaCodec->parameters.GetInteger(\"usedtx\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\t// Check DTX in the encoding.\n\t\tif (encoding.dtx)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\tfor (const auto& fb : mediaCodec->rtcpFeedback)\n\t\t{\n\t\t\tif (!params.useNack && fb.type == \"nack\" && fb.parameter.empty())\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"NACK supported\");\n\n\t\t\t\tparams.useNack = true;\n\t\t\t}\n\t\t\telse if (!params.usePli && fb.type == \"nack\" && fb.parameter == \"pli\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"PLI supported\");\n\n\t\t\t\tparams.usePli = true;\n\t\t\t}\n\t\t\telse if (!params.useFir && fb.type == \"ccm\" && fb.parameter == \"fir\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"FIR supported\");\n\n\t\t\t\tparams.useFir = true;\n\t\t\t}\n\t\t}\n\n\t\tthis->rtpStream =\n\t\t  new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid);\n\t\tthis->rtpStreams.push_back(this->rtpStream);\n\n\t\t// If the Consumer is paused, tell the RtpStreamSend.\n\t\tif (IsPaused() || IsProducerPaused())\n\t\t{\n\t\t\tthis->rtpStream->Pause();\n\t\t}\n\n\t\tconst auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\n\t\tif (rtxCodec && encoding.hasRtx)\n\t\t{\n\t\t\tthis->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc);\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::RequestKeyFrame()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto mappedSsrc = this->consumableRtpEncodings[0].ssrc;\n\n\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t}\n\n\tvoid SimpleConsumer::StorePacketInTargetLayerRetransmissionBuffer(\n\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"storing packet in target layer retransmission buffer [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t  packet->GetSsrc(),\n\t\t  packet->GetSequenceNumber(),\n\t\t  packet->GetTimestamp());\n\n\t\t// Store original packet into the buffer. Only clone once and only if\n\t\t// necessary.\n\t\tif (!sharedPacket.HasPacket())\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\t\t// Assert that, if sharedPacket was already filled, both packet and\n\t\t// sharedPacket are the very same RTP packet.\n\t\telse\n\t\t{\n\t\t\tsharedPacket.AssertSamePacket(packet);\n\t\t}\n\n\t\tthis->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket;\n\n\t\tif (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize)\n\t\t{\n\t\t\tthis->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin());\n\t\t}\n\t}\n\n\tvoid SimpleConsumer::EmitScore() const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\n\t\tauto notificationOffset = FBS::Consumer::CreateScoreNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_SCORE,\n\t\t  FBS::Notification::Body::Consumer_ScoreNotification,\n\t\t  notificationOffset);\n\t}\n\n\tvoid SimpleConsumer::OnRtpStreamScore(\n\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\t}\n\n\tvoid SimpleConsumer::OnRtpStreamRetransmitRtpPacket(\n\t  RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnConsumerRetransmitRtpPacket(this, packet);\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SimulcastConsumer.cpp",
    "content": "#define MS_CLASS \"RTC::SimulcastConsumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SimulcastConsumer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <limits> // std::numeric_limits\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint64_t StreamMinActiveMs{ 2000u };\n\tstatic constexpr uint64_t BweDowngradeConservativeMs{ 10000u };\n\tstatic constexpr uint64_t BweDowngradeMinActiveMs{ 8000u };\n\tstatic constexpr uint16_t MaxSequenceNumberGap{ 100u };\n\tstatic constexpr size_t TargetLayerRetransmissionBufferSize{ 30u };\n\n\t/* Instance methods. */\n\n\tSimulcastConsumer::SimulcastConsumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& producerId,\n\t  RTC::Consumer::Listener* listener,\n\t  const FBS::Transport::ConsumeRequest* data)\n\t  : RTC::Consumer::Consumer(\n\t      shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMULCAST)\n\t{\n\t\tMS_TRACE();\n\n\t\t// We allow a single encoding in simulcast (so we can enable temporal layers\n\t\t// with a single simulcast stream).\n\t\t// NOTE: No need to check this->consumableRtpEncodings.size() > 0 here since\n\t\t// it's already done in Consumer constructor.\n\n\t\tauto& encoding = this->rtpParameters.encodings[0];\n\n\t\t// Ensure there are as many spatial layers as encodings.\n\t\tif (encoding.spatialLayers != this->consumableRtpEncodings.size())\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"encoding.spatialLayers does not match number of consumableRtpEncodings\");\n\t\t}\n\n\t\t// Fill mapMappedSsrcSpatialLayer.\n\t\tfor (size_t idx{ 0u }; idx < this->consumableRtpEncodings.size(); ++idx)\n\t\t{\n\t\t\tauto& encoding = this->consumableRtpEncodings[idx];\n\n\t\t\tthis->mapMappedSsrcSpatialLayer[encoding.ssrc] = static_cast<int16_t>(idx);\n\t\t}\n\n\t\t// Set preferredLayers (if given).\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS))\n\t\t{\n\t\t\tconst auto* preferredLayers = data->preferredLayers();\n\n\t\t\tthis->preferredLayers.spatial = preferredLayers->spatialLayer();\n\n\t\t\tif (this->preferredLayers.spatial > encoding.spatialLayers - 1)\n\t\t\t{\n\t\t\t\tthis->preferredLayers.spatial = static_cast<int16_t>(encoding.spatialLayers - 1);\n\t\t\t}\n\n\t\t\tif (auto preferredTemporalLayer = preferredLayers->temporalLayer(); preferredTemporalLayer.has_value())\n\t\t\t{\n\t\t\t\tthis->preferredLayers.temporal = preferredTemporalLayer.value();\n\n\t\t\t\tif (this->preferredLayers.temporal > encoding.temporalLayers - 1)\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Initially set preferredSpatialLayer and preferredTemporalLayer to the\n\t\t\t// maximum value.\n\t\t\tthis->preferredLayers.spatial  = static_cast<int16_t>(encoding.spatialLayers - 1);\n\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t}\n\n\t\t// Reserve space for the Producer RTP streams by filling all the possible\n\t\t// entries with nullptr.\n\t\tthis->producerRtpStreams.insert(\n\t\t  this->producerRtpStreams.begin(), this->consumableRtpEncodings.size(), nullptr);\n\n\t\t// Create the encoding context.\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tif (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType))\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t  \"%s codec not supported for simulcast\", mediaCodec->mimeType.ToString().c_str());\n\t\t}\n\n\t\t// Let's chosee an initial output seq number between 1000 and 32768 to avoid\n\t\t// libsrtp bug:\n\t\t// https://github.com/versatica/mediasoup/issues/1437\n\t\tconst uint16_t initialOutputSeq =\n\t\t  Utils::Crypto::GetRandomUInt<uint16_t>(1000u, std::numeric_limits<uint16_t>::max() / 2);\n\n\t\tthis->rtpSeqManager = RTC::SeqManager<uint16_t>(initialOutputSeq);\n\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\n\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\tparams.temporalLayers = encoding.temporalLayers;\n\n\t\tthis->encodingContext.reset(\n\t\t  RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params));\n\n\t\tMS_ASSERT(this->encodingContext, \"no encoding context for this codec\");\n\n\t\t// Create RtpStreamSend instance for sending a single stream to the remote.\n\t\tCreateRtpStream();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelRequestHandler*/ nullptr);\n\t}\n\n\tSimulcastConsumer::~SimulcastConsumer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->rtpStream;\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::DumpResponse> SimulcastConsumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Call the parent method.\n\t\tauto base = RTC::Consumer::FillBuffer(builder);\n\t\t// Add rtpStream.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Dump>> rtpStreams;\n\t\trtpStreams.emplace_back(this->rtpStream->FillBuffer(builder));\n\n\t\tauto dump = FBS::Consumer::CreateConsumerDumpDirect(\n\t\t  builder,\n\t\t  base,\n\t\t  &rtpStreams,\n\t\t  this->preferredLayers.spatial,\n\t\t  this->targetLayers.spatial,\n\t\t  this->currentSpatialLayer,\n\t\t  this->preferredLayers.temporal,\n\t\t  this->targetLayers.temporal,\n\t\t  this->encodingContext->GetCurrentTemporalLayer());\n\n\t\treturn FBS::Consumer::CreateDumpResponse(builder, dump);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> SimulcastConsumer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Stats>> rtpStreams;\n\n\t\t// Add stats of our send stream.\n\t\trtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder));\n\n\t\tauto* producerCurrentRtpStream = GetProducerCurrentRtpStream();\n\n\t\t// Add stats of our recv stream.\n\t\tif (producerCurrentRtpStream)\n\t\t{\n\t\t\trtpStreams.emplace_back(producerCurrentRtpStream->FillBufferStats(builder));\n\t\t}\n\n\t\treturn FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> SimulcastConsumer::FillBufferScore(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->producerRtpStreamScores, \"producerRtpStreamScores not set\");\n\n\t\tauto* producerCurrentRtpStream = GetProducerCurrentRtpStream();\n\n\t\tuint8_t producerScore{ 0 };\n\n\t\tif (producerCurrentRtpStream)\n\t\t{\n\t\t\tproducerScore = producerCurrentRtpStream->GetScore();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tproducerScore = 0;\n\t\t}\n\n\t\treturn FBS::Consumer::CreateConsumerScoreDirect(\n\t\t  builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores);\n\t}\n\n\tvoid SimulcastConsumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME:\n\t\t\t{\n\t\t\t\tif (IsActive())\n\t\t\t\t{\n\t\t\t\t\tRequestKeyFrames();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS:\n\t\t\t{\n\t\t\t\tauto previousPreferredLayers = this->preferredLayers;\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Consumer::SetPreferredLayersRequest>();\n\t\t\t\tconst auto* preferredLayers = body->preferredLayers();\n\n\t\t\t\t// Spatial layer.\n\t\t\t\tthis->preferredLayers.spatial = preferredLayers->spatialLayer();\n\n\t\t\t\tif (this->preferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1)\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.spatial =\n\t\t\t\t\t  static_cast<int16_t>(this->rtpStream->GetSpatialLayers() - 1);\n\t\t\t\t}\n\n\t\t\t\t// preferredTemporaLayer is optional.\n\t\t\t\tauto preferredTemporalLayer = preferredLayers->temporalLayer();\n\n\t\t\t\tif (preferredTemporalLayer.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal = preferredTemporalLayer.value();\n\n\t\t\t\t\tif (this->preferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->preferredLayers.temporal =\n\t\t\t\t\t\t  static_cast<int16_t>(this->rtpStream->GetTemporalLayers() - 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal =\n\t\t\t\t\t  static_cast<int16_t>(this->rtpStream->GetTemporalLayers() - 1);\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"preferred layers changed [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t\t\t  this->preferredLayers.spatial,\n\t\t\t\t  this->preferredLayers.temporal,\n\t\t\t\t  this->id.c_str());\n\n\t\t\t\tpreferredTemporalLayer     = this->preferredLayers.temporal;\n\t\t\t\tauto preferredLayersOffset = FBS::Consumer::CreateConsumerLayers(\n\t\t\t\t  request->GetBufferBuilder(), this->preferredLayers.spatial, preferredTemporalLayer);\n\t\t\t\tauto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse(\n\t\t\t\t  request->GetBufferBuilder(), preferredLayersOffset);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset);\n\n\t\t\t\tif (IsActive() && this->preferredLayers != previousPreferredLayers)\n\t\t\t\t{\n\t\t\t\t\tMayChangeLayers(/*force*/ true);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Consumer::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc);\n\n\t\tMS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), \"unknown mappedSsrc\");\n\n\t\tconst int16_t spatialLayer = it->second;\n\n\t\tthis->producerRtpStreams[spatialLayer] = rtpStream;\n\t}\n\n\tvoid SimulcastConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc);\n\n\t\tMS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), \"unknown mappedSsrc\");\n\n\t\tconst int16_t spatialLayer = it->second;\n\n\t\tthis->producerRtpStreams[spatialLayer] = rtpStream;\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::ProducerRtpStreamScore(\n\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\n\t\tif (RTC::Consumer::IsActive())\n\t\t{\n\t\t\t// All Producer streams are dead.\n\t\t\tif (!IsActive())\n\t\t\t{\n\t\t\t\tUpdateTargetLayers(-1, -1);\n\t\t\t}\n\t\t\t// Just check target layers if the stream has died or reborned.\n\t\t\telse if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u))\n\t\t\t{\n\t\t\t\tMayChangeLayers();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Just interested if this is the first Sender Report for a RTP stream.\n\t\tif (!first)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tMS_DEBUG_TAG(simulcast, \"first SenderReport [ssrc:%\" PRIu32 \"]\", rtpStream->GetSsrc());\n\n\t\t// If our RTP timestamp reference stream does not yet have SR, do nothing\n\t\t// since we know we won't be able to switch.\n\t\tauto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();\n\n\t\tif (!producerTsReferenceRtpStream || !producerTsReferenceRtpStream->GetSenderReportNtpMs())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tuint8_t SimulcastConsumer::GetBitratePriority() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->priority;\n\t}\n\n\tuint32_t SimulcastConsumer::IncreaseLayer(uint32_t bitrate, bool considerLoss)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\t// If already in the preferred layers, do nothing.\n\t\tif (this->provisionalTargetLayers == this->preferredLayers)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tuint32_t virtualBitrate;\n\n\t\tif (considerLoss)\n\t\t{\n\t\t\t// Calculate virtual available bitrate based on given bitrate and our\n\t\t\t// packet lost.\n\t\t\tauto lossPercentage = this->rtpStream->GetLossPercentage();\n\n\t\t\tif (lossPercentage < 2)\n\t\t\t{\n\t\t\t\tvirtualBitrate = 1.08 * bitrate;\n\t\t\t}\n\t\t\telse if (lossPercentage > 10)\n\t\t\t{\n\t\t\t\tvirtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvirtualBitrate = bitrate;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvirtualBitrate = bitrate;\n\t\t}\n\n\t\tuint32_t requiredBitrate{ 0u };\n\t\tint16_t spatialLayer{ 0 };\n\t\tint16_t temporalLayer{ 0 };\n\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\tfor (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx)\n\t\t{\n\t\t\tspatialLayer = static_cast<int16_t>(sIdx);\n\n\t\t\t// If this is higher than current spatial layer and we moved to to current\n\t\t\t// spatial layer due to BWE limitations, check how much it has elapsed\n\t\t\t// since then.\n\t\t\tif (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs)\n\t\t\t{\n\t\t\t\tif (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"avoid upgrading to spatial layer %\" PRIi16 \" due to recent BWE downgrade\", spatialLayer);\n\n\t\t\t\t\tgoto done;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ignore spatial layers lower than the one we already have.\n\t\t\tif (spatialLayer < this->provisionalTargetLayers.spatial)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// If this is the higher than preferred spatial layer, abort.\n\t\t\telse if (spatialLayer > this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"avoid upgrading to spatial layer %\" PRIi16\n\t\t\t\t  \" since it's higher than preferred spatial layer %\" PRIi16,\n\t\t\t\t  spatialLayer,\n\t\t\t\t  this->preferredLayers.spatial);\n\n\t\t\t\tgoto done;\n\t\t\t}\n\n\t\t\t// This can be null.\n\t\t\tauto* producerRtpStream = this->producerRtpStreams.at(spatialLayer);\n\n\t\t\t// Producer stream does not exist. Ignore.\n\t\t\tif (!producerRtpStream)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Ignore spatial layers (streams) with score 0.\n\t\t\tif (producerRtpStream->GetScore() == 0)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If the stream has not been active time enough and we have an active one\n\t\t\t// already, move to the next spatial layer.\n\t\t\tif (\n\t\t\t  spatialLayer != this->provisionalTargetLayers.spatial &&\n\t\t\t  this->provisionalTargetLayers.spatial != -1 &&\n\t\t\t  producerRtpStream->GetActiveMs() < StreamMinActiveMs)\n\t\t\t{\n\t\t\t\tconst auto* provisionalProducerRtpStream =\n\t\t\t\t  this->producerRtpStreams.at(this->provisionalTargetLayers.spatial);\n\n\t\t\t\t// The stream for the current provisional spatial layer has been active\n\t\t\t\t// for enough time, move to the next spatial layer.\n\t\t\t\tif (provisionalProducerRtpStream->GetActiveMs() >= StreamMinActiveMs)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// We may not yet switch to this spatial layer.\n\t\t\tif (!CanSwitchToSpatialLayer(spatialLayer))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttemporalLayer = 0;\n\n\t\t\t// Check bitrate of every temporal layer.\n\t\t\tfor (; temporalLayer < producerRtpStream->GetTemporalLayers(); ++temporalLayer)\n\t\t\t{\n\t\t\t\t// Ignore temporal layers lower than the one we already have (taking\n\t\t\t\t// into account the spatial layer too).\n\t\t\t\tif (\n\t\t\t\t  spatialLayer == this->provisionalTargetLayers.spatial &&\n\t\t\t\t  temporalLayer <= this->provisionalTargetLayers.temporal)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\trequiredBitrate = producerRtpStream->GetLayerBitrate(nowMs, 0, temporalLayer);\n\n\t\t\t\t// This is simulcast so we must substract the bitrate of the current\n\t\t\t\t// temporal spatial layer if this is the temporal layer 0 of a higher\n\t\t\t\t// spatial layer.\n\t\t\t\tif (\n\t\t\t\t  requiredBitrate && temporalLayer == 0 && this->provisionalTargetLayers.spatial > -1 &&\n\t\t\t\t  spatialLayer > this->provisionalTargetLayers.spatial)\n\t\t\t\t{\n\t\t\t\t\tauto* provisionalProducerRtpStream =\n\t\t\t\t\t  this->producerRtpStreams.at(this->provisionalTargetLayers.spatial);\n\t\t\t\t\tauto provisionalRequiredBitrate = provisionalProducerRtpStream->GetBitrate(\n\t\t\t\t\t  nowMs, 0, this->provisionalTargetLayers.temporal);\n\n\t\t\t\t\tif (requiredBitrate > provisionalRequiredBitrate)\n\t\t\t\t\t{\n\t\t\t\t\t\trequiredBitrate -= provisionalRequiredBitrate;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\trequiredBitrate = 1u; // Don't set 0 since it would be ignored.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"testing layers %\" PRIi16 \":%\" PRIi16 \" [virtual bitrate:%\" PRIu32\n\t\t\t\t  \", required bitrate:%\" PRIu32 \"]\",\n\t\t\t\t  spatialLayer,\n\t\t\t\t  temporalLayer,\n\t\t\t\t  virtualBitrate,\n\t\t\t\t  requiredBitrate);\n\n\t\t\t\t// If active layer, end iterations here. Otherwise move to next spatial\n\t\t\t\t// layer.\n\t\t\t\tif (requiredBitrate)\n\t\t\t\t{\n\t\t\t\t\tgoto done;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If this is the preferred spatial layer or higher, take it and exit.\n\t\t\tif (spatialLayer >= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\tdone:\n\n\t\t// No higher active layers found.\n\t\tif (!requiredBitrate)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\t// No luck.\n\t\tif (requiredBitrate > virtualBitrate)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\t// Set provisional layers.\n\t\tthis->provisionalTargetLayers.spatial  = spatialLayer;\n\t\tthis->provisionalTargetLayers.temporal = temporalLayer;\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"setting provisional layers to %\" PRIi16 \":%\" PRIi16 \" [virtual bitrate:%\" PRIu32\n\t\t  \", required bitrate:%\" PRIu32 \"]\",\n\t\t  this->provisionalTargetLayers.spatial,\n\t\t  this->provisionalTargetLayers.temporal,\n\t\t  virtualBitrate,\n\t\t  requiredBitrate);\n\n\t\tif (requiredBitrate <= bitrate)\n\t\t{\n\t\t\treturn requiredBitrate;\n\t\t}\n\t\telse if (requiredBitrate <= virtualBitrate)\n\t\t{\n\t\t\treturn bitrate;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn requiredBitrate; // NOTE: This cannot happen.\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::ApplyLayers()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\tauto provisionalTargetLayers = this->provisionalTargetLayers;\n\n\t\t// Reset provisional target layers.\n\t\tthis->provisionalTargetLayers.Reset();\n\n\t\tif (provisionalTargetLayers != this->targetLayers)\n\t\t{\n\t\t\tUpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal);\n\n\t\t\t// If this looks like a spatial layer downgrade due to BWE limitations, set\n\t\t\t// member.\n\t\t\tif (\n\t\t\t  this->rtpStream->GetActiveMs() > BweDowngradeMinActiveMs &&\n\t\t\t  this->targetLayers.spatial < this->currentSpatialLayer &&\n\t\t\t  this->currentSpatialLayer <= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"possible target spatial layer downgrade (from %\" PRIi16 \" to %\" PRIi16\n\t\t\t\t  \") due to BWE limitation\",\n\t\t\t\t  this->currentSpatialLayer,\n\t\t\t\t  this->targetLayers.spatial);\n\n\t\t\t\tthis->lastBweDowngradeAtMs = this->shared->GetTimeMs();\n\t\t\t}\n\t\t}\n\t}\n\n\tuint32_t SimulcastConsumer::GetDesiredBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\t\tuint32_t desiredBitrate{ 0u };\n\n\t\t// Let's iterate all streams of the Producer (from highest to lowest) and\n\t\t// obtain their bitrate. Choose the highest one.\n\t\t// NOTE: When the Producer enables a higher stream, initially the bitrate of\n\t\t// it could be less than the bitrate of a lower stream. That's why we\n\t\t// iterate all streams here anyway.\n\t\tfor (auto sIdx{ static_cast<int16_t>(this->producerRtpStreams.size() - 1) }; sIdx >= 0; --sIdx)\n\t\t{\n\t\t\tauto* producerRtpStream = this->producerRtpStreams.at(sIdx);\n\n\t\t\tif (!producerRtpStream)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tauto streamBitrate = producerRtpStream->GetBitrate(nowMs);\n\n\t\t\tdesiredBitrate = std::max(streamBitrate, desiredBitrate);\n\t\t}\n\n\t\t// If consumer.rtpParameters.encodings[0].maxBitrate was given and it's\n\t\t// greater than computed one, then use it.\n\t\tauto maxBitrate = this->rtpParameters.encodings[0].maxBitrate;\n\n\t\tdesiredBitrate = std::max(maxBitrate, desiredBitrate);\n\n\t\treturn desiredBitrate;\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tvoid SimulcastConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.consumerId = this->id;\n#endif\n\n\t\tauto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc());\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\t// Only drop the packet in the RTP sequence manager if it belongs to the\n\t\t\t// current spatial layer.\n\t\t\tif (spatialLayer == this->currentSpatialLayer)\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->targetLayers.temporal == -1)\n\t\t{\n\t\t\t// Only drop the packet in the RTP sequence manager if it belongs to the\n\t\t\t// current spatial layer.\n\t\t\tif (spatialLayer == this->currentSpatialLayer)\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto payloadType = packet->GetPayloadType();\n\n\t\t// NOTE: This may happen if this Consumer supports just some codecs of those\n\t\t// in the corresponding Producer.\n\t\tif (!this->supportedCodecPayloadTypes[payloadType])\n\t\t{\n\t\t\t// Only drop the packet in the RTP sequence manager if it belongs to the\n\t\t\t// current spatial layer.\n\t\t\tif (spatialLayer == this->currentSpatialLayer)\n\t\t\t{\n\t\t\t\tMS_WARN_DEV(\"payload type not supported [payloadType:%\" PRIu8 \"]\", payloadType);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tbool shouldSwitchCurrentSpatialLayer{ false };\n\n\t\t// Check whether this is the packet we are waiting for in order to update\n\t\t// the current spatial layer.\n\t\tif (\n\t\t  this->currentSpatialLayer != this->targetLayers.spatial &&\n\t\t  spatialLayer == this->targetLayers.spatial)\n\t\t{\n\t\t\t// Ignore if not a key frame.\n\t\t\tif (!packet->IsKeyFrame())\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);\n#endif\n\n\t\t\t\t// NOTE: Don't drop the packet in the RTP sequence manager since this\n\t\t\t\t// packet doesn't belong to the current spatial layer.\n\n\t\t\t\t// Store the packet for the scenario in which this packet is part of the\n\t\t\t\t// key frame and it arrived before the first packet of the key frame.\n\t\t\t\tStorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tshouldSwitchCurrentSpatialLayer = true;\n\n\t\t\t// Need to resync the stream.\n\t\t\tthis->syncRequired       = true;\n\t\t\tthis->spatialLayerToSync = spatialLayer;\n\t\t}\n\t\t// If the packet belongs to different spatial layer than the one being sent,\n\t\t// drop it.\n\t\telse if (spatialLayer != this->currentSpatialLayer)\n\t\t{\n\t\t\t// NOTE: Don't drop the packet in the RTP sequence manager since this\n\t\t\t// packet doesn't belong to the current spatial layer.\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If we need to sync and this is not a key frame, ignore the packet.\n\t\t// NOTE: syncRequired is true if packet is a key frame of the target spatial\n\t\t// layer or if transport just connected or consumer resumed.\n\t\tif (this->syncRequired && !packet->IsKeyFrame())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);\n#endif\n\n\t\t\t// NOTE: No need to drop the packet in the RTP sequence manager since here\n\t\t\t// we are blocking all packets but the key frame that would trigger sync\n\t\t\t// below.\n\n\t\t\t// Store the packet for the scenario in which this packet is part of the\n\t\t\t// key frame and it arrived before the first packet of the key frame.\n\t\t\tStorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket);\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Packets with only padding are not forwarded.\n\t\tif (packet->GetPayloadLength() == 0)\n\t\t{\n\t\t\t// Only drop the packet in the RTP sequence manager if it belongs to the\n\t\t\t// current spatial layer.\n\t\t\tif (spatialLayer == this->currentSpatialLayer)\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Whether this is the first packet after re-sync.\n\t\tconst bool isSyncPacket = this->syncRequired;\n\n\t\t// Whether packets stored in the target layer retransmission buffer must be\n\t\t// sent once this packet is sent.\n\t\tbool sendPacketsInTargetLayerRetransmissionBuffer{ false };\n\n\t\t// Sync sequence number and timestamp if required.\n\t\tif (isSyncPacket && (this->spatialLayerToSync == -1 || spatialLayer == this->spatialLayerToSync))\n\t\t{\n\t\t\tif (packet->IsKeyFrame())\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"sync key frame received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\tsendPacketsInTargetLayerRetransmissionBuffer = true;\n\t\t\t}\n\n\t\t\tuint32_t tsOffset{ 0u };\n\n\t\t\t// Sync our RTP stream's RTP timestamp.\n\t\t\tif (spatialLayer == this->tsReferenceSpatialLayer)\n\t\t\t{\n\t\t\t\ttsOffset = 0u;\n\t\t\t}\n\t\t\t// If this is not the RTP stream we use as TS reference, do NTP based RTP\n\t\t\t// TS synchronization.\n\t\t\telse\n\t\t\t{\n\t\t\t\tauto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();\n\t\t\t\tauto* producerTargetRtpStream      = GetProducerTargetRtpStream();\n\n\t\t\t\t// NOTE: If we are here is because we have Sender Reports for both the\n\t\t\t\t// TS reference stream and the target one.\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  producerTsReferenceRtpStream->GetSenderReportNtpMs(),\n\t\t\t\t  \"no Sender Report for TS reference RTP stream\");\n\t\t\t\tMS_ASSERT(\n\t\t\t\t  producerTargetRtpStream->GetSenderReportNtpMs(), \"no Sender Report for current RTP stream\");\n\n\t\t\t\t// Calculate NTP and TS stuff.\n\t\t\t\tauto ntpMs1 = producerTsReferenceRtpStream->GetSenderReportNtpMs();\n\t\t\t\tauto ts1    = producerTsReferenceRtpStream->GetSenderReportTs();\n\t\t\t\tauto ntpMs2 = producerTargetRtpStream->GetSenderReportNtpMs();\n\t\t\t\tauto ts2    = producerTargetRtpStream->GetSenderReportTs();\n\t\t\t\tint64_t diffMs;\n\n\t\t\t\tif (ntpMs2 >= ntpMs1)\n\t\t\t\t{\n\t\t\t\t\tdiffMs = ntpMs2 - ntpMs1;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tdiffMs = -1 * (ntpMs1 - ntpMs2);\n\t\t\t\t}\n\n\t\t\t\tconst int64_t diffTs  = diffMs * this->rtpStream->GetClockRate() / 1000;\n\t\t\t\tconst uint32_t newTs2 = ts2 - diffTs;\n\n\t\t\t\t// Apply offset. This is the difference that later must be removed from the\n\t\t\t\t// sending RTP packet.\n\t\t\t\ttsOffset = newTs2 - ts1;\n\t\t\t}\n\n\t\t\t// When switching to a new stream it may happen that the timestamp of this\n\t\t\t// key frame is lower than the highest timestamp sent to the remote endpoint.\n\t\t\t// If so, apply an extra offset to \"fix\" it for the whole live of this\n\t\t\t// selected Producer stream.\n\t\t\tif (shouldSwitchCurrentSpatialLayer && (packet->GetTimestamp() - tsOffset <= this->rtpStream->GetMaxPacketTs()))\n\t\t\t{\n\t\t\t\t// Max delay in ms we allow for the stream when switching.\n\t\t\t\t// https://en.wikipedia.org/wiki/Audio-to-video_synchronization#Recommendations\n\t\t\t\tstatic const uint32_t MaxExtraOffsetMs{ 75u };\n\n\t\t\t\t// Outgoing packet matches the highest timestamp seen in the previous\n\t\t\t\t// stream. Apply an expected offset for a new frame in a 30fps stream.\n\t\t\t\tstatic const uint8_t MsOffset{ 33u }; // (1 / 30 * 1000).\n\n\t\t\t\tconst int64_t maxTsExtraOffset = MaxExtraOffsetMs * this->rtpStream->GetClockRate() / 1000;\n\t\t\t\tuint32_t tsExtraOffset = this->rtpStream->GetMaxPacketTs() - packet->GetTimestamp() +\n\t\t\t\t                         tsOffset + (MsOffset * this->rtpStream->GetClockRate() / 1000);\n\n\t\t\t\t// NOTE: Don't ask for a key frame if already done.\n\t\t\t\tif (this->keyFrameForTsOffsetRequested)\n\t\t\t\t{\n\t\t\t\t\t// Give up and use the theoretical offset.\n\t\t\t\t\tif (tsExtraOffset > maxTsExtraOffset)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t\t  simulcast,\n\t\t\t\t\t\t  \"giving up on proper stream switching after got a requested keyframe for which still too high RTP timestamp extra offset is needed (%\" PRIu32\n\t\t\t\t\t\t  \")\",\n\t\t\t\t\t\t  tsExtraOffset);\n\n\t\t\t\t\t\ttsExtraOffset = 1u;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (tsExtraOffset > maxTsExtraOffset)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  simulcast,\n\t\t\t\t\t  \"cannot switch stream due to too high RTP timestamp extra offset needed (%\" PRIu32\n\t\t\t\t\t  \"), requesting keyframe\",\n\t\t\t\t\t  tsExtraOffset);\n\n\t\t\t\t\tRequestKeyFrameForTargetSpatialLayer();\n\n\t\t\t\t\tthis->keyFrameForTsOffsetRequested = true;\n\n\t\t\t\t\t// Reset flags since we are discarding this key frame.\n\t\t\t\t\tthis->syncRequired       = false;\n\t\t\t\t\tthis->spatialLayerToSync = -1;\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\t\tpacket->logger.Discarded(\n\t\t\t\t\t  RTC::RtcLogger::RtpPacket::DiscardReason::TOO_HIGH_TIMESTAMP_EXTRA_NEEDED);\n#endif\n\n\t\t\t\t\t// NOTE: Don't drop the packet in the RTP sequence manager since this\n\t\t\t\t\t// packet doesn't belong to the current spatial layer.\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (tsExtraOffset > 0u)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  simulcast,\n\t\t\t\t\t  \"RTP timestamp extra offset generated for stream switching: %\" PRIu32,\n\t\t\t\t\t  tsExtraOffset);\n\n\t\t\t\t\t// Increase the timestamp offset for the whole life of this Producer stream\n\t\t\t\t\t// (until switched to a different one).\n\t\t\t\t\ttsOffset -= tsExtraOffset;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->tsOffset = tsOffset;\n\n\t\t\t// Sync our RTP stream's sequence number.\n\t\t\t// If previous frame has not been sent completely when we switch layer,\n\t\t\t// we can tell libwebrtc that previous frame is incomplete by skipping\n\t\t\t// one RTP sequence number.\n\t\t\t// 'packet->GetSequenceNumber() -2' may increase SeqManager::base and\n\t\t\t// increase the output sequence number.\n\t\t\t// https://github.com/versatica/mediasoup/issues/408\n\t\t\tthis->rtpSeqManager.Sync(packet->GetSequenceNumber() - (this->lastSentPacketHasMarker ? 1 : 2));\n\n\t\t\tthis->encodingContext->SyncRequired();\n\n\t\t\tthis->syncRequired                 = false;\n\t\t\tthis->spatialLayerToSync           = -1;\n\t\t\tthis->keyFrameForTsOffsetRequested = false;\n\t\t}\n\n\t\tif (!shouldSwitchCurrentSpatialLayer && this->checkingForOldPacketsInSpatialLayer)\n\t\t{\n\t\t\t// If this is a packet previous to the spatial layer switch, ignore the\n\t\t\t// packet.\n\t\t\t// NOTE: We drop it in RTP sequence manager because this packet belongs\n\t\t\t// to current spatial layer.\n\t\t\tif (SeqManager<uint16_t>::IsSeqLowerThan(packet->GetSequenceNumber(), this->snReferenceSpatialLayer))\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(\n\t\t\t\t  RTC::RtcLogger::RtpPacket::DiscardReason::PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\t\treturn;\n\t\t\t}\n\t\t\telse if (\n\t\t\t  SeqManager<uint16_t>::IsSeqHigherThan(\n\t\t\t    packet->GetSequenceNumber(), this->snReferenceSpatialLayer + MaxSequenceNumberGap))\n\t\t\t{\n\t\t\t\tthis->checkingForOldPacketsInSpatialLayer = false;\n\t\t\t}\n\t\t}\n\n\t\tbool marker{ false };\n\n\t\tif (shouldSwitchCurrentSpatialLayer)\n\t\t{\n\t\t\t// Update current spatial layer.\n\t\t\tthis->currentSpatialLayer = this->targetLayers.spatial;\n\n\t\t\tthis->snReferenceSpatialLayer             = packet->GetSequenceNumber();\n\t\t\tthis->checkingForOldPacketsInSpatialLayer = true;\n\n\t\t\t// Update target and current temporal layer.\n\t\t\tthis->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal);\n\t\t\tthis->encodingContext->SetCurrentTemporalLayer(packet->GetTemporalLayer());\n\n\t\t\t// Reset the score of our RtpStream to 10.\n\t\t\tthis->rtpStream->ResetScore(10u, /*notify*/ false);\n\n\t\t\t// Emit the layersChange event.\n\t\t\tEmitLayersChange();\n\n\t\t\t// Emit the score event.\n\t\t\tEmitScore();\n\n\t\t\t// Rewrite payload if needed.\n\t\t\tpacket->ProcessPayload(this->encodingContext.get(), marker);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tauto previousTemporalLayer = this->encodingContext->GetCurrentTemporalLayer();\n\n\t\t\t// Rewrite payload if needed. Drop packet if necessary.\n\t\t\t// NOTE: We drop it in RTP sequence manager because this packet belongs\n\t\t\t// to current spatial layer.\n\t\t\tif (!packet->ProcessPayload(this->encodingContext.get(), marker))\n\t\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC);\n#endif\n\n\t\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (previousTemporalLayer != this->encodingContext->GetCurrentTemporalLayer())\n\t\t\t{\n\t\t\t\tEmitLayersChange();\n\t\t\t}\n\t\t}\n\n\t\t// Update RTP seq number and timestamp based on NTP offset.\n\t\tuint16_t seq;\n\t\tconst uint32_t timestamp = packet->GetTimestamp() - this->tsOffset;\n\n\t\tthis->rtpSeqManager.Input(packet->GetSequenceNumber(), seq);\n\n\t\t// Save original packet fields.\n\t\tauto origSsrc      = packet->GetSsrc();\n\t\tauto origSeq       = packet->GetSequenceNumber();\n\t\tauto origTimestamp = packet->GetTimestamp();\n\n\t\t// Rewrite packet.\n\t\tpacket->SetSsrc(this->rtpParameters.encodings[0].ssrc);\n\t\tpacket->SetSequenceNumber(seq);\n\t\tpacket->SetTimestamp(timestamp);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.sendRtpTimestamp = timestamp;\n\t\tpacket->logger.sendSeqNumber    = seq;\n#endif\n\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp,\n\t\t\t  \"sending sync packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSsrc,\n\t\t\t  origSeq,\n\t\t\t  origTimestamp);\n\t\t}\n\n\t\tconst RTC::RTP::RtpStreamSend::ReceivePacketResult result =\n\t\t  this->rtpStream->ReceivePacket(packet, sharedPacket);\n\n\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t{\n\t\t\tif (this->rtpSeqManager.GetMaxOutput() == packet->GetSequenceNumber())\n\t\t\t{\n\t\t\t\tthis->lastSentPacketHasMarker = packet->HasMarker();\n\t\t\t}\n\n\t\t\t// Send the packet.\n\t\t\tthis->listener->OnConsumerSendRtpPacket(this, packet);\n\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventRtpAndKeyFrameTypes(packet);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  rtp,\n\t\t\t  \"failed to send packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSsrc,\n\t\t\t  origSeq,\n\t\t\t  origTimestamp);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED);\n#endif\n\t\t}\n\n\t\t// Restore packet fields.\n\t\tpacket->SetSsrc(origSsrc);\n\t\tpacket->SetSequenceNumber(origSeq);\n\t\tpacket->SetTimestamp(origTimestamp);\n\n\t\t// Restore the original payload if needed.\n\t\tpacket->RestorePayload();\n\n\t\t// If sharedPacket doesn't have a packet inside and it has been stored we\n\t\t// need to clone the packet into it.\n\t\tif (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED)\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\n\t\t// If sent packet was the first packet of a key frame, let's send buffered\n\t\t// packets belonging to the same key frame that arrived earlier due to\n\t\t// packet misorder.\n\t\tif (sendPacketsInTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\t// NOTE: Only send buffered packets if the first packet containing the key\n\t\t\t// frame was sent.\n\t\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t\t{\n\t\t\t\tfor (auto& kv : this->targetLayerRetransmissionBuffer)\n\t\t\t\t{\n\t\t\t\t\tauto& bufferedSharedPacket = kv.second;\n\t\t\t\t\tauto* bufferedPacket       = bufferedSharedPacket.GetPacket();\n\n\t\t\t\t\tif (bufferedPacket->GetSequenceNumber() > origSeq)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"sending packet buffered in the target layer retransmission buffer [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t\t\t\t  \"] after sending first packet of the key frame [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  bufferedPacket->GetSsrc(),\n\t\t\t\t\t\t  bufferedPacket->GetSequenceNumber(),\n\t\t\t\t\t\t  bufferedPacket->GetTimestamp(),\n\t\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\t\t\tSendRtpPacket(bufferedPacket, bufferedSharedPacket);\n\n\t\t\t\t\t\t// Be sure that the target layer retransmission buffer has not been\n\t\t\t\t\t\t// emptied as a result of sending this packet. If so, exit the loop.\n\t\t\t\t\t\tif (this->targetLayerRetransmissionBuffer.empty())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"target layer retransmission buffer emptied while iterating it, exiting the loop\");\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tbool SimulcastConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (static_cast<float>((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tauto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs);\n\n\t\tif (!senderReport)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Build SDES chunk for this sender.\n\t\tauto* sdesChunk = this->rtpStream->GetRtcpSdesChunk();\n\n\t\tauto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs);\n\n\t\t// RTCP Compound packet buffer cannot hold the data.\n\t\tif (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->lastRtcpSentTime = nowMs;\n\n\t\treturn true;\n\t}\n\n\tvoid SimulcastConsumer::NeedWorstRemoteFractionLost(\n\t  uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto fractionLost = this->rtpStream->GetFractionLost();\n\n\t\t// If our fraction lost is worse than the given one, update it.\n\t\tworstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost);\n\t}\n\n\tvoid SimulcastConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventNackType();\n\n\t\tthis->rtpStream->ReceiveNack(nackPacket);\n\t}\n\n\tvoid SimulcastConsumer::ReceiveKeyFrameRequest(\n\t  RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (messageType)\n\t\t{\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t{\n\t\t\t\tEmitTraceEventPliType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t{\n\t\t\t\tEmitTraceEventFirType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tthis->rtpStream->ReceiveKeyFrameRequest(messageType);\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrameForCurrentSpatialLayer();\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpReceiverReport(report);\n\t}\n\n\tvoid SimulcastConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report);\n\t}\n\n\tuint32_t SimulcastConsumer::GetTransmissionRate(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->rtpStream->GetBitrate(nowMs);\n\t}\n\n\tfloat SimulcastConsumer::GetRtt() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->rtpStream->GetRtt();\n\t}\n\n\tvoid SimulcastConsumer::UserOnTransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired                 = true;\n\t\tthis->spatialLayerToSync           = -1;\n\t\tthis->keyFrameForTsOffsetRequested = false;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::UserOnTransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastBweDowngradeAtMs = 0u;\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\n\t\tUpdateTargetLayers(-1, -1);\n\t}\n\n\tvoid SimulcastConsumer::UserOnPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastBweDowngradeAtMs = 0u;\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\n\t\tUpdateTargetLayers(-1, -1);\n\n\t\tif (this->externallyManagedBitrate)\n\t\t{\n\t\t\tthis->listener->OnConsumerNeedZeroBitrate(this);\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::UserOnResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired                        = true;\n\t\tthis->spatialLayerToSync                  = -1;\n\t\tthis->keyFrameForTsOffsetRequested        = false;\n\t\tthis->checkingForOldPacketsInSpatialLayer = false;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::CreateRtpStream()\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tMS_DEBUG_TAG(\n\t\t  rtp, \"[ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \"]\", encoding.ssrc, mediaCodec->payloadType);\n\n\t\t// Set stream params.\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc           = encoding.ssrc;\n\t\tparams.payloadType    = mediaCodec->payloadType;\n\t\tparams.mimeType       = mediaCodec->mimeType;\n\t\tparams.clockRate      = mediaCodec->clockRate;\n\t\tparams.cname          = this->rtpParameters.rtcp.cname;\n\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\tparams.temporalLayers = encoding.temporalLayers;\n\n\t\t// Check in band FEC in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"useinbandfec\") && mediaCodec->parameters.GetInteger(\"useinbandfec\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"in band FEC enabled\");\n\n\t\t\tparams.useInBandFec = true;\n\t\t}\n\n\t\t// Check DTX in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"usedtx\") && mediaCodec->parameters.GetInteger(\"usedtx\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\t// Check DTX in the encoding.\n\t\tif (encoding.dtx)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\tfor (const auto& fb : mediaCodec->rtcpFeedback)\n\t\t{\n\t\t\tif (!params.useNack && fb.type == \"nack\" && fb.parameter.empty())\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"NACK supported\");\n\n\t\t\t\tparams.useNack = true;\n\t\t\t}\n\t\t\telse if (!params.usePli && fb.type == \"nack\" && fb.parameter == \"pli\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"PLI supported\");\n\n\t\t\t\tparams.usePli = true;\n\t\t\t}\n\t\t\telse if (!params.useFir && fb.type == \"ccm\" && fb.parameter == \"fir\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"FIR supported\");\n\n\t\t\t\tparams.useFir = true;\n\t\t\t}\n\t\t}\n\n\t\tthis->rtpStream =\n\t\t  new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid);\n\t\tthis->rtpStreams.push_back(this->rtpStream);\n\n\t\t// If the Consumer is paused, tell the RtpStreamSend.\n\t\tif (IsPaused() || IsProducerPaused())\n\t\t{\n\t\t\tthis->rtpStream->Pause();\n\t\t}\n\n\t\tconst auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\n\t\tif (rtxCodec && encoding.hasRtx)\n\t\t{\n\t\t\tthis->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc);\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::RequestKeyFrames()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* producerTargetRtpStream  = GetProducerTargetRtpStream();\n\t\tauto* producerCurrentRtpStream = GetProducerCurrentRtpStream();\n\n\t\tif (producerTargetRtpStream)\n\t\t{\n\t\t\tauto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc;\n\n\t\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t\t}\n\n\t\tif (producerCurrentRtpStream && producerCurrentRtpStream != producerTargetRtpStream)\n\t\t{\n\t\t\tauto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc;\n\n\t\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::RequestKeyFrameForTargetSpatialLayer()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* producerTargetRtpStream = GetProducerTargetRtpStream();\n\n\t\tif (!producerTargetRtpStream)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc;\n\n\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t}\n\n\tvoid SimulcastConsumer::RequestKeyFrameForCurrentSpatialLayer()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* producerCurrentRtpStream = GetProducerCurrentRtpStream();\n\n\t\tif (!producerCurrentRtpStream)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc;\n\n\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t}\n\n\tvoid SimulcastConsumer::MayChangeLayers(bool force)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::ConsumerTypes::VideoLayers newTargetLayers;\n\n\t\tif (RecalculateTargetLayers(newTargetLayers))\n\t\t{\n\t\t\t// If bitrate externally managed, don't bother the transport unless\n\t\t\t// the newTargetSpatialLayer has changed (or force is true).\n\t\t\t// This is because, if bitrate is externally managed, the target temporal\n\t\t\t// layer is managed by the available given bitrate so the transport\n\t\t\t// will let us change it when it considers.\n\t\t\tif (this->externallyManagedBitrate)\n\t\t\t{\n\t\t\t\tif (newTargetLayers.spatial != this->targetLayers.spatial || force)\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnConsumerNeedBitrateChange(this);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tUpdateTargetLayers(newTargetLayers.spatial, newTargetLayers.temporal);\n\t\t\t}\n\t\t}\n\t}\n\n\tbool SimulcastConsumer::RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Start with no layers.\n\t\tnewTargetLayers.Reset();\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\tfor (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx)\n\t\t{\n\t\t\tauto spatialLayer       = static_cast<int16_t>(sIdx);\n\t\t\tauto* producerRtpStream = this->producerRtpStreams.at(sIdx);\n\t\t\tauto producerScore      = producerRtpStream ? producerRtpStream->GetScore() : 0u;\n\n\t\t\t// If this is higher than current spatial layer and we moved to to current spatial\n\t\t\t// layer due to BWE limitations, check how much it has elapsed since then.\n\t\t\tif (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs)\n\t\t\t{\n\t\t\t\tif (newTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ignore spatial layers for non existing Producer streams or for those\n\t\t\t// with score 0.\n\t\t\tif (producerScore == 0u)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If the stream has not been active time enough and we have an active one\n\t\t\t// already, move to the next spatial layer.\n\t\t\t// NOTE: Require bitrate externally managed for this.\n\t\t\tif (this->externallyManagedBitrate && newTargetLayers.spatial != -1 && producerRtpStream->GetActiveMs() < StreamMinActiveMs)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// We may not yet switch to this spatial layer.\n\t\t\tif (!CanSwitchToSpatialLayer(spatialLayer))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnewTargetLayers.spatial = spatialLayer;\n\n\t\t\t// If this is the preferred or higher spatial layer take it and exit.\n\t\t\tif (spatialLayer >= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (newTargetLayers.spatial != -1)\n\t\t{\n\t\t\tif (newTargetLayers.spatial == this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = this->preferredLayers.temporal;\n\t\t\t}\n\t\t\telse if (newTargetLayers.spatial < this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = static_cast<int16_t>(this->rtpStream->GetTemporalLayers() - 1);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = 0;\n\t\t\t}\n\t\t}\n\n\t\t// Return true if any target layer changed.\n\t\treturn (newTargetLayers != this->targetLayers);\n\t}\n\n\tvoid SimulcastConsumer::UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// If we don't have yet a RTP timestamp reference, set it now.\n\t\tif (\n\t\t  newTargetSpatialLayer != -1 && (this->tsReferenceSpatialLayer == -1 ||\n\t\t                                  !GetProducerTsReferenceRtpStream()->GetSenderReportNtpMs()))\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  simulcast, \"using spatial layer %\" PRIi16 \" as RTP timestamp reference\", newTargetSpatialLayer);\n\n\t\t\tthis->tsReferenceSpatialLayer = newTargetSpatialLayer;\n\t\t}\n\n\t\t// If the new target spatial layer doesn't match the current one, clear the\n\t\t// target layer retransmission buffer.\n\t\tif (newTargetSpatialLayer != this->targetLayers.spatial)\n\t\t{\n\t\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t\t}\n\n\t\tif (newTargetSpatialLayer == -1)\n\t\t{\n\t\t\t// Unset current and target layers.\n\t\t\tthis->targetLayers.spatial  = -1;\n\t\t\tthis->targetLayers.temporal = -1;\n\t\t\tthis->currentSpatialLayer   = -1;\n\n\t\t\tthis->encodingContext->SetTargetTemporalLayer(-1);\n\t\t\tthis->encodingContext->SetCurrentTemporalLayer(-1);\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  simulcast, \"target layers changed [spatial:-1, temporal:-1, consumerId:%s]\", this->id.c_str());\n\n\t\t\tEmitLayersChange();\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->targetLayers.spatial  = newTargetSpatialLayer;\n\t\tthis->targetLayers.temporal = newTargetTemporalLayer;\n\n\t\t// If the new target spatial layer matches the current one, apply the new\n\t\t// target temporal layer now.\n\t\tif (this->targetLayers.spatial == this->currentSpatialLayer)\n\t\t{\n\t\t\tthis->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal);\n\t\t}\n\n\t\tMS_DEBUG_TAG(\n\t\t  simulcast,\n\t\t  \"target layers changed [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t  this->targetLayers.spatial,\n\t\t  this->targetLayers.temporal,\n\t\t  this->id.c_str());\n\n\t\t// If the target spatial layer is different than the current one, request\n\t\t// a key frame.\n\t\tif (this->targetLayers.spatial != this->currentSpatialLayer)\n\t\t{\n\t\t\tRequestKeyFrameForTargetSpatialLayer();\n\t\t}\n\t}\n\n\tbool SimulcastConsumer::CanSwitchToSpatialLayer(int16_t spatialLayer) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// This method assumes that the caller has verified that there is a valid\n\t\t// Producer RtpStream for the given spatial layer.\n\t\tMS_ASSERT(\n\t\t  this->producerRtpStreams.at(spatialLayer),\n\t\t  \"no Producer RtpStream for the given spatialLayer:%\" PRIi16,\n\t\t  spatialLayer);\n\n\t\t// We can switch to the given spatial layer if:\n\t\t// - we don't have any TS reference spatial layer yet, or\n\t\t// - the given spatial layer matches the TS reference spatial layer, or\n\t\t// - both , the RTP streams of our TS reference spatial layer and the given\n\t\t//   spatial layer, have Sender Report.\n\t\treturn (\n\t\t  this->tsReferenceSpatialLayer == -1 || spatialLayer == this->tsReferenceSpatialLayer ||\n\t\t  this->producerRtpStreams.at(spatialLayer)->GetSenderReportNtpMs());\n\t}\n\n\tvoid SimulcastConsumer::StorePacketInTargetLayerRetransmissionBuffer(\n\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"storing packet in target layer retransmission buffer [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t  packet->GetSsrc(),\n\t\t  packet->GetSequenceNumber(),\n\t\t  packet->GetTimestamp());\n\n\t\t// Store original packet into the buffer. Only clone once and only if\n\t\t// necessary.\n\t\tif (!sharedPacket.HasPacket())\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\t\t// Assert that, if sharedPacket was already filled, both packet and\n\t\t// sharedPacket are the very same RTP packet.\n\t\telse\n\t\t{\n\t\t\tsharedPacket.AssertSamePacket(packet);\n\t\t}\n\n\t\tthis->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket;\n\n\t\tif (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize)\n\t\t{\n\t\t\tthis->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin());\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::EmitScore() const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\n\t\tauto notificationOffset = FBS::Consumer::CreateScoreNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_SCORE,\n\t\t  FBS::Notification::Body::Consumer_ScoreNotification,\n\t\t  notificationOffset);\n\t}\n\n\tvoid SimulcastConsumer::EmitLayersChange() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"current layers changed to [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t  this->currentSpatialLayer,\n\t\t  this->encodingContext->GetCurrentTemporalLayer(),\n\t\t  this->id.c_str());\n\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerLayers> layersOffset;\n\n\t\tif (this->currentSpatialLayer >= 0)\n\t\t{\n\t\t\tlayersOffset = FBS::Consumer::CreateConsumerLayers(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  this->currentSpatialLayer,\n\t\t\t  this->encodingContext->GetCurrentTemporalLayer());\n\t\t}\n\n\t\tauto notificationOffset = FBS::Consumer::CreateLayersChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), layersOffset);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_LAYERS_CHANGE,\n\t\t  FBS::Notification::Body::Consumer_LayersChangeNotification,\n\t\t  notificationOffset);\n\t}\n\n\tRTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerCurrentRtpStream() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->currentSpatialLayer == -1)\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t// This may return nullptr.\n\t\treturn this->producerRtpStreams.at(this->currentSpatialLayer);\n\t}\n\n\tRTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerTargetRtpStream() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->targetLayers.spatial == -1)\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t// This may return nullptr.\n\t\treturn this->producerRtpStreams.at(this->targetLayers.spatial);\n\t}\n\n\tRTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerTsReferenceRtpStream() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->tsReferenceSpatialLayer == -1)\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t// This may return nullptr.\n\t\treturn this->producerRtpStreams.at(this->tsReferenceSpatialLayer);\n\t}\n\n\tvoid SimulcastConsumer::OnRtpStreamScore(\n\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\n\t\tif (IsActive())\n\t\t{\n\t\t\t// Just check target layers if our bitrate is not externally managed.\n\t\t\t// NOTE: For now this is a bit useless since, when locally managed, we do\n\t\t\t// not check the Consumer score at all.\n\t\t\tif (!this->externallyManagedBitrate)\n\t\t\t{\n\t\t\t\tMayChangeLayers();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SimulcastConsumer::OnRtpStreamRetransmitRtpPacket(\n\t  RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnConsumerRetransmitRtpPacket(this, packet);\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SrtpSession.cpp",
    "content": "#define MS_CLASS \"RTC::SrtpSession\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SrtpSession.hpp\"\n#include \"DepLibSRTP.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memset()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t EncryptBufferSize{ 65536 };\n\talignas(4) static thread_local uint8_t EncryptBuffer[EncryptBufferSize];\n\n\t/* Class methods. */\n\n\tvoid SrtpSession::ClassInit()\n\t{\n\t\t// Set libsrtp event handler.\n\t\tconst srtp_err_status_t err =\n\t\t  srtp_install_event_handler(static_cast<srtp_event_handler_func_t*>(OnSrtpEvent));\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_THROW_ERROR(\n\t\t\t  \"srtp_install_event_handler() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\t\t}\n\t}\n\n\tFBS::SrtpParameters::SrtpCryptoSuite SrtpSession::CryptoSuiteToFbs(CryptoSuite cryptoSuite)\n\t{\n\t\tswitch (cryptoSuite)\n\t\t{\n\t\t\tcase SrtpSession::CryptoSuite::AEAD_AES_256_GCM:\n\t\t\t{\n\t\t\t\treturn FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM;\n\t\t\t}\n\n\t\t\tcase SrtpSession::CryptoSuite::AEAD_AES_128_GCM:\n\t\t\t{\n\t\t\t\treturn FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_128_GCM;\n\t\t\t}\n\n\t\t\tcase SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\t{\n\t\t\t\treturn FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_80;\n\t\t\t}\n\n\t\t\tcase SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t{\n\t\t\t\treturn FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_32;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tSrtpSession::CryptoSuite SrtpSession::CryptoSuiteFromFbs(FBS::SrtpParameters::SrtpCryptoSuite cryptoSuite)\n\t{\n\t\tswitch (cryptoSuite)\n\t\t{\n\t\t\tcase FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM:\n\t\t\t{\n\t\t\t\treturn SrtpSession::CryptoSuite::AEAD_AES_256_GCM;\n\t\t\t}\n\n\t\t\tcase FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_128_GCM:\n\t\t\t{\n\t\t\t\treturn SrtpSession::CryptoSuite::AEAD_AES_128_GCM;\n\t\t\t}\n\n\t\t\tcase FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\t{\n\t\t\t\treturn SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80;\n\t\t\t}\n\n\t\t\tcase FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t{\n\t\t\t\treturn SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tvoid SrtpSession::OnSrtpEvent(srtp_event_data_t* data)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (data->event)\n\t\t{\n\t\t\tcase event_ssrc_collision:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"SSRC collision occurred\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase event_key_soft_limit:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"stream reached the soft key usage limit and will expire soon\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase event_key_hard_limit:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"stream reached the hard key usage limit and has expired\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase event_packet_index_limit:\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"stream reached the hard packet limit (2^48 packets)\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Instance methods. */\n\n\tSrtpSession::SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tsrtp_policy_t policy; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t\t// Set all policy fields to 0.\n\t\tstd::memset(&policy, 0, sizeof(srtp_policy_t));\n\n\t\tswitch (cryptoSuite)\n\t\t{\n\t\t\tcase CryptoSuite::AEAD_AES_256_GCM:\n\t\t\t{\n\t\t\t\tsrtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp);\n\t\t\t\tsrtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase CryptoSuite::AEAD_AES_128_GCM:\n\t\t\t{\n\t\t\t\tsrtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);\n\t\t\t\tsrtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase CryptoSuite::AES_CM_128_HMAC_SHA1_80:\n\t\t\t{\n\t\t\t\tsrtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);\n\t\t\t\tsrtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase CryptoSuite::AES_CM_128_HMAC_SHA1_32:\n\t\t\t{\n\t\t\t\tsrtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);\n\t\t\t\t// NOTE: Must be 80 for RTCP.\n\t\t\t\tsrtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ABORT(\"unknown SRTP crypto suite\");\n\t\t\t}\n\t\t}\n\n\t\tMS_ASSERT(\n\t\t  keyLen == policy.rtp.cipher_key_len, \"given keyLen does not match policy.rtp.cipher_keyLen\");\n\n\t\tswitch (type)\n\t\t{\n\t\t\tcase Type::INBOUND:\n\t\t\t{\n\t\t\t\tpolicy.ssrc.type = ssrc_any_inbound;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Type::OUTBOUND:\n\t\t\t{\n\t\t\t\tpolicy.ssrc.type = ssrc_any_outbound;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tpolicy.ssrc.value = 0;\n\t\tpolicy.key        = key;\n\t\t// Required for sending RTP retransmission without RTX.\n\t\tpolicy.allow_repeat_tx = true;\n\t\tpolicy.window_size     = 1024;\n\t\tpolicy.next            = nullptr;\n\n\t\t// Set the SRTP session.\n\t\tconst srtp_err_status_t err = srtp_create(&this->session, &policy);\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_THROW_ERROR(\"srtp_create() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\t\t}\n\t}\n\n\tSrtpSession::~SrtpSession()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->session != nullptr)\n\t\t{\n\t\t\tconst srtp_err_status_t err = srtp_dealloc(this->session);\n\n\t\t\tif (DepLibSRTP::IsError(err))\n\t\t\t{\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"srtp_dealloc() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\t\t\t\t}\n\t\t\t\tcatch (const std::exception& error) // NOLINT(bugprone-empty-catch)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: This is to avoid a warning:\n\t\t\t\t\t// '~SrtpSession' has a non-throwing exception specification but can\n\t\t\t\t\t// still throw [-Wexceptions]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tbool SrtpSession::EncryptRtp(const uint8_t** data, size_t* len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure that the resulting SRTP packet fits into the encrypt buffer.\n\t\tif (*len + SRTP_MAX_TRAILER_LEN > EncryptBufferSize)\n\t\t{\n\t\t\tMS_WARN_TAG(srtp, \"cannot encrypt RTP packet, size too big (%zu bytes)\", *len);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tuint8_t* encryptBuffer = EncryptBuffer;\n\t\tsize_t encryptLen      = EncryptBufferSize;\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\tif (DepLibUring::IsEnabled())\n\t\t{\n\t\t\tif (!DepLibUring::IsActive())\n\t\t\t{\n\t\t\t\tgoto protect;\n\t\t\t}\n\n\t\t\t// Use a preallocated buffer, if available.\n\t\t\tauto* sendBuffer = DepLibUring::GetSendBuffer();\n\n\t\t\tif (sendBuffer)\n\t\t\t{\n\t\t\t\tencryptBuffer = sendBuffer;\n\t\t\t\tencryptLen    = DepLibUring::SendBufferSize;\n\t\t\t}\n\t\t}\n\n\tprotect:\n#endif\n\n\t\tconst srtp_err_status_t err = srtp_protect(\n\t\t  /*srtp_t ctx*/ this->session,\n\t\t  /*const uint8_t* rtp*/ *data,\n\t\t  /*size_t rtp_len*/ *len,\n\t\t  /*uint8_t* srtp*/ encryptBuffer,\n\t\t  /*size_t* srtp_len*/ std::addressof(encryptLen),\n\t\t  /*size_t mki_index*/ 0);\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_WARN_TAG(srtp, \"srtp_protect() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Update the given data pointer and len.\n\t\t*data = const_cast<const uint8_t*>(encryptBuffer);\n\t\t*len  = encryptLen;\n\n\t\treturn true;\n\t}\n\n\tbool SrtpSession::DecryptSrtp(uint8_t* data, size_t* len)\n\t{\n\t\tMS_TRACE();\n\n\t\tsize_t decryptLen = *len;\n\n\t\tconst srtp_err_status_t err = srtp_unprotect(\n\t\t  /*srtp_t ctx*/ this->session,\n\t\t  /*const uint8_t* srtp*/ data,\n\t\t  /*size_t srtp_len*/ *len,\n\t\t  /*uint8_t* rtp*/ data,\n\t\t  /*size_t* rtp_len*/ std::addressof(decryptLen));\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_DEBUG_TAG(srtp, \"srtp_unprotect() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Update the given len.\n\t\t*len = decryptLen;\n\n\t\treturn true;\n\t}\n\n\tbool SrtpSession::EncryptRtcp(const uint8_t** data, size_t* len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure that the resulting SRTCP packet fits into the encrypt buffer.\n\t\tif (*len + SRTP_MAX_TRAILER_LEN > EncryptBufferSize)\n\t\t{\n\t\t\tMS_WARN_TAG(srtp, \"cannot encrypt RTCP packet, size too big (%zu bytes)\", *len);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tuint8_t* encryptBuffer = EncryptBuffer;\n\t\tsize_t encryptLen      = EncryptBufferSize;\n\n\t\tconst srtp_err_status_t err = srtp_protect_rtcp(\n\t\t  /*srtp_t ctx*/ this->session,\n\t\t  /*const uint8_t* rtcp*/ *data,\n\t\t  /*size_t rtcp_len*/ *len,\n\t\t  /*uint8_t* srtcp*/ encryptBuffer,\n\t\t  /*size_t* srtcp_len*/ std::addressof(encryptLen),\n\t\t  /*size_t mki_index*/ 0);\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_WARN_TAG(srtp, \"srtp_protect_rtcp() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Update the given data pointer and len.\n\t\t*data = const_cast<const uint8_t*>(EncryptBuffer);\n\t\t*len  = encryptLen;\n\n\t\treturn true;\n\t}\n\n\tbool SrtpSession::DecryptSrtcp(uint8_t* data, size_t* len)\n\t{\n\t\tMS_TRACE();\n\n\t\tsize_t decryptLen = *len;\n\n\t\tconst srtp_err_status_t err = srtp_unprotect_rtcp(\n\t\t  /*srtp_t ctx*/ this->session,\n\t\t  /*const uint8_t* srtcp*/ data,\n\t\t  /*size_t srtcp_len*/ *len,\n\t\t  /*uint8_t* rtcp*/ data,\n\t\t  /*size_t* rtcp_len*/ std::addressof(decryptLen));\n\n\t\tif (DepLibSRTP::IsError(err))\n\t\t{\n\t\t\tMS_DEBUG_TAG(srtp, \"srtp_unprotect_rtcp() failed: %s\", DepLibSRTP::GetErrorString(err).c_str());\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Update the given len.\n\t\t*len = decryptLen;\n\n\t\treturn true;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/SvcConsumer.cpp",
    "content": "#define MS_CLASS \"RTC::SvcConsumer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/SvcConsumer.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/RTP/Codecs/Tools.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <limits> // std::numeric_limits\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint64_t BweDowngradeConservativeMs{ 10000u };\n\tstatic constexpr uint64_t BweDowngradeMinActiveMs{ 8000u };\n\tstatic constexpr size_t TargetLayerRetransmissionBufferSize{ 20u };\n\n\t/* Instance methods. */\n\n\tSvcConsumer::SvcConsumer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const std::string& producerId,\n\t  RTC::Consumer::Listener* listener,\n\t  const FBS::Transport::ConsumeRequest* data)\n\t  : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SVC)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure there is a single encoding.\n\t\tif (this->consumableRtpEncodings.size() != 1u)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid consumableRtpEncodings with size != 1\");\n\t\t}\n\n\t\tauto& encoding = this->rtpParameters.encodings[0];\n\n\t\t// Ensure there are multiple spatial or temporal layers.\n\t\tif (encoding.spatialLayers < 2u && encoding.temporalLayers < 2u)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid number of layers\");\n\t\t}\n\n\t\t// Set preferredLayers (if given).\n\t\tif (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS))\n\t\t{\n\t\t\tthis->preferredLayers.spatial = data->preferredLayers()->spatialLayer();\n\n\t\t\tif (this->preferredLayers.spatial > encoding.spatialLayers - 1)\n\t\t\t{\n\t\t\t\tthis->preferredLayers.spatial = static_cast<int16_t>(encoding.spatialLayers - 1);\n\t\t\t}\n\n\t\t\tif (flatbuffers::IsFieldPresent(data->preferredLayers(), FBS::Consumer::ConsumerLayers::VT_TEMPORALLAYER))\n\t\t\t{\n\t\t\t\tif (this->preferredLayers.temporal > encoding.temporalLayers - 1)\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Initially set preferredSpatialLayer and preferredTemporalLayer to the\n\t\t\t// maximum value.\n\t\t\tthis->preferredLayers.spatial  = static_cast<int16_t>(encoding.spatialLayers - 1);\n\t\t\tthis->preferredLayers.temporal = static_cast<int16_t>(encoding.temporalLayers - 1);\n\t\t}\n\n\t\t// Create the encoding context.\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tif (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType))\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"%s codec not supported for svc\", mediaCodec->mimeType.ToString().c_str());\n\t\t}\n\n\t\t// Let's chosee an initial output seq number between 1000 and 32768 to avoid\n\t\t// libsrtp bug:\n\t\t// https://github.com/versatica/mediasoup/issues/1437\n\t\tconst uint16_t initialOutputSeq =\n\t\t  Utils::Crypto::GetRandomUInt<uint16_t>(1000u, std::numeric_limits<uint16_t>::max() / 2);\n\n\t\tthis->rtpSeqManager = RTC::SeqManager<uint16_t>(initialOutputSeq);\n\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\n\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\tparams.temporalLayers = encoding.temporalLayers;\n\t\tparams.ksvc           = encoding.ksvc;\n\n\t\tthis->encodingContext.reset(\n\t\t  RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params));\n\n\t\tMS_ASSERT(this->encodingContext, \"no encoding context for this codec\");\n\n\t\t// Create RtpStreamSend instance for sending a single stream to the remote.\n\t\tCreateRtpStream();\n\n\t\t// NOTE: This may throw.\n\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t  this->id,\n\t\t  /*channelRequestHandler*/ this,\n\t\t  /*channelNotificationHandler*/ nullptr);\n\t}\n\n\tSvcConsumer::~SvcConsumer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\tdelete this->rtpStream;\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::DumpResponse> SvcConsumer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Call the parent method.\n\t\tauto base = RTC::Consumer::FillBuffer(builder);\n\t\t// Add rtpStream.\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Dump>> rtpStreams;\n\t\trtpStreams.emplace_back(this->rtpStream->FillBuffer(builder));\n\n\t\tauto dump = FBS::Consumer::CreateConsumerDumpDirect(\n\t\t  builder,\n\t\t  base,\n\t\t  &rtpStreams,\n\t\t  this->preferredLayers.spatial,\n\t\t  this->encodingContext->GetTargetSpatialLayer(),\n\t\t  this->encodingContext->GetCurrentSpatialLayer(),\n\t\t  this->preferredLayers.temporal,\n\t\t  this->encodingContext->GetTargetTemporalLayer(),\n\t\t  this->encodingContext->GetCurrentTemporalLayer());\n\n\t\treturn FBS::Consumer::CreateDumpResponse(builder, dump);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::GetStatsResponse> SvcConsumer::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpStream::Stats>> rtpStreams;\n\n\t\t// Add stats of our send stream.\n\t\trtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder));\n\n\t\t// Add stats of our recv stream.\n\t\tif (this->producerRtpStream)\n\t\t{\n\t\t\trtpStreams.emplace_back(producerRtpStream->FillBufferStats(builder));\n\t\t}\n\n\t\treturn FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams);\n\t}\n\n\tflatbuffers::Offset<FBS::Consumer::ConsumerScore> SvcConsumer::FillBufferScore(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->producerRtpStreamScores, \"producerRtpStreamScores not set\");\n\n\t\tuint8_t producerScore{ 0 };\n\n\t\tif (this->producerRtpStream)\n\t\t{\n\t\t\tproducerScore = this->producerRtpStream->GetScore();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tproducerScore = 0;\n\t\t}\n\n\t\treturn FBS::Consumer::CreateConsumerScoreDirect(\n\t\t  builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores);\n\t}\n\n\tvoid SvcConsumer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME:\n\t\t\t{\n\t\t\t\tif (IsActive())\n\t\t\t\t{\n\t\t\t\t\tRequestKeyFrame();\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS:\n\t\t\t{\n\t\t\t\tauto previousPreferredLayers = this->preferredLayers;\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Consumer::SetPreferredLayersRequest>();\n\t\t\t\tconst auto* preferredLayers = body->preferredLayers();\n\n\t\t\t\t// Spatial layer.\n\t\t\t\tthis->preferredLayers.spatial = preferredLayers->spatialLayer();\n\n\t\t\t\tif (this->preferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1)\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.spatial =\n\t\t\t\t\t  static_cast<int16_t>(this->rtpStream->GetSpatialLayers() - 1);\n\t\t\t\t}\n\n\t\t\t\t// preferredTemporaLayer is optional.\n\t\t\t\tauto preferredTemporalLayer = preferredLayers->temporalLayer();\n\n\t\t\t\tif (preferredTemporalLayer.has_value())\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal = preferredTemporalLayer.value();\n\n\t\t\t\t\tif (this->preferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->preferredLayers.temporal =\n\t\t\t\t\t\t  static_cast<int16_t>(this->rtpStream->GetTemporalLayers() - 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->preferredLayers.temporal = this->rtpStream->GetTemporalLayers() - 1;\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"preferred layers changed [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t\t\t  this->preferredLayers.spatial,\n\t\t\t\t  this->preferredLayers.temporal,\n\t\t\t\t  this->id.c_str());\n\n\t\t\t\tpreferredTemporalLayer     = this->preferredLayers.temporal;\n\t\t\t\tauto preferredLayersOffset = FBS::Consumer::CreateConsumerLayers(\n\t\t\t\t  request->GetBufferBuilder(), this->preferredLayers.spatial, preferredTemporalLayer);\n\t\t\t\tauto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse(\n\t\t\t\t  request->GetBufferBuilder(), preferredLayersOffset);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset);\n\n\t\t\t\tif (IsActive() && this->preferredLayers != previousPreferredLayers)\n\t\t\t\t{\n\t\t\t\t\tMayChangeLayers(/*force*/ true);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Consumer::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SvcConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->producerRtpStream = rtpStream;\n\t}\n\n\tvoid SvcConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->producerRtpStream = rtpStream;\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SvcConsumer::ProducerRtpStreamScore(\n\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit score event.\n\t\tEmitScore();\n\n\t\tif (RTC::Consumer::IsActive())\n\t\t{\n\t\t\t// Just check target layers if the stream has died or reborned.\n\t\t\tif (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u))\n\t\t\t{\n\t\t\t\tMayChangeLayers();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SvcConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Do nothing.\n\t}\n\n\tuint8_t SvcConsumer::GetBitratePriority() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->priority;\n\t}\n\n\tuint32_t SvcConsumer::IncreaseLayer(uint32_t bitrate, bool considerLoss)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\tif (this->producerRtpStream->GetScore() == 0u)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\t// If already in the preferred layers, do nothing.\n\t\tif (this->provisionalTargetLayers == this->preferredLayers)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tuint32_t virtualBitrate;\n\n\t\tif (considerLoss)\n\t\t{\n\t\t\t// Calculate virtual available bitrate based on given bitrate and our\n\t\t\t// packet lost.\n\t\t\tauto lossPercentage = this->rtpStream->GetLossPercentage();\n\n\t\t\tif (lossPercentage < 2)\n\t\t\t{\n\t\t\t\tvirtualBitrate = 1.08 * bitrate;\n\t\t\t}\n\t\t\telse if (lossPercentage > 10)\n\t\t\t{\n\t\t\t\tvirtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tvirtualBitrate = bitrate;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvirtualBitrate = bitrate;\n\t\t}\n\n\t\tuint32_t requiredBitrate{ 0u };\n\t\tint16_t spatialLayer{ 0 };\n\t\tint16_t temporalLayer{ 0 };\n\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\tfor (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer)\n\t\t{\n\t\t\t// If this is higher than current spatial layer and we moved to to current spatial\n\t\t\t// layer due to BWE limitations, check how much it has elapsed since then.\n\t\t\tif (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs)\n\t\t\t{\n\t\t\t\tif (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"avoid upgrading to spatial layer %\" PRIi16 \" due to recent BWE downgrade\", spatialLayer);\n\n\t\t\t\t\tgoto done;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ignore spatial layers lower than the one we already have.\n\t\t\tif (spatialLayer < this->provisionalTargetLayers.spatial)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttemporalLayer = 0;\n\n\t\t\t// Check bitrate of every temporal layer.\n\t\t\tfor (; temporalLayer < this->producerRtpStream->GetTemporalLayers(); ++temporalLayer)\n\t\t\t{\n\t\t\t\t// Ignore temporal layers lower than the one we already have (taking into account\n\t\t\t\t// the spatial layer too).\n\t\t\t\tif (\n\t\t\t\t  spatialLayer == this->provisionalTargetLayers.spatial &&\n\t\t\t\t  temporalLayer <= this->provisionalTargetLayers.temporal)\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\trequiredBitrate =\n\t\t\t\t  this->producerRtpStream->GetLayerBitrate(nowMs, spatialLayer, temporalLayer);\n\n\t\t\t\t// When using K-SVC we must subtract the bitrate of the current used layer\n\t\t\t\t// if the new layer is the temporal layer 0 of an higher spatial layer.\n\t\t\t\t//\n\t\t\t\tif (\n\t\t\t\t  this->encodingContext->IsKSvc() && requiredBitrate && temporalLayer == 0 &&\n\t\t\t\t  this->provisionalTargetLayers.spatial > -1 &&\n\t\t\t\t  spatialLayer > this->provisionalTargetLayers.spatial)\n\t\t\t\t{\n\t\t\t\t\tauto provisionalRequiredBitrate = this->producerRtpStream->GetSpatialLayerBitrate(\n\t\t\t\t\t  nowMs, this->provisionalTargetLayers.spatial);\n\n\t\t\t\t\tif (requiredBitrate > provisionalRequiredBitrate)\n\t\t\t\t\t{\n\t\t\t\t\t\trequiredBitrate -= provisionalRequiredBitrate;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\trequiredBitrate = 1u; // Don't set 0 since it would be ignored.\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"testing layers %\" PRIi16 \":%\" PRIi16 \" [virtual bitrate:%\" PRIu32\n\t\t\t\t  \", required bitrate:%\" PRIu32 \"]\",\n\t\t\t\t  spatialLayer,\n\t\t\t\t  temporalLayer,\n\t\t\t\t  virtualBitrate,\n\t\t\t\t  requiredBitrate);\n\n\t\t\t\t// If active layer, end iterations here. Otherwise move to next spatial layer.\n\t\t\t\tif (requiredBitrate)\n\t\t\t\t{\n\t\t\t\t\tgoto done;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If this is the preferred or higher spatial layer, take it and exit.\n\t\t\tif (spatialLayer >= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\tdone:\n\n\t\t// No higher active layers found.\n\t\tif (!requiredBitrate)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\t// No luck.\n\t\tif (requiredBitrate > virtualBitrate)\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\t// Set provisional layers.\n\t\tthis->provisionalTargetLayers.spatial  = spatialLayer;\n\t\tthis->provisionalTargetLayers.temporal = temporalLayer;\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"upgrading to layers %\" PRIi16 \":%\" PRIi16 \" [virtual bitrate:%\" PRIu32\n\t\t  \", required bitrate:%\" PRIu32 \"]\",\n\t\t  this->provisionalTargetLayers.spatial,\n\t\t  this->provisionalTargetLayers.temporal,\n\t\t  virtualBitrate,\n\t\t  requiredBitrate);\n\n\t\tif (requiredBitrate <= bitrate)\n\t\t{\n\t\t\treturn requiredBitrate;\n\t\t}\n\t\telse if (requiredBitrate <= virtualBitrate)\n\t\t{\n\t\t\treturn bitrate;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn requiredBitrate; // NOTE: This cannot happen.\n\t\t}\n\t}\n\n\tvoid SvcConsumer::ApplyLayers()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\t\tMS_ASSERT(IsActive(), \"should be active\");\n\n\t\tauto provisionalTargetLayers = this->provisionalTargetLayers;\n\n\t\t// Reset provisional target layers.\n\t\tthis->provisionalTargetLayers.Reset();\n\n\t\tif (provisionalTargetLayers != this->encodingContext->GetTargetLayers())\n\t\t{\n\t\t\tUpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal);\n\n\t\t\t// If this looks like a spatial layer downgrade due to BWE limitations, set member.\n\t\t\tif (\n\t\t\t  this->rtpStream->GetActiveMs() > BweDowngradeMinActiveMs &&\n\t\t\t  this->encodingContext->GetTargetSpatialLayer() <\n\t\t\t    this->encodingContext->GetCurrentSpatialLayer() &&\n\t\t\t  this->encodingContext->GetCurrentSpatialLayer() <= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"possible target spatial layer downgrade (from %\" PRIi16 \" to %\" PRIi16\n\t\t\t\t  \") due to BWE limitation\",\n\t\t\t\t  this->encodingContext->GetCurrentSpatialLayer(),\n\t\t\t\t  this->encodingContext->GetTargetSpatialLayer());\n\n\t\t\t\tthis->lastBweDowngradeAtMs = this->shared->GetTimeMs();\n\t\t\t}\n\t\t}\n\t}\n\n\tuint32_t SvcConsumer::GetDesiredBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->externallyManagedBitrate, \"bitrate is not externally managed\");\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\t\tuint32_t desiredBitrate{ 0u };\n\n\t\t// When using K-SVC each spatial layer is independent of the others.\n\t\tif (this->encodingContext->IsKSvc())\n\t\t{\n\t\t\t// Let's iterate all spatial layers of the Producer (from highest to lowest) and\n\t\t\t// obtain their bitrate. Choose the highest one.\n\t\t\t// NOTE: When the Producer enables a higher spatial layer, initially the bitrate\n\t\t\t// oft could be less than the bitrate of a lower one. That's why we iterate all\n\t\t\t// spatial layers here anyway.\n\t\t\tfor (auto spatialLayer{ this->producerRtpStream->GetSpatialLayers() - 1 }; spatialLayer >= 0;\n\t\t\t     --spatialLayer)\n\t\t\t{\n\t\t\t\tauto spatialLayerBitrate =\n\t\t\t\t  this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer);\n\n\t\t\t\tdesiredBitrate = std::max(spatialLayerBitrate, desiredBitrate);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tdesiredBitrate = this->producerRtpStream->GetBitrate(nowMs);\n\t\t}\n\n\t\t// If consumer.rtpParameters.encodings[0].maxBitrate was given and it's\n\t\t// greater than computed one, then use it.\n\t\tauto maxBitrate = this->rtpParameters.encodings[0].maxBitrate;\n\n\t\tdesiredBitrate = std::max(maxBitrate, desiredBitrate);\n\n\t\treturn desiredBitrate;\n\t}\n\n\t// NOLINTNEXTLINE(misc-no-recursion)\n\tvoid SvcConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.consumerId = this->id;\n#endif\n\n\t\tif (!IsActive())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (this->encodingContext->GetTargetSpatialLayer() == -1 || this->encodingContext->GetTargetTemporalLayer() == -1)\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If we need to sync and this is not a key frame, ignore the packet.\n\t\tif (this->syncRequired && !packet->IsKeyFrame())\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME);\n#endif\n\n\t\t\t// NOTE: No need to drop the packet in the RTP sequence manager since here\n\t\t\t// we are blocking all packets but the key frame that would trigger sync\n\t\t\t// below.\n\n\t\t\t// Store the packet for the scenario in which this packet is part of the\n\t\t\t// key frame and it arrived before the first packet of the key frame.\n\t\t\tStorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket);\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto payloadType = packet->GetPayloadType();\n\n\t\t// NOTE: This may happen if this Consumer supports just some codecs of those\n\t\t// in the corresponding Producer.\n\t\tif (!this->supportedCodecPayloadTypes[payloadType])\n\t\t{\n\t\t\tMS_WARN_DEV(\"payload type not supported [payloadType:%\" PRIu8 \"]\", payloadType);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Packets with only padding are not forwarded.\n\t\tif (packet->GetPayloadLength() == 0)\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Whether this is the first packet after re-sync.\n\t\tconst bool isSyncPacket = this->syncRequired;\n\n\t\t// Whether packets stored in the target layer retransmission buffer must be\n\t\t// sent once this packet is sent.\n\t\tbool sendPacketsInTargetLayerRetransmissionBuffer{ false };\n\n\t\t// Sync sequence number and timestamp if required.\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tif (packet->IsKeyFrame())\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  rtp,\n\t\t\t\t  \"sync key frame received [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\tsendPacketsInTargetLayerRetransmissionBuffer = true;\n\t\t\t}\n\n\t\t\tthis->rtpSeqManager.Sync(packet->GetSequenceNumber() - 1);\n\t\t\tthis->encodingContext->SyncRequired();\n\n\t\t\tthis->syncRequired = false;\n\t\t}\n\n\t\tauto previousLayers = this->encodingContext->GetCurrentLayers();\n\n\t\tbool marker{ false };\n\t\tconst bool origMarker = packet->HasMarker();\n\n\t\tif (!packet->ProcessPayload(this->encodingContext.get(), marker))\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC);\n#endif\n\n\t\t\tthis->rtpSeqManager.Drop(packet->GetSequenceNumber());\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (previousLayers != this->encodingContext->GetCurrentLayers())\n\t\t{\n\t\t\t// Emit the layersChange event.\n\t\t\tEmitLayersChange();\n\t\t}\n\n\t\t// Update RTP seq number and timestamp based on NTP offset.\n\t\tuint16_t seq;\n\n\t\tthis->rtpSeqManager.Input(packet->GetSequenceNumber(), seq);\n\n\t\t// Save original packet fields.\n\t\tauto origSsrc = packet->GetSsrc();\n\t\tauto origSeq  = packet->GetSequenceNumber();\n\n\t\t// Rewrite packet.\n\t\tpacket->SetSsrc(this->rtpParameters.encodings[0].ssrc);\n\t\tpacket->SetSequenceNumber(seq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.sendRtpTimestamp = packet->GetTimestamp();\n\t\tpacket->logger.sendSeqNumber    = seq;\n#endif\n\n\t\tpacket->SetMarker(marker);\n\n\t\tif (isSyncPacket)\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  rtp,\n\t\t\t  \"sending sync packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSeq);\n\t\t}\n\n\t\tconst RTC::RTP::RtpStreamSend::ReceivePacketResult result =\n\t\t  this->rtpStream->ReceivePacket(packet, sharedPacket);\n\n\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t{\n\t\t\t// Send the packet.\n\t\t\tthis->listener->OnConsumerSendRtpPacket(this, packet);\n\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventRtpAndKeyFrameTypes(packet);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  rtp,\n\t\t\t  \"failed to send packet [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t  \"] from original [ssrc:%\" PRIu32 \", seq:%\" PRIu16 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetSequenceNumber(),\n\t\t\t  packet->GetTimestamp(),\n\t\t\t  origSsrc,\n\t\t\t  origSeq);\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED);\n#endif\n\t\t}\n\n\t\t// Restore packet fields.\n\t\tpacket->SetSsrc(origSsrc);\n\t\tpacket->SetSequenceNumber(origSeq);\n\t\tpacket->SetMarker(origMarker);\n\n\t\t// Restore the original payload if needed.\n\t\tpacket->RestorePayload();\n\n\t\t// If sharedPacket doesn't have a packet inside and it has been stored we\n\t\t// need to clone the packet into it.\n\t\tif (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED)\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\n\t\t// If sent packet was the first packet of a key frame, let's send buffered\n\t\t// packets belonging to the same key frame that arrived earlier due to\n\t\t// packet misorder.\n\t\tif (sendPacketsInTargetLayerRetransmissionBuffer)\n\t\t{\n\t\t\t// NOTE: Only send buffered packets if the first packet containing the key\n\t\t\t// frame was sent.\n\t\t\tif (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED)\n\t\t\t{\n\t\t\t\tfor (auto& kv : this->targetLayerRetransmissionBuffer)\n\t\t\t\t{\n\t\t\t\t\tauto& bufferedSharedPacket = kv.second;\n\t\t\t\t\tauto* bufferedPacket       = bufferedSharedPacket.GetPacket();\n\n\t\t\t\t\tif (bufferedPacket->GetSequenceNumber() > origSeq)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t  \"sending packet buffered in the target layer retransmission buffer [ssrc:%\" PRIu32\n\t\t\t\t\t\t  \", seq:%\" PRIu16 \", ts:%\" PRIu32\n\t\t\t\t\t\t  \"] after sending first packet of the key frame [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t\t\t\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  bufferedPacket->GetSsrc(),\n\t\t\t\t\t\t  bufferedPacket->GetSequenceNumber(),\n\t\t\t\t\t\t  bufferedPacket->GetTimestamp(),\n\t\t\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t\t\t  packet->GetSequenceNumber(),\n\t\t\t\t\t\t  packet->GetTimestamp());\n\n\t\t\t\t\t\tSendRtpPacket(bufferedPacket, bufferedSharedPacket);\n\n\t\t\t\t\t\t// Be sure that the target layer retransmission buffer has not been\n\t\t\t\t\t\t// emptied as a result of sending this packet. If so, exit the loop.\n\t\t\t\t\t\tif (this->targetLayerRetransmissionBuffer.empty())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t\t\t  \"target layer retransmission buffer emptied while iterating it, exiting the loop\");\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis->targetLayerRetransmissionBuffer.clear();\n\t\t}\n\t}\n\n\tbool SvcConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (static_cast<float>((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tauto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs);\n\n\t\tif (!senderReport)\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// Build SDES chunk for this sender.\n\t\tauto* sdesChunk = this->rtpStream->GetRtcpSdesChunk();\n\n\t\tauto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs);\n\n\t\t// RTCP Compound packet buffer cannot hold the data.\n\t\tif (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo))\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tthis->lastRtcpSentTime = nowMs;\n\n\t\treturn true;\n\t}\n\n\tvoid SvcConsumer::NeedWorstRemoteFractionLost(uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto fractionLost = this->rtpStream->GetFractionLost();\n\n\t\t// If our fraction lost is worse than the given one, update it.\n\t\tworstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost);\n\t}\n\n\tvoid SvcConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventNackType();\n\n\t\tthis->rtpStream->ReceiveNack(nackPacket);\n\t}\n\n\tvoid SvcConsumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (messageType)\n\t\t{\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t{\n\t\t\t\tEmitTraceEventPliType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t{\n\t\t\t\tEmitTraceEventFirType(ssrc);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tthis->rtpStream->ReceiveKeyFrameRequest(messageType);\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tRequestKeyFrame();\n\t\t}\n\t}\n\n\tvoid SvcConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpReceiverReport(report);\n\t}\n\n\tvoid SvcConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report);\n\t}\n\n\tuint32_t SvcConsumer::GetTransmissionRate(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsActive())\n\t\t{\n\t\t\treturn 0u;\n\t\t}\n\n\t\treturn this->rtpStream->GetBitrate(nowMs);\n\t}\n\n\tfloat SvcConsumer::GetRtt() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->rtpStream->GetRtt();\n\t}\n\n\tvoid SvcConsumer::UserOnTransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired = true;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SvcConsumer::UserOnTransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastBweDowngradeAtMs = 0u;\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\n\t\tUpdateTargetLayers(-1, -1);\n\t}\n\n\tvoid SvcConsumer::UserOnPaused()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastBweDowngradeAtMs = 0u;\n\n\t\tthis->rtpStream->Pause();\n\t\tthis->targetLayerRetransmissionBuffer.clear();\n\n\t\tUpdateTargetLayers(-1, -1);\n\n\t\tif (this->externallyManagedBitrate)\n\t\t{\n\t\t\tthis->listener->OnConsumerNeedZeroBitrate(this);\n\t\t}\n\t}\n\n\tvoid SvcConsumer::UserOnResumed()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->syncRequired = true;\n\n\t\tif (IsActive())\n\t\t{\n\t\t\tMayChangeLayers();\n\t\t}\n\t}\n\n\tvoid SvcConsumer::CreateRtpStream()\n\t{\n\t\tMS_TRACE();\n\n\t\tauto& encoding         = this->rtpParameters.encodings[0];\n\t\tconst auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding);\n\n\t\tMS_DEBUG_TAG(\n\t\t  rtp, \"[ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \"]\", encoding.ssrc, mediaCodec->payloadType);\n\n\t\t// Set stream params.\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc           = encoding.ssrc;\n\t\tparams.payloadType    = mediaCodec->payloadType;\n\t\tparams.mimeType       = mediaCodec->mimeType;\n\t\tparams.clockRate      = mediaCodec->clockRate;\n\t\tparams.cname          = this->rtpParameters.rtcp.cname;\n\t\tparams.spatialLayers  = encoding.spatialLayers;\n\t\tparams.temporalLayers = encoding.temporalLayers;\n\n\t\t// Check in band FEC in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"useinbandfec\") && mediaCodec->parameters.GetInteger(\"useinbandfec\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"in band FEC enabled\");\n\n\t\t\tparams.useInBandFec = true;\n\t\t}\n\n\t\t// Check DTX in codec parameters.\n\t\tif (mediaCodec->parameters.HasInteger(\"usedtx\") && mediaCodec->parameters.GetInteger(\"usedtx\") == 1)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\t// Check DTX in the encoding.\n\t\tif (encoding.dtx)\n\t\t{\n\t\t\tMS_DEBUG_TAG(rtp, \"DTX enabled\");\n\n\t\t\tparams.useDtx = true;\n\t\t}\n\n\t\tfor (const auto& fb : mediaCodec->rtcpFeedback)\n\t\t{\n\t\t\tif (!params.useNack && fb.type == \"nack\" && fb.parameter.empty())\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"NACK supported\");\n\n\t\t\t\tparams.useNack = true;\n\t\t\t}\n\t\t\telse if (!params.usePli && fb.type == \"nack\" && fb.parameter == \"pli\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"PLI supported\");\n\n\t\t\t\tparams.usePli = true;\n\t\t\t}\n\t\t\telse if (!params.useFir && fb.type == \"ccm\" && fb.parameter == \"fir\")\n\t\t\t{\n\t\t\t\tMS_DEBUG_2TAGS(rtp, rtcp, \"FIR supported\");\n\n\t\t\t\tparams.useFir = true;\n\t\t\t}\n\t\t}\n\n\t\tthis->rtpStream =\n\t\t  new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid);\n\t\tthis->rtpStreams.push_back(this->rtpStream);\n\n\t\t// If the Consumer is paused, tell the RtpStreamSend.\n\t\tif (IsPaused() || IsProducerPaused())\n\t\t{\n\t\t\tthis->rtpStream->Pause();\n\t\t}\n\n\t\tconst auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding);\n\n\t\tif (rtxCodec && encoding.hasRtx)\n\t\t{\n\t\t\tthis->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc);\n\t\t}\n\t}\n\n\tvoid SvcConsumer::RequestKeyFrame()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->kind != RTC::Media::Kind::VIDEO)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto mappedSsrc = this->consumableRtpEncodings[0].ssrc;\n\n\t\tthis->listener->OnConsumerKeyFrameRequested(this, mappedSsrc);\n\t}\n\n\tvoid SvcConsumer::MayChangeLayers(bool force)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::ConsumerTypes::VideoLayers newTargetLayers;\n\n\t\tif (RecalculateTargetLayers(newTargetLayers))\n\t\t{\n\t\t\t// If bitrate externally managed, don't bother the transport unless\n\t\t\t// the newTargetSpatialLayer has changed (or force is true).\n\t\t\t// This is because, if bitrate is externally managed, the target temporal\n\t\t\t// layer is managed by the available given bitrate so the transport\n\t\t\t// will let us change it when it considers.\n\t\t\tif (this->externallyManagedBitrate)\n\t\t\t{\n\t\t\t\tif (newTargetLayers.spatial != this->encodingContext->GetTargetSpatialLayer() || force)\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnConsumerNeedBitrateChange(this);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tUpdateTargetLayers(newTargetLayers.spatial, newTargetLayers.temporal);\n\t\t\t}\n\t\t}\n\t}\n\n\tbool SvcConsumer::RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Start with no layers.\n\t\tnewTargetLayers.Reset();\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\t\tint16_t spatialLayer{ 0 };\n\n\t\tif (!this->producerRtpStream)\n\t\t{\n\t\t\tgoto done;\n\t\t}\n\n\t\tif (this->producerRtpStream->GetScore() == 0u)\n\t\t{\n\t\t\tgoto done;\n\t\t}\n\n\t\tfor (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer)\n\t\t{\n\t\t\t// If this is higher than current spatial layer and we moved to to current spatial\n\t\t\t// layer due to BWE limitations, check how much it has elapsed since then.\n\t\t\tif (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs)\n\t\t\t{\n\t\t\t\tif (newTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer())\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer))\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tnewTargetLayers.spatial = spatialLayer;\n\n\t\t\t// If this is the preferred or higher spatial layer and has bitrate,\n\t\t\t// take it and exit.\n\t\t\tif (spatialLayer >= this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (newTargetLayers.spatial != -1)\n\t\t{\n\t\t\tif (newTargetLayers.spatial == this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = this->preferredLayers.temporal;\n\t\t\t}\n\t\t\telse if (newTargetLayers.spatial < this->preferredLayers.spatial)\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = static_cast<int16_t>(this->rtpStream->GetTemporalLayers() - 1);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tnewTargetLayers.temporal = 0;\n\t\t\t}\n\t\t}\n\n\tdone:\n\n\t\t// Return true if any target layer changed.\n\t\treturn newTargetLayers != this->encodingContext->GetTargetLayers();\n\t}\n\n\tvoid SvcConsumer::UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (newTargetSpatialLayer == -1)\n\t\t{\n\t\t\t// Unset current and target layers.\n\t\t\tthis->encodingContext->SetTargetSpatialLayer(-1);\n\t\t\tthis->encodingContext->SetCurrentSpatialLayer(-1);\n\t\t\tthis->encodingContext->SetTargetTemporalLayer(-1);\n\t\t\tthis->encodingContext->SetCurrentTemporalLayer(-1);\n\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  svc, \"target layers changed [spatial:-1, temporal:-1, consumerId:%s]\", this->id.c_str());\n\n\t\t\tEmitLayersChange();\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->encodingContext->SetTargetSpatialLayer(newTargetSpatialLayer);\n\t\tthis->encodingContext->SetTargetTemporalLayer(newTargetTemporalLayer);\n\n\t\tMS_DEBUG_TAG(\n\t\t  svc,\n\t\t  \"target layers changed [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t  newTargetSpatialLayer,\n\t\t  newTargetTemporalLayer,\n\t\t  this->id.c_str());\n\n\t\t// Target spatial layer has changed.\n\t\tif (newTargetSpatialLayer != this->encodingContext->GetCurrentSpatialLayer())\n\t\t{\n\t\t\t// In K-SVC always ask for a keyframe when changing target spatial layer.\n\t\t\tif (this->encodingContext->IsKSvc())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"K-SVC: requesting keyframe to target spatial change\");\n\n\t\t\t\tRequestKeyFrame();\n\t\t\t}\n\t\t\t// In full SVC just ask for a keyframe when upgrading target spatial layer.\n\t\t\t// NOTE: This is because nobody implements RTCP LRR yet.\n\t\t\telse if (newTargetSpatialLayer > this->encodingContext->GetCurrentSpatialLayer())\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"full SVC: requesting keyframe to target spatial upgrade\");\n\n\t\t\t\tRequestKeyFrame();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SvcConsumer::StorePacketInTargetLayerRetransmissionBuffer(\n\t  RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"storing packet in target layer retransmission buffer [ssrc:%\" PRIu32 \", seq:%\" PRIu16\n\t\t  \", ts:%\" PRIu32 \"]\",\n\t\t  packet->GetSsrc(),\n\t\t  packet->GetSequenceNumber(),\n\t\t  packet->GetTimestamp());\n\n\t\t// Store original packet into the buffer. Only clone once and only if\n\t\t// necessary.\n\t\tif (!sharedPacket.HasPacket())\n\t\t{\n\t\t\tsharedPacket.Assign(packet);\n\t\t}\n\t\t// Assert that, if sharedPacket was already filled, both packet and\n\t\t// sharedPacket are the very same RTP packet.\n\t\telse\n\t\t{\n\t\t\tsharedPacket.AssertSamePacket(packet);\n\t\t}\n\n\t\tthis->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket;\n\n\t\tif (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize)\n\t\t{\n\t\t\tthis->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin());\n\t\t}\n\t}\n\n\tvoid SvcConsumer::EmitScore() const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder());\n\n\t\tauto notificationOffset = FBS::Consumer::CreateScoreNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_SCORE,\n\t\t  FBS::Notification::Body::Consumer_ScoreNotification,\n\t\t  notificationOffset);\n\t}\n\n\tvoid SvcConsumer::EmitLayersChange() const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"current layers changed to [spatial:%\" PRIi16 \", temporal:%\" PRIi16 \", consumerId:%s]\",\n\t\t  this->encodingContext->GetCurrentSpatialLayer(),\n\t\t  this->encodingContext->GetCurrentTemporalLayer(),\n\t\t  this->id.c_str());\n\n\t\tflatbuffers::Offset<FBS::Consumer::ConsumerLayers> layersOffset;\n\n\t\tif (this->encodingContext->GetCurrentSpatialLayer() >= 0)\n\t\t{\n\t\t\tlayersOffset = FBS::Consumer::CreateConsumerLayers(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  this->encodingContext->GetCurrentSpatialLayer(),\n\t\t\t  this->encodingContext->GetCurrentTemporalLayer());\n\t\t}\n\n\t\tauto notificationOffset = FBS::Consumer::CreateLayersChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), layersOffset);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::CONSUMER_LAYERS_CHANGE,\n\t\t  FBS::Notification::Body::Consumer_LayersChangeNotification,\n\t\t  notificationOffset);\n\t}\n\n\tvoid SvcConsumer::OnRtpStreamScore(\n\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Emit the score event.\n\t\tEmitScore();\n\n\t\tif (IsActive())\n\t\t{\n\t\t\t// Just check target layers if our bitrate is not externally managed.\n\t\t\t// NOTE: For now this is a bit useless since, when locally managed, we do\n\t\t\t// not check the Consumer score at all.\n\t\t\tif (!this->externallyManagedBitrate)\n\t\t\t{\n\t\t\t\tMayChangeLayers();\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid SvcConsumer::OnRtpStreamRetransmitRtpPacket(\n\t  RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnConsumerRetransmitRtpPacket(this, packet);\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx());\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TcpConnection.cpp",
    "content": "#define MS_CLASS \"RTC::TcpConnection\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/TcpConnection.hpp\"\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memmove(), std::memcpy()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t ReadBufferSize{ 65536 };\n\t// NOTE: Buffer must be 4-byte aligned since RTP/RTCP/STUN packet parsing casts\n\t// it to structs (e.g. RTP::Packet::FixedHeader) that require 4-byte alignment.\n\t// Without this, accessing multi-byte fields would be undefined behavior on\n\t// strict-alignment architectures.\n\talignas(4) static thread_local uint8_t ReadBuffer[ReadBufferSize];\n\n\t/* Instance methods. */\n\n\tTcpConnection::TcpConnection(Listener* listener, size_t bufferSize)\n\t  : ::TcpConnectionHandle::TcpConnectionHandle(bufferSize), listener(listener)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tTcpConnection::~TcpConnection()\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid TcpConnection::UserOnTcpConnectionRead()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"data received [local:%s :%\" PRIu16 \", remote:%s :%\" PRIu16 \"]\",\n\t\t  GetLocalIp().c_str(),\n\t\t  GetLocalPort(),\n\t\t  GetPeerIp().c_str(),\n\t\t  GetPeerPort());\n\n\t\t/*\n\t\t * Framing RFC 4571\n\t\t *\n\t\t *     0                   1                   2                   3\n\t\t *     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n\t\t *     ---------------------------------------------------------------\n\t\t *     |             LENGTH            |  STUN / DTLS / RTP / RTCP   |\n\t\t *     ---------------------------------------------------------------\n\t\t *\n\t\t * A 16-bit unsigned integer LENGTH field, coded in network byte order\n\t\t * (big-endian), begins the frame.  If LENGTH is non-zero, an RTP or\n\t\t * RTCP packet follows the LENGTH field.  The value coded in the LENGTH\n\t\t * field MUST equal the number of octets in the RTP or RTCP packet.\n\t\t * Zero is a valid value for LENGTH, and it codes the null packet.\n\t\t */\n\n\t\t// Be ready to parse more than a single frame in a single TCP chunk.\n\t\twhile (true)\n\t\t{\n\t\t\t// We may receive multiple packets in the same TCP chunk. If one of them is\n\t\t\t// a DTLS Close Alert this would be closed (Close() called) so we cannot call\n\t\t\t// our listeners anymore.\n\t\t\tif (IsClosed())\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst size_t dataLen = this->bufferDataLen - this->frameStart;\n\t\t\tsize_t packetLen;\n\n\t\t\tif (dataLen >= 2)\n\t\t\t{\n\t\t\t\tpacketLen = size_t{ Utils::Byte::Get2Bytes(this->buffer + this->frameStart, 0) };\n\t\t\t}\n\n\t\t\t// We have packetLen bytes.\n\t\t\tif (dataLen >= 2 && dataLen >= 2 + packetLen)\n\t\t\t{\n\t\t\t\tconst uint8_t* packet = this->buffer + this->frameStart + 2;\n\n\t\t\t\t// Update received bytes and notify the listener.\n\t\t\t\tif (packetLen != 0)\n\t\t\t\t{\n\t\t\t\t\t// Copy the received packet into the static buffer so it can be expanded\n\t\t\t\t\t// later.\n\t\t\t\t\tstd::memcpy(ReadBuffer, packet, packetLen);\n\n\t\t\t\t\tthis->listener->OnTcpConnectionPacketReceived(this, ReadBuffer, packetLen, ReadBufferSize);\n\t\t\t\t}\n\n\t\t\t\t// If there is no more space available in the buffer and that is because\n\t\t\t\t// the latest parsed frame filled it, then empty the full buffer.\n\t\t\t\tif ((this->frameStart + 2 + packetLen) == this->bufferSize)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"no more space in the buffer, emptying the buffer data\");\n\n\t\t\t\t\tthis->frameStart    = 0;\n\t\t\t\t\tthis->bufferDataLen = 0;\n\t\t\t\t}\n\t\t\t\t// If there is still space in the buffer, set the beginning of the next\n\t\t\t\t// frame to the next position after the parsed frame.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->frameStart += 2 + packetLen;\n\t\t\t\t}\n\n\t\t\t\t// If there is more data in the buffer after the parsed frame then\n\t\t\t\t// parse again. Otherwise break here and wait for more data.\n\t\t\t\tif (this->bufferDataLen > this->frameStart)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\"there is more data after the parsed frame, continue parsing\");\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Incomplete packet.\n\n\t\t\t// Check if the buffer is full.\n\t\t\tif (this->bufferDataLen == this->bufferSize)\n\t\t\t{\n\t\t\t\t// First case: the incomplete frame does not begin at position 0 of\n\t\t\t\t// the buffer, so move the frame to the position 0.\n\t\t\t\tif (this->frameStart != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t\t  \"no more space in the buffer, moving parsed bytes to the beginning of \"\n\t\t\t\t\t  \"the buffer and wait for more data\");\n\n\t\t\t\t\tstd::memmove(\n\t\t\t\t\t  this->buffer, this->buffer + this->frameStart, this->bufferSize - this->frameStart);\n\t\t\t\t\tthis->bufferDataLen = this->bufferSize - this->frameStart;\n\t\t\t\t\tthis->frameStart    = 0;\n\t\t\t\t}\n\t\t\t\t// Second case: the incomplete frame begins at position 0 of the buffer.\n\t\t\t\t// The frame is too big.\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_DEV(\n\t\t\t\t\t  \"no more space in the buffer for the unfinished frame being parsed, closing the \"\n\t\t\t\t\t  \"connection\");\n\n\t\t\t\t\tErrorReceiving();\n\n\t\t\t\t\t// And exit fast since we are supposed to be deallocated.\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The buffer is not full.\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_DEBUG_DEV(\"frame not finished yet, waiting for more data\");\n\t\t\t}\n\n\t\t\t// Exit the parsing loop.\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tvoid TcpConnection::Send(const uint8_t* data, size_t len, ::TcpConnectionHandle::onSendCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Write according to Framing RFC 4571.\n\n\t\tuint8_t frameLen[2];\n\n\t\tUtils::Byte::Set2Bytes(frameLen, 0, len);\n\t\t::TcpConnectionHandle::Write(frameLen, 2, data, len, cb);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TcpServer.cpp",
    "content": "#define MS_CLASS \"RTC::TcpServer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/TcpServer.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/PortManager.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr size_t TcpConnectionBufferSize{ 65536 };\n\n\t/* Instance methods. */\n\n\tTcpServer::TcpServer(\n\t  Listener* listener,\n\t  RTC::TcpConnection::Listener* connListener,\n\t  std::string& ip,\n\t  uint16_t port,\n\t  RTC::Transport::SocketFlags& flags)\n\t  : // This may throw.\n\t    ::TcpServerHandle::TcpServerHandle(RTC::PortManager::BindTcp(ip, port, flags)),\n\t    listener(listener),\n\t    connListener(connListener),\n\t    fixedPort(true)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tTcpServer::TcpServer(\n\t  Listener* listener,\n\t  RTC::TcpConnection::Listener* connListener,\n\t  std::string& ip,\n\t  uint16_t minPort,\n\t  uint16_t maxPort,\n\t  RTC::Transport::SocketFlags& flags,\n\t  uint64_t& portRangeHash)\n\t  : // This may throw.\n\t    ::TcpServerHandle::TcpServerHandle(\n\t      RTC::PortManager::BindTcp(ip, minPort, maxPort, flags, portRangeHash)),\n\t    listener(listener),\n\t    connListener(connListener)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->portRangeHash = portRangeHash;\n\t}\n\n\tTcpServer::~TcpServer()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->fixedPort)\n\t\t{\n\t\t\tRTC::PortManager::Unbind(this->portRangeHash, this->localPort);\n\t\t}\n\t}\n\n\tvoid TcpServer::UserOnTcpConnectionAlloc()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Allocate a new RTC::TcpConnection for the TcpServer to handle it.\n\t\tauto* connection = new RTC::TcpConnection(this->connListener, TcpConnectionBufferSize);\n\n\t\t// Accept it.\n\t\tAcceptTcpConnection(connection);\n\t}\n\n\tvoid TcpServer::UserOnTcpConnectionClosed(::TcpConnectionHandle* connection)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnRtcTcpConnectionClosed(this, static_cast<RTC::TcpConnection*>(connection));\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/Transport.cpp",
    "content": "#define MS_CLASS \"RTC::Transport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/Transport.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"FBS/transport.h\"\n#include \"RTC/BweType.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/PipeConsumer.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include \"RTC/SCTP/association/Association.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SimpleConsumer.hpp\"\n#include \"RTC/SimulcastConsumer.hpp\"\n#include \"RTC/SvcConsumer.hpp\"\n#ifdef MS_RTC_LOGGER_RTP\n#include \"RTC/RtcLogger.hpp\"\n#endif\n#include <libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h> // webrtc::RtpPacketSendInfo\n#include <iterator>                                              // std::ostream_iterator\n#include <map>                                                   // std::multimap\n\nnamespace RTC\n{\n\tstatic const size_t DefaultSctpSendBufferSize{ 262144 }; // 2^18 bytes.\n\tstatic const size_t MaxSctpSendBufferSize{ 268435456 };  // 2^28 bytes.\n\n\t/* Instance methods. */\n\n\tTransport::Transport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  const FBS::Transport::Options* options)\n\t  : id(id),\n\t    shared(shared),\n\t    listener(listener),\n\t    recvRtpTransmission(shared, /*ignorePaddingOnlyPackets*/ false),\n\t    sendRtpTransmission(shared, /*ignorePaddingOnlyPackets*/ false),\n\t    recvRtxTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 1000u),\n\t    sendRtxTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 1000u),\n\t    sendProbationTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 100u)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (options->direct())\n\t\t{\n\t\t\tthis->direct = true;\n\n\t\t\tif (auto maxMessageSize = options->maxMessageSize(); maxMessageSize.has_value())\n\t\t\t{\n\t\t\t\tthis->maxMessageSize = maxMessageSize.value();\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t  auto initialAvailableOutgoingBitrate = options->initialAvailableOutgoingBitrate();\n\t\t  initialAvailableOutgoingBitrate.has_value())\n\t\t{\n\t\t\tthis->initialAvailableOutgoingBitrate = initialAvailableOutgoingBitrate.value();\n\t\t}\n\n\t\tif (options->enableSctp())\n\t\t{\n\t\t\tif (this->direct)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"cannot enable SCTP in a direct Transport\");\n\t\t\t}\n\n\t\t\t// numSctpStreams is mandatory.\n\t\t\tif (!flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_NUMSCTPSTREAMS))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"numSctpStreams missing\");\n\t\t\t}\n\n\t\t\t// maxSctpMessageSize is mandatory.\n\t\t\tif (!flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_MAXSCTPMESSAGESIZE))\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"maxSctpMessageSize missing\");\n\t\t\t}\n\n\t\t\tthis->maxMessageSize = options->maxSctpMessageSize();\n\n\t\t\tsize_t sctpSendBufferSize;\n\n\t\t\t// sctpSendBufferSize is optional.\n\t\t\tif (flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_SCTPSENDBUFFERSIZE))\n\t\t\t{\n\t\t\t\tif (options->sctpSendBufferSize() > MaxSctpSendBufferSize)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"wrong sctpSendBufferSize (maximum value exceeded)\");\n\t\t\t\t}\n\n\t\t\t\tsctpSendBufferSize = options->sctpSendBufferSize();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsctpSendBufferSize = DefaultSctpSendBufferSize;\n\t\t\t}\n\n\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t{\n\t\t\t\t// TODO: SCTP: Many interesting options missing.\n\t\t\t\t// NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the\n\t\t\t\t// transport is ignored.\n\t\t\t\tconst RTC::SCTP::SctpOptions sctpOptions = { // TODO: SCTP: Sure?\n\t\t\t\t\t                                           .maxSendMessageSize = this->maxMessageSize,\n\t\t\t\t\t                                           .maxSendBufferSize  = sctpSendBufferSize\n\t\t\t\t};\n\n\t\t\t\tthis->sctpAssociation =\n\t\t\t\t  std::make_unique<RTC::SCTP::Association>(sctpOptions, this, this->shared);\n\t\t\t}\n\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\telse\n\t\t\t{\n\t\t\t\t// This may throw.\n\t\t\t\tthis->oldSctpAssociation = new RTC::SctpAssociation(\n\t\t\t\t  this,\n\t\t\t\t  options->numSctpStreams()->os(),\n\t\t\t\t  options->numSctpStreams()->mis(),\n\t\t\t\t  this->maxMessageSize,\n\t\t\t\t  sctpSendBufferSize,\n\t\t\t\t  options->isDataChannel());\n\t\t\t}\n\t\t}\n\n\t\t// Create the RTCP timer.\n\t\tthis->rtcpTimer = this->shared->CreateTimer(this);\n\t}\n\n\tTransport::~Transport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// The destructor must delete and clear everything silently.\n\n\t\t// Delete all Producers.\n\t\tfor (auto& kv : this->mapProducers)\n\t\t{\n\t\t\tauto* producer = kv.second;\n\n\t\t\tdelete producer;\n\t\t}\n\t\tthis->mapProducers.clear();\n\n\t\t// Delete all Consumers.\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\n\t\t\tdelete consumer;\n\t\t}\n\t\tthis->mapConsumers.clear();\n\t\tthis->mapSsrcConsumer.clear();\n\t\tthis->mapRtxSsrcConsumer.clear();\n\n\t\t// Delete all DataProducers.\n\t\tfor (auto& kv : this->mapDataProducers)\n\t\t{\n\t\t\tauto* dataProducer = kv.second;\n\n\t\t\tdelete dataProducer;\n\t\t}\n\t\tthis->mapDataProducers.clear();\n\n\t\t// Delete all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tdelete dataConsumer;\n\t\t}\n\t\tthis->mapDataConsumers.clear();\n\n\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\t// NOTE: When using the built-in SCTP stack we don't do anything here since\n\t\t\t// the `SetDestroying()` method has already been called by the Transport\n\t\t\t// subclass and it closed the SCTP Association.\n\t\t\t//\n\t\t\t// NOTE: We cannot do it here in the destructor because here we are no longer\n\t\t\t// the Transport subclass but Transport parent (this is how the destruction\n\t\t\t// chain works in C++).\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse\n\t\t{\n\t\t\t// Delete SCTP association.\n\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\tdelete this->oldSctpAssociation;\n\t\t\tthis->oldSctpAssociation = nullptr;\n\t\t}\n\n\t\t// Delete the RTCP timer.\n\t\tdelete this->rtcpTimer;\n\t\tthis->rtcpTimer = nullptr;\n\t}\n\n\tvoid Transport::CloseProducersAndConsumers()\n\t{\n\t\tMS_TRACE();\n\n\t\t// This method is called by the Router and must notify him about all Producers\n\t\t// and Consumers that we are gonna close.\n\t\t//\n\t\t// The caller is supposed to delete this Transport instance after calling\n\t\t// this method.\n\n\t\t// Close all Producers.\n\t\tfor (auto& kv : this->mapProducers)\n\t\t{\n\t\t\tauto* producer = kv.second;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnTransportProducerClosed(this, producer);\n\n\t\t\tdelete producer;\n\t\t}\n\t\tthis->mapProducers.clear();\n\n\t\t// Delete all Consumers.\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnTransportConsumerClosed(this, consumer);\n\n\t\t\tdelete consumer;\n\t\t}\n\t\tthis->mapConsumers.clear();\n\t\tthis->mapSsrcConsumer.clear();\n\t\tthis->mapRtxSsrcConsumer.clear();\n\n\t\t// Delete all DataProducers.\n\t\tfor (auto& kv : this->mapDataProducers)\n\t\t{\n\t\t\tauto* dataProducer = kv.second;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnTransportDataProducerClosed(this, dataProducer);\n\n\t\t\tdelete dataProducer;\n\t\t}\n\t\tthis->mapDataProducers.clear();\n\n\t\t// Delete all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnTransportDataConsumerClosed(this, dataConsumer);\n\n\t\t\tdelete dataConsumer;\n\t\t}\n\t\tthis->mapDataConsumers.clear();\n\t}\n\n\tvoid Transport::ListenServerClosed()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ask our parent Router to close/delete us.\n\t\tthis->listener->OnTransportListenServerClosed(this);\n\t}\n\n\tflatbuffers::Offset<FBS::Transport::Dump> Transport::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add producerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> producerIds;\n\n\t\tfor (const auto& kv : this->mapProducers)\n\t\t{\n\t\t\tconst auto& producerId = kv.first;\n\n\t\t\tproducerIds.emplace_back(builder.CreateString(producerId));\n\t\t}\n\n\t\t// Add consumerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> consumerIds;\n\n\t\tfor (const auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tconst auto& consumerId = kv.first;\n\n\t\t\tconsumerIds.emplace_back(builder.CreateString(consumerId));\n\t\t}\n\n\t\t// Add mapSsrcConsumerId.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::Uint32String>> mapSsrcConsumerId;\n\n\t\tfor (const auto& kv : this->mapSsrcConsumer)\n\t\t{\n\t\t\tauto ssrc      = kv.first;\n\t\t\tauto* consumer = kv.second;\n\n\t\t\tmapSsrcConsumerId.emplace_back(\n\t\t\t  FBS::Common::CreateUint32StringDirect(builder, ssrc, consumer->id.c_str()));\n\t\t}\n\n\t\t// Add mapRtxSsrcConsumerId.\n\t\tstd::vector<flatbuffers::Offset<FBS::Common::Uint32String>> mapRtxSsrcConsumerId;\n\n\t\tfor (const auto& kv : this->mapRtxSsrcConsumer)\n\t\t{\n\t\t\tauto ssrc      = kv.first;\n\t\t\tauto* consumer = kv.second;\n\n\t\t\tmapRtxSsrcConsumerId.emplace_back(\n\t\t\t  FBS::Common::CreateUint32StringDirect(builder, ssrc, consumer->id.c_str()));\n\t\t}\n\n\t\t// Add dataProducerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> dataProducerIds;\n\n\t\tfor (const auto& kv : this->mapDataProducers)\n\t\t{\n\t\t\tconst auto& dataProducerId = kv.first;\n\n\t\t\tdataProducerIds.emplace_back(builder.CreateString(dataProducerId));\n\t\t}\n\n\t\t// Add dataConsumerIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> dataConsumerIds;\n\n\t\tfor (const auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tconst auto& dataConsumerId = kv.first;\n\n\t\t\tdataConsumerIds.emplace_back(builder.CreateString(dataConsumerId));\n\t\t}\n\n\t\t// Add headerExtensionIds.\n\t\tauto recvRtpHeaderExtensions = FBS::Transport::CreateRecvRtpHeaderExtensions(\n\t\t  builder,\n\t\t  this->recvRtpHeaderExtensionIds.mid != 0u\n\t\t    ? flatbuffers::Optional<uint8_t>(this->recvRtpHeaderExtensionIds.mid)\n\t\t    : flatbuffers::nullopt,\n\t\t  this->recvRtpHeaderExtensionIds.rid != 0u\n\t\t    ? flatbuffers::Optional<uint8_t>(this->recvRtpHeaderExtensionIds.rid)\n\t\t    : flatbuffers::nullopt,\n\t\t  this->recvRtpHeaderExtensionIds.rrid != 0u\n\t\t    ? flatbuffers::Optional<uint8_t>(this->recvRtpHeaderExtensionIds.rrid)\n\t\t    : flatbuffers::nullopt,\n\t\t  this->recvRtpHeaderExtensionIds.absSendTime != 0u\n\t\t    ? flatbuffers::Optional<uint8_t>(this->recvRtpHeaderExtensionIds.absSendTime)\n\t\t    : flatbuffers::nullopt,\n\t\t  this->recvRtpHeaderExtensionIds.transportWideCc01 != 0u\n\t\t    ? flatbuffers::Optional<uint8_t>(this->recvRtpHeaderExtensionIds.transportWideCc01)\n\t\t    : flatbuffers::nullopt);\n\n\t\tauto rtpListenerOffset = this->rtpListener.FillBuffer(builder);\n\n\t\t// Add sctpParameters.\n\t\tflatbuffers::Offset<FBS::SctpParameters::SctpParameters> sctpParameters;\n\t\t// Add sctpState.\n\t\tFBS::SctpAssociation::SctpState sctpState{ FBS::SctpAssociation::SctpState::NEW };\n\t\t// Add sctpListener.\n\t\tflatbuffers::Offset<FBS::Transport::SctpListener> sctpListener;\n\n\t\tif (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation)\n\t\t{\n\t\t\t// Add sctpParameters.\n\t\t\tsctpParameters = this->sctpAssociation->FillBuffer(builder);\n\n\t\t\t// NOTE: There is never permanent FAILED state.\n\t\t\tswitch (this->sctpAssociation->GetAssociationState())\n\t\t\t{\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::NEW:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::NEW;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CONNECTING:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTING;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::SHUTTING_DOWN:\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CLOSED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsctpListener = this->sctpListener.FillBuffer(builder);\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation)\n\t\t{\n\t\t\t// Add sctpParameters.\n\t\t\tsctpParameters = this->oldSctpAssociation->FillBuffer(builder);\n\n\t\t\tswitch (this->oldSctpAssociation->GetState())\n\t\t\t{\n\t\t\t\tcase RTC::SctpAssociation::SctpState::NEW:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::NEW;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CONNECTING:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTING;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::FAILED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::FAILED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CLOSED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsctpListener = this->sctpListener.FillBuffer(builder);\n\t\t}\n\n\t\t// Add traceEventTypes.\n\t\tstd::vector<FBS::Transport::TraceEventType> traceEventTypes;\n\n\t\tif (this->traceEventTypes.probation)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Transport::TraceEventType::PROBATION);\n\t\t}\n\t\tif (this->traceEventTypes.bwe)\n\t\t{\n\t\t\ttraceEventTypes.emplace_back(FBS::Transport::TraceEventType::BWE);\n\t\t}\n\n\t\treturn FBS::Transport::CreateDumpDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  this->direct,\n\t\t  &producerIds,\n\t\t  &consumerIds,\n\t\t  &mapSsrcConsumerId,\n\t\t  &mapRtxSsrcConsumerId,\n\t\t  &dataProducerIds,\n\t\t  &dataConsumerIds,\n\t\t  recvRtpHeaderExtensions,\n\t\t  rtpListenerOffset,\n\t\t  this->maxMessageSize,\n\t\t  sctpParameters,\n\t\t  (this->sctpAssociation || this->oldSctpAssociation)\n\t\t    ? flatbuffers::Optional<FBS::SctpAssociation::SctpState>(sctpState)\n\t\t    : flatbuffers::nullopt,\n\t\t  sctpListener,\n\t\t  &traceEventTypes);\n\t}\n\n\tflatbuffers::Offset<FBS::Transport::Stats> Transport::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\t// Add sctpState.\n\t\tFBS::SctpAssociation::SctpState sctpState{ FBS::SctpAssociation::SctpState::NEW };\n\n\t\t// Add sctpState.\n\t\tif (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation)\n\t\t{\n\t\t\t// NOTE: There is never permanent FAILED state.\n\t\t\tswitch (this->sctpAssociation->GetAssociationState())\n\t\t\t{\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::NEW:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::NEW;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CONNECTING:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTING;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::SHUTTING_DOWN:\n\t\t\t\tcase RTC::SCTP::Types::AssociationState::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CLOSED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation)\n\t\t{\n\t\t\tswitch (this->oldSctpAssociation->GetState())\n\t\t\t{\n\t\t\t\tcase RTC::SctpAssociation::SctpState::NEW:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::NEW;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CONNECTING:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTING;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CONNECTED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CONNECTED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::FAILED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::FAILED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::SctpAssociation::SctpState::CLOSED:\n\t\t\t\t{\n\t\t\t\t\tsctpState = FBS::SctpAssociation::SctpState::CLOSED;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn FBS::Transport::CreateStatsDirect(\n\t\t  builder,\n\t\t  // transportId.\n\t\t  this->id.c_str(),\n\t\t  // timestamp.\n\t\t  nowMs,\n\t\t  // sctpState.\n\t\t  (this->sctpAssociation || this->oldSctpAssociation)\n\t\t    ? flatbuffers::Optional<FBS::SctpAssociation::SctpState>(sctpState)\n\t\t    : flatbuffers::nullopt,\n\t\t  // bytesReceived.\n\t\t  this->recvTransmission.GetBytes(),\n\t\t  // recvBitrate.\n\t\t  this->recvTransmission.GetRate(nowMs),\n\t\t  // bytesSent.\n\t\t  this->sendTransmission.GetBytes(),\n\t\t  // sendBitrate.\n\t\t  this->sendTransmission.GetRate(nowMs),\n\t\t  // rtpBytesReceived.\n\t\t  this->recvRtpTransmission.GetBytes(),\n\t\t  // rtpRecvBitrate.\n\t\t  this->recvRtpTransmission.GetBitrate(nowMs),\n\t\t  // rtpBytesSent.\n\t\t  this->sendRtpTransmission.GetBytes(),\n\t\t  // rtpSendBitrate.\n\t\t  this->sendRtpTransmission.GetBitrate(nowMs),\n\t\t  // rtxBytesReceived.\n\t\t  this->recvRtxTransmission.GetBytes(),\n\t\t  // rtxRecvBitrate.\n\t\t  this->recvRtxTransmission.GetBitrate(nowMs),\n\t\t  // rtxBytesSent.\n\t\t  this->sendRtxTransmission.GetBytes(),\n\t\t  // rtxSendBitrate.\n\t\t  this->sendRtxTransmission.GetBitrate(nowMs),\n\t\t  // probationBytesSent.\n\t\t  this->sendProbationTransmission.GetBytes(),\n\t\t  // probationSendBitrate.\n\t\t  this->sendProbationTransmission.GetBitrate(nowMs),\n\t\t  // availableOutgoingBitrate.\n\t\t  this->tccClient ? flatbuffers::Optional<uint32_t>(this->tccClient->GetAvailableBitrate())\n\t\t                  : flatbuffers::nullopt,\n\t\t  // availableIncomingBitrate.\n\t\t  this->tccServer ? flatbuffers::Optional<uint32_t>(this->tccServer->GetAvailableBitrate())\n\t\t                  : flatbuffers::nullopt,\n\t\t  // maxIncomingBitrate.\n\t\t  this->maxIncomingBitrate ? flatbuffers::Optional<uint32_t>(this->maxIncomingBitrate)\n\t\t                           : flatbuffers::nullopt,\n\t\t  // maxOutgoingBitrate.\n\t\t  this->maxOutgoingBitrate ? flatbuffers::Optional<uint32_t>(this->maxOutgoingBitrate)\n\t\t                           : flatbuffers::nullopt,\n\t\t  // minOutgoingBitrate.\n\t\t  this->minOutgoingBitrate ? flatbuffers::Optional<uint32_t>(this->minOutgoingBitrate)\n\t\t                           : flatbuffers::nullopt,\n\t\t  // rtpPacketLossReceived.\n\t\t  this->tccServer ? flatbuffers::Optional<double>(this->tccServer->GetPacketLoss())\n\t\t                  : flatbuffers::nullopt,\n\t\t  // rtpPacketLossSent.\n\t\t  this->tccClient ? flatbuffers::Optional<double>(this->tccClient->GetPacketLoss())\n\t\t                  : flatbuffers::nullopt);\n\t}\n\n\tvoid Transport::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_SET_MAX_INCOMING_BITRATE:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::SetMaxIncomingBitrateRequest>();\n\n\t\t\t\tthis->maxIncomingBitrate = body->maxIncomingBitrate();\n\n\t\t\t\tMS_DEBUG_TAG(bwe, \"maximum incoming bitrate set to %\" PRIu32, this->maxIncomingBitrate);\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tif (this->tccServer)\n\t\t\t\t{\n\t\t\t\t\tthis->tccServer->SetMaxIncomingBitrate(this->maxIncomingBitrate);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_SET_MAX_OUTGOING_BITRATE:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::SetMaxOutgoingBitrateRequest>();\n\t\t\t\tconst uint32_t bitrate = body->maxOutgoingBitrate();\n\n\t\t\t\tif (bitrate > 0u && bitrate < RTC::TransportCongestionControlMinOutgoingBitrate)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"bitrate must be >= %\" PRIu32 \" or 0 (unlimited)\",\n\t\t\t\t\t  RTC::TransportCongestionControlMinOutgoingBitrate);\n\t\t\t\t}\n\t\t\t\telse if (bitrate > 0u && bitrate < this->minOutgoingBitrate)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"bitrate must be >= current min outgoing bitrate (%\" PRIu32 \") or 0 (unlimited)\",\n\t\t\t\t\t  this->minOutgoingBitrate);\n\t\t\t\t}\n\n\t\t\t\tif (this->tccClient)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: This may throw so don't update things before calling this\n\t\t\t\t\t// method.\n\t\t\t\t\tthis->tccClient->SetMaxOutgoingBitrate(bitrate);\n\t\t\t\t\tthis->maxOutgoingBitrate = bitrate;\n\n\t\t\t\t\tMS_DEBUG_TAG(bwe, \"maximum outgoing bitrate set to %\" PRIu32, this->maxOutgoingBitrate);\n\n\t\t\t\t\tComputeOutgoingDesiredBitrate();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->maxOutgoingBitrate = bitrate;\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_SET_MIN_OUTGOING_BITRATE:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::SetMinOutgoingBitrateRequest>();\n\t\t\t\tconst uint32_t bitrate = body->minOutgoingBitrate();\n\n\t\t\t\tif (bitrate > 0u && bitrate < RTC::TransportCongestionControlMinOutgoingBitrate)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"bitrate must be >= %\" PRIu32 \" or 0 (unlimited)\",\n\t\t\t\t\t  RTC::TransportCongestionControlMinOutgoingBitrate);\n\t\t\t\t}\n\t\t\t\telse if (bitrate > 0u && this->maxOutgoingBitrate > 0 && bitrate > this->maxOutgoingBitrate)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t  \"bitrate must be <= current max outgoing bitrate (%\" PRIu32 \") or 0 (unlimited)\",\n\t\t\t\t\t  this->maxOutgoingBitrate);\n\t\t\t\t}\n\n\t\t\t\tif (this->tccClient)\n\t\t\t\t{\n\t\t\t\t\t// NOTE: This may throw so don't update things before calling this\n\t\t\t\t\t// method.\n\t\t\t\t\tthis->tccClient->SetMinOutgoingBitrate(bitrate);\n\t\t\t\t\tthis->minOutgoingBitrate = bitrate;\n\n\t\t\t\t\tMS_DEBUG_TAG(bwe, \"minimum outgoing bitrate set to %\" PRIu32, this->minOutgoingBitrate);\n\n\t\t\t\t\tComputeOutgoingDesiredBitrate();\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tthis->minOutgoingBitrate = bitrate;\n\t\t\t\t}\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_PRODUCE:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::ProduceRequest>();\n\t\t\t\tauto producerId  = body->producerId()->str();\n\n\t\t\t\tif (this->mapProducers.find(producerId) != this->mapProducers.end())\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"a Producer with same producerId already exists\");\n\t\t\t\t}\n\n\t\t\t\t// This may throw.\n\t\t\t\tauto* producer = new RTC::Producer(this->shared, producerId, this, body);\n\n\t\t\t\t// Insert the Producer into the RtpListener.\n\t\t\t\t// This may throw. If so, delete the Producer and throw.\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->rtpListener.AddProducer(producer);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tdelete producer;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\t// This may throw if a Producer with same id already exists.\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnTransportNewProducer(this, producer);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tthis->rtpListener.RemoveProducer(producer);\n\n\t\t\t\t\tdelete producer;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapProducers[producerId] = producer;\n\n\t\t\t\tMS_DEBUG_DEV(\"Producer created [producerId:%s]\", producerId.c_str());\n\n\t\t\t\t// Take the transport related RTP header extensions of the Producer and\n\t\t\t\t// add them to the Transport.\n\t\t\t\t// NOTE: Producer::GetRtpHeaderExtensionIds() returns the original\n\t\t\t\t// header extension ids of the Producer (and not their mapped values).\n\t\t\t\tconst auto& producerRtpHeaderExtensionIds = producer->GetRtpHeaderExtensionIds();\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.mid != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.mid = producerRtpHeaderExtensionIds.mid;\n\t\t\t\t}\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.rid != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.rid = producerRtpHeaderExtensionIds.rid;\n\t\t\t\t}\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.rrid != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.rrid = producerRtpHeaderExtensionIds.rrid;\n\t\t\t\t}\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.absSendTime != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.absSendTime = producerRtpHeaderExtensionIds.absSendTime;\n\t\t\t\t}\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.transportWideCc01 != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.transportWideCc01 =\n\t\t\t\t\t  producerRtpHeaderExtensionIds.transportWideCc01;\n\t\t\t\t}\n\n\t\t\t\tif (producerRtpHeaderExtensionIds.dependencyDescriptor != 0u)\n\t\t\t\t{\n\t\t\t\t\tthis->recvRtpHeaderExtensionIds.dependencyDescriptor =\n\t\t\t\t\t  producerRtpHeaderExtensionIds.dependencyDescriptor;\n\t\t\t\t}\n\n\t\t\t\t// Create status response.\n\t\t\t\tauto responseOffset = FBS::Transport::CreateProduceResponse(\n\t\t\t\t  request->GetBufferBuilder(), FBS::RtpParameters::Type(producer->GetType()));\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Transport_ProduceResponse, responseOffset);\n\n\t\t\t\t// Check if TransportCongestionControlServer or REMB server must be\n\t\t\t\t// created.\n\t\t\t\tconst auto& rtpHeaderExtensionIds = producer->GetRtpHeaderExtensionIds();\n\t\t\t\tconst auto& codecs                = producer->GetRtpParameters().codecs;\n\n\t\t\t\t// Set TransportCongestionControlServer.\n\t\t\t\tif (!this->tccServer)\n\t\t\t\t{\n\t\t\t\t\tbool createTccServer{ false };\n\t\t\t\t\tRTC::BweType bweType;\n\n\t\t\t\t\t// Use transport-cc if:\n\t\t\t\t\t// - there is transport-wide-cc-01 RTP header extension, and\n\t\t\t\t\t// - there is \"transport-cc\" in codecs RTCP feedback.\n\t\t\t\t\t//\n\t\t\t\t\tif (\n\t\t\t\t\t  rtpHeaderExtensionIds.transportWideCc01 != 0u &&\n\t\t\t\t\t  std::any_of(\n\t\t\t\t\t    codecs.begin(),\n\t\t\t\t\t    codecs.end(),\n\t\t\t\t\t    [](const RTC::RtpCodecParameters& codec)\n\t\t\t\t\t    {\n\t\t\t\t\t\t    return std::any_of(\n\t\t\t\t\t\t      codec.rtcpFeedback.begin(),\n\t\t\t\t\t\t      codec.rtcpFeedback.end(),\n\t\t\t\t\t\t      [](const RTC::RtcpFeedback& fb)\n\t\t\t\t\t\t      {\n\t\t\t\t\t\t\t      return fb.type == \"transport-cc\";\n\t\t\t\t\t\t      });\n\t\t\t\t\t    }))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(bwe, \"enabling TransportCongestionControlServer with transport-cc\");\n\n\t\t\t\t\t\tcreateTccServer = true;\n\t\t\t\t\t\tbweType         = RTC::BweType::TRANSPORT_CC;\n\t\t\t\t\t}\n\t\t\t\t\t// Use REMB if:\n\t\t\t\t\t// - there is abs-send-time RTP header extension, and\n\t\t\t\t\t// - there is \"remb\" in codecs RTCP feedback.\n\t\t\t\t\t//\n\t\t\t\t\telse if (\n\t\t\t\t\t  rtpHeaderExtensionIds.absSendTime != 0u && std::any_of(\n\t\t\t\t\t                                               codecs.begin(),\n\t\t\t\t\t                                               codecs.end(),\n\t\t\t\t\t                                               [](const RTC::RtpCodecParameters& codec)\n\t\t\t\t\t                                               {\n\t\t\t\t\t\t                                               return std::any_of(\n\t\t\t\t\t\t                                                 codec.rtcpFeedback.begin(),\n\t\t\t\t\t\t                                                 codec.rtcpFeedback.end(),\n\t\t\t\t\t\t                                                 [](const RTC::RtcpFeedback& fb)\n\t\t\t\t\t\t                                                 {\n\t\t\t\t\t\t\t                                                 return fb.type == \"goog-remb\";\n\t\t\t\t\t\t                                                 });\n\t\t\t\t\t                                               }))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(bwe, \"enabling TransportCongestionControlServer with REMB\");\n\n\t\t\t\t\t\tcreateTccServer = true;\n\t\t\t\t\t\tbweType         = RTC::BweType::REMB;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (createTccServer)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->tccServer = std::make_shared<RTC::TransportCongestionControlServer>(\n\t\t\t\t\t\t  this, this->shared, bweType, RTC::Consts::RtcpPacketMaxSize);\n\n\t\t\t\t\t\tif (this->maxIncomingBitrate != 0u)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->tccServer->SetMaxIncomingBitrate(this->maxIncomingBitrate);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (IsConnected())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->tccServer->TransportConnected();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CONSUME:\n\t\t\t{\n\t\t\t\tconst auto* body             = request->data->body_as<FBS::Transport::ConsumeRequest>();\n\t\t\t\tconst std::string producerId = body->producerId()->str();\n\t\t\t\tconst std::string consumerId = body->consumerId()->str();\n\n\t\t\t\tif (this->mapConsumers.find(consumerId) != this->mapConsumers.end())\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"a Consumer with same consumerId already exists\");\n\t\t\t\t}\n\n\t\t\t\tauto type = RTC::RtpParameters::Type(body->type());\n\n\t\t\t\tRTC::Consumer* consumer{ nullptr };\n\n\t\t\t\tswitch (type)\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RtpParameters::Type::SIMPLE:\n\t\t\t\t\t{\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tconsumer = new RTC::SimpleConsumer(this->shared, consumerId, producerId, this, body);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpParameters::Type::SIMULCAST:\n\t\t\t\t\t{\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tconsumer = new RTC::SimulcastConsumer(this->shared, consumerId, producerId, this, body);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpParameters::Type::SVC:\n\t\t\t\t\t{\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tconsumer = new RTC::SvcConsumer(this->shared, consumerId, producerId, this, body);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RtpParameters::Type::PIPE:\n\t\t\t\t\t{\n\t\t\t\t\t\t// This may throw.\n\t\t\t\t\t\tconsumer = new RTC::PipeConsumer(this->shared, consumerId, producerId, this, body);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\t// This may throw if no Producer is found.\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnTransportNewConsumer(this, consumer, producerId);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tdelete consumer;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\t// Insert into the maps.\n\t\t\t\tthis->mapConsumers[consumerId] = consumer;\n\n\t\t\t\tfor (auto ssrc : consumer->GetMediaSsrcs())\n\t\t\t\t{\n\t\t\t\t\tthis->mapSsrcConsumer[ssrc] = consumer;\n\t\t\t\t}\n\n\t\t\t\tfor (auto ssrc : consumer->GetRtxSsrcs())\n\t\t\t\t{\n\t\t\t\t\tthis->mapRtxSsrcConsumer[ssrc] = consumer;\n\t\t\t\t}\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"Consumer created [consumerId:%s, producerId:%s]\", consumerId.c_str(), producerId.c_str());\n\n\t\t\t\tflatbuffers::Offset<FBS::Consumer::ConsumerLayers> preferredLayersOffset;\n\t\t\t\tauto preferredLayers = consumer->GetPreferredLayers();\n\n\t\t\t\tif (preferredLayers.spatial > -1 && preferredLayers.temporal > -1)\n\t\t\t\t{\n\t\t\t\t\tconst flatbuffers::Optional<int16_t> preferredTemporalLayer{ preferredLayers.temporal };\n\t\t\t\t\tpreferredLayersOffset = FBS::Consumer::CreateConsumerLayers(\n\t\t\t\t\t  request->GetBufferBuilder(), preferredLayers.spatial, preferredTemporalLayer);\n\t\t\t\t}\n\n\t\t\t\tauto scoreOffset    = consumer->FillBufferScore(request->GetBufferBuilder());\n\t\t\t\tauto responseOffset = FBS::Transport::CreateConsumeResponse(\n\t\t\t\t  request->GetBufferBuilder(),\n\t\t\t\t  consumer->IsPaused(),\n\t\t\t\t  consumer->IsProducerPaused(),\n\t\t\t\t  scoreOffset,\n\t\t\t\t  preferredLayersOffset);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Transport_ConsumeResponse, responseOffset);\n\n\t\t\t\t// Check if Transport Congestion Control client must be created.\n\t\t\t\tconst auto& rtpHeaderExtensionIds = consumer->GetRtpHeaderExtensionIds();\n\t\t\t\tconst auto& codecs                = consumer->GetRtpParameters().codecs;\n\n\t\t\t\t// Set TransportCongestionControlClient.\n\t\t\t\tif (!this->tccClient)\n\t\t\t\t{\n\t\t\t\t\tbool createTccClient{ false };\n\t\t\t\t\tRTC::BweType bweType;\n\n\t\t\t\t\t// Use transport-cc if:\n\t\t\t\t\t// - it's a video Consumer, and\n\t\t\t\t\t// - there is transport-wide-cc-01 RTP header extension, and\n\t\t\t\t\t// - there is \"transport-cc\" in codecs RTCP feedback.\n\t\t\t\t\t//\n\t\t\t\t\tif (\n\t\t\t\t\t  consumer->GetKind() == RTC::Media::Kind::VIDEO &&\n\t\t\t\t\t  rtpHeaderExtensionIds.transportWideCc01 != 0u &&\n\t\t\t\t\t  std::any_of(\n\t\t\t\t\t    codecs.begin(),\n\t\t\t\t\t    codecs.end(),\n\t\t\t\t\t    [](const RTC::RtpCodecParameters& codec)\n\t\t\t\t\t    {\n\t\t\t\t\t\t    return std::any_of(\n\t\t\t\t\t\t      codec.rtcpFeedback.begin(),\n\t\t\t\t\t\t      codec.rtcpFeedback.end(),\n\t\t\t\t\t\t      [](const RTC::RtcpFeedback& fb)\n\t\t\t\t\t\t      {\n\t\t\t\t\t\t\t      return fb.type == \"transport-cc\";\n\t\t\t\t\t\t      });\n\t\t\t\t\t    }))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(bwe, \"enabling TransportCongestionControlClient with transport-cc\");\n\n\t\t\t\t\t\tcreateTccClient = true;\n\t\t\t\t\t\tbweType         = RTC::BweType::TRANSPORT_CC;\n\t\t\t\t\t}\n\t\t\t\t\t// Use REMB if:\n\t\t\t\t\t// - it's a video Consumer, and\n\t\t\t\t\t// - there is abs-send-time RTP header extension, and\n\t\t\t\t\t// - there is \"remb\" in codecs RTCP feedback.\n\t\t\t\t\t//\n\t\t\t\t\telse if (\n\t\t\t\t\t  consumer->GetKind() == RTC::Media::Kind::VIDEO &&\n\t\t\t\t\t  rtpHeaderExtensionIds.absSendTime != 0u &&\n\t\t\t\t\t  std::any_of(\n\t\t\t\t\t    codecs.begin(),\n\t\t\t\t\t    codecs.end(),\n\t\t\t\t\t    [](const RTC::RtpCodecParameters& codec)\n\t\t\t\t\t    {\n\t\t\t\t\t\t    return std::any_of(\n\t\t\t\t\t\t      codec.rtcpFeedback.begin(),\n\t\t\t\t\t\t      codec.rtcpFeedback.end(),\n\t\t\t\t\t\t      [](const RTC::RtcpFeedback& fb)\n\t\t\t\t\t\t      {\n\t\t\t\t\t\t\t      return fb.type == \"goog-remb\";\n\t\t\t\t\t\t      });\n\t\t\t\t\t    }))\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(bwe, \"enabling TransportCongestionControlClient with REMB\");\n\n\t\t\t\t\t\tcreateTccClient = true;\n\t\t\t\t\t\tbweType         = RTC::BweType::REMB;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (createTccClient)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Tell all the Consumers that we are gonna manage their bitrate.\n\t\t\t\t\t\tfor (auto& kv : this->mapConsumers)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto* consumer = kv.second;\n\n\t\t\t\t\t\t\tconsumer->SetExternallyManagedBitrate();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tthis->tccClient = std::make_shared<RTC::TransportCongestionControlClient>(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  this->shared,\n\t\t\t\t\t\t  bweType,\n\t\t\t\t\t\t  this->initialAvailableOutgoingBitrate,\n\t\t\t\t\t\t  this->maxOutgoingBitrate,\n\t\t\t\t\t\t  this->minOutgoingBitrate);\n\n\t\t\t\t\t\tif (IsConnected())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->tccClient->TransportConnected();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If applicable, tell the new Consumer that we are gonna manage its\n\t\t\t\t// bitrate.\n\t\t\t\tif (this->tccClient)\n\t\t\t\t{\n\t\t\t\t\tconsumer->SetExternallyManagedBitrate();\n\t\t\t\t}\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t\t\t// Create SenderBandwidthEstimator if:\n\t\t\t\t// - not already created,\n\t\t\t\t// - it's a video Consumer, and\n\t\t\t\t// - there is transport-wide-cc-01 RTP header extension, and\n\t\t\t\t// - there is \"transport-cc\" in codecs RTCP feedback.\n\t\t\t\t//\n\t\t\t\tif (\n\t\t\t\t  !this->senderBwe && consumer->GetKind() == RTC::Media::Kind::VIDEO &&\n\t\t\t\t  rtpHeaderExtensionIds.transportWideCc01 != 0u &&\n\t\t\t\t  std::any_of(\n\t\t\t\t    codecs.begin(),\n\t\t\t\t    codecs.end(),\n\t\t\t\t    [](const RTC::RtpCodecParameters& codec)\n\t\t\t\t    {\n\t\t\t\t\t    return std::any_of(\n\t\t\t\t\t      codec.rtcpFeedback.begin(),\n\t\t\t\t\t      codec.rtcpFeedback.end(),\n\t\t\t\t\t      [](const RTC::RtcpFeedback& fb)\n\t\t\t\t\t      {\n\t\t\t\t\t\t      return fb.type == \"transport-cc\";\n\t\t\t\t\t      });\n\t\t\t\t    }))\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(bwe, \"enabling SenderBandwidthEstimator\");\n\n\t\t\t\t\t// Tell all the Consumers that we are gonna manage their bitrate.\n\t\t\t\t\tfor (auto& kv : this->mapConsumers)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* consumer = kv.second;\n\n\t\t\t\t\t\tconsumer->SetExternallyManagedBitrate();\n\t\t\t\t\t};\n\n\t\t\t\t\tthis->senderBwe = std::make_shared<RTC::SenderBandwidthEstimator>(\n\t\t\t\t\t  this, this->shared, this->initialAvailableOutgoingBitrate);\n\n\t\t\t\t\tif (IsConnected())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->senderBwe->TransportConnected();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If applicable, tell the new Consumer that we are gonna manage its\n\t\t\t\t// bitrate.\n\t\t\t\tif (this->senderBwe)\n\t\t\t\t{\n\t\t\t\t\tconsumer->SetExternallyManagedBitrate();\n\t\t\t\t}\n#endif\n\n\t\t\t\tif (IsConnected())\n\t\t\t\t{\n\t\t\t\t\tconsumer->TransportConnected();\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_PRODUCE_DATA:\n\t\t\t{\n\t\t\t\t// Early check. The Transport must support SCTP or be direct.\n\t\t\t\tif (\n\t\t\t\t  ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t   (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) &&\n\t\t\t\t  !this->direct)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"SCTP not enabled and not a direct Transport\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::ProduceDataRequest>();\n\n\t\t\t\tauto dataProducerId = body->dataProducerId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoDataProducer(dataProducerId);\n\n\t\t\t\t// This may throw.\n\t\t\t\tauto* dataProducer =\n\t\t\t\t  new RTC::DataProducer(this->shared, dataProducerId, this->maxMessageSize, this, body);\n\n\t\t\t\t// Verify the type of the DataProducer.\n\t\t\t\tswitch (dataProducer->GetType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::DataProducer::Type::SCTP:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t  (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t\t\t  (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete dataProducer;\n\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t\t  \"cannot create a DataProducer of type 'sctp', SCTP not enabled in this Transport\");\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::DataProducer::Type::DIRECT:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!this->direct)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete dataProducer;\n\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t\t  \"cannot create a DataProducer of type 'direct', not a direct Transport\");\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (dataProducer->GetType() == RTC::DataProducer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\t// Insert the DataProducer into the SctpListener.\n\t\t\t\t\t// This may throw. If so, delete the DataProducer and throw.\n\t\t\t\t\ttry\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->sctpListener.AddDataProducer(dataProducer);\n\t\t\t\t\t}\n\t\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t\t{\n\t\t\t\t\t\tdelete dataProducer;\n\n\t\t\t\t\t\tthrow;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\t// This may throw if a DataProducer with same id already exists.\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnTransportNewDataProducer(this, dataProducer);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tif (dataProducer->GetType() == RTC::DataProducer::Type::SCTP)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->sctpListener.RemoveDataProducer(dataProducer);\n\t\t\t\t\t}\n\n\t\t\t\t\tdelete dataProducer;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\t// Insert into the map.\n\t\t\t\tthis->mapDataProducers[dataProducerId] = dataProducer;\n\n\t\t\t\tMS_DEBUG_DEV(\"DataProducer created [dataProducerId:%s]\", dataProducerId.c_str());\n\n\t\t\t\tauto dumpOffset = dataProducer->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataProducer_DumpResponse, dumpOffset);\n\n\t\t\t\tif (dataProducer->GetType() == RTC::DataProducer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\t// Tell to the SCTP association.\n\t\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->sctpAssociation->MayConnect();\n\t\t\t\t\t}\n\t\t\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->oldSctpAssociation->HandleDataProducer(dataProducer);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CONSUME_DATA:\n\t\t\t{\n\t\t\t\t// Early check. The Transport must support SCTP or be direct.\n\t\t\t\tif (\n\t\t\t\t  ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t   (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) &&\n\t\t\t\t  !this->direct)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"SCTP not enabled and not a direct Transport\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::ConsumeDataRequest>();\n\n\t\t\t\tauto dataProducerId = body->dataProducerId()->str();\n\t\t\t\tauto dataConsumerId = body->dataConsumerId()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tCheckNoDataConsumer(dataConsumerId);\n\n\t\t\t\t// This may throw.\n\t\t\t\tauto* dataConsumer = new RTC::DataConsumer(\n\t\t\t\t  this->shared, dataConsumerId, dataProducerId, this, body, this->maxMessageSize);\n\n\t\t\t\t// Verify the type of the DataConsumer.\n\t\t\t\tswitch (dataConsumer->GetType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::DataConsumer::Type::SCTP:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t  (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t\t\t  (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete dataConsumer;\n\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t\t  \"cannot create a DataConsumer of type 'sctp', SCTP not enabled in this Transport\");\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::DataConsumer::Type::DIRECT:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!this->direct)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdelete dataConsumer;\n\n\t\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\n\t\t\t\t\t\t\t  \"cannot create a DataConsumer of type 'direct', not a direct Transport\");\n\t\t\t\t\t\t\t;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\t// This may throw if no DataProducer is found.\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tthis->listener->OnTransportNewDataConsumer(this, dataConsumer, dataProducerId);\n\t\t\t\t}\n\t\t\t\tcatch (const MediaSoupError& error)\n\t\t\t\t{\n\t\t\t\t\tdelete dataConsumer;\n\n\t\t\t\t\tthrow;\n\t\t\t\t}\n\n\t\t\t\t// Insert into the maps.\n\t\t\t\tthis->mapDataConsumers[dataConsumerId] = dataConsumer;\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"DataConsumer created [dataConsumerId:%s, dataProducerId:%s]\",\n\t\t\t\t  dataConsumerId.c_str(),\n\t\t\t\t  dataProducerId.c_str());\n\n\t\t\t\tauto dumpOffset = dataConsumer->FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::DataConsumer_DumpResponse, dumpOffset);\n\n\t\t\t\tif (IsConnected())\n\t\t\t\t{\n\t\t\t\t\tdataConsumer->TransportConnected();\n\t\t\t\t}\n\n\t\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->sctpAssociation->GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Tell to the DataConsumer.\n\t\t\t\t\t\t\tdataConsumer->SctpAssociationConnected();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Tell to the SCTP association.\n\t\t\t\t\t\tthis->sctpAssociation->MayConnect();\n\t\t\t\t\t}\n\t\t\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tif (this->oldSctpAssociation->GetState() == RTC::SctpAssociation::SctpState::CONNECTED)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Tell to the DataConsumer.\n\t\t\t\t\t\t\tdataConsumer->SctpAssociationConnected();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Tell to the SCTP association.\n\t\t\t\t\t\tthis->oldSctpAssociation->HandleDataConsumer(dataConsumer);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_ENABLE_TRACE_EVENT:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::EnableTraceEventRequest>();\n\n\t\t\t\t// Reset traceEventTypes.\n\t\t\t\tstruct TraceEventTypes newTraceEventTypes;\n\n\t\t\t\tfor (const auto& type : *body->events())\n\t\t\t\t{\n\t\t\t\t\tswitch (type)\n\t\t\t\t\t{\n\t\t\t\t\t\tcase FBS::Transport::TraceEventType::PROBATION:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.probation = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase FBS::Transport::TraceEventType::BWE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tnewTraceEventTypes.bwe = true;\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->traceEventTypes = newTraceEventTypes;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CLOSE_PRODUCER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::CloseProducerRequest>();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::Producer* producer = GetProducerById(body->producerId()->str());\n\n\t\t\t\t// Remove it from the RtpListener.\n\t\t\t\tthis->rtpListener.RemoveProducer(producer);\n\n\t\t\t\t// Remove it from the map.\n\t\t\t\tthis->mapProducers.erase(producer->id);\n\n\t\t\t\t// Tell the child class to clear associated SSRCs.\n\t\t\t\tfor (const auto& kv : producer->GetRtpStreams())\n\t\t\t\t{\n\t\t\t\t\tauto* rtpStream = kv.first;\n\n\t\t\t\t\tRecvStreamClosed(rtpStream->GetSsrc());\n\n\t\t\t\t\tif (rtpStream->HasRtx())\n\t\t\t\t\t{\n\t\t\t\t\t\tRecvStreamClosed(rtpStream->GetRtxSsrc());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnTransportProducerClosed(this, producer);\n\n\t\t\t\tMS_DEBUG_DEV(\"Producer closed [producerId:%s]\", producer->id.c_str());\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete producer;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CLOSE_CONSUMER:\n\t\t\t{\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::CloseConsumerRequest>();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::Consumer* consumer = GetConsumerById(body->consumerId()->str());\n\n\t\t\t\t// Remove it from the maps.\n\t\t\t\tthis->mapConsumers.erase(consumer->id);\n\n\t\t\t\tfor (auto ssrc : consumer->GetMediaSsrcs())\n\t\t\t\t{\n\t\t\t\t\tthis->mapSsrcConsumer.erase(ssrc);\n\n\t\t\t\t\t// Tell the child class to clear associated SSRCs.\n\t\t\t\t\tSendStreamClosed(ssrc);\n\t\t\t\t}\n\n\t\t\t\tfor (auto ssrc : consumer->GetRtxSsrcs())\n\t\t\t\t{\n\t\t\t\t\tthis->mapRtxSsrcConsumer.erase(ssrc);\n\n\t\t\t\t\t// Tell the child class to clear associated SSRCs.\n\t\t\t\t\tSendStreamClosed(ssrc);\n\t\t\t\t}\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnTransportConsumerClosed(this, consumer);\n\n\t\t\t\tMS_DEBUG_DEV(\"Consumer closed [consumerId:%s]\", consumer->id.c_str());\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete consumer;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\t// This may be the latest active Consumer with BWE. If so we have to stop\n\t\t\t\t// probation.\n\t\t\t\tif (this->tccClient)\n\t\t\t\t{\n\t\t\t\t\tComputeOutgoingDesiredBitrate(/*forceBitrate*/ true);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CLOSE_DATAPRODUCER:\n\t\t\t{\n\t\t\t\tif (\n\t\t\t\t  ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t   (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) &&\n\t\t\t\t  !this->direct)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"cannot close DataProducer, SCTP not enabled and not a direct Transport\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::CloseDataProducerRequest>();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::DataProducer* dataProducer = GetDataProducerById(body->dataProducerId()->str());\n\n\t\t\t\tif (dataProducer->GetType() == RTC::DataProducer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\t// Remove it from the SctpListener.\n\t\t\t\t\tthis->sctpListener.RemoveDataProducer(dataProducer);\n\t\t\t\t}\n\n\t\t\t\t// Remove it from the map.\n\t\t\t\tthis->mapDataProducers.erase(dataProducer->id);\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnTransportDataProducerClosed(this, dataProducer);\n\n\t\t\t\tMS_DEBUG_DEV(\"DataProducer closed [dataProducerId:%s]\", dataProducer->id.c_str());\n\n\t\t\t\tif (dataProducer->GetType() == RTC::DataProducer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t\t{\n\t\t\t\t\t\t// TODO: SCTP\n\t\t\t\t\t}\n\t\t\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Tell the SctpAssociation so it can reset the SCTP stream.\n\t\t\t\t\t\tthis->oldSctpAssociation->DataProducerClosed(dataProducer);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete dataProducer;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_CLOSE_DATACONSUMER:\n\t\t\t{\n\t\t\t\tif (\n\t\t\t\t  ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t\t\t   (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) &&\n\t\t\t\t  !this->direct)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"cannot close DataConsumer, SCTP not enabled and not a direct Transport\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::Transport::CloseDataConsumerRequest>();\n\n\t\t\t\t// This may throw.\n\t\t\t\tRTC::DataConsumer* dataConsumer = GetDataConsumerById(body->dataConsumerId()->str());\n\n\t\t\t\t// Remove it from the maps.\n\t\t\t\tthis->mapDataConsumers.erase(dataConsumer->id);\n\n\t\t\t\t// Notify the listener.\n\t\t\t\tthis->listener->OnTransportDataConsumerClosed(this, dataConsumer);\n\n\t\t\t\tMS_DEBUG_DEV(\"DataConsumer closed [dataConsumerId:%s]\", dataConsumer->id.c_str());\n\n\t\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t\t{\n\t\t\t\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t\t\t\t{\n\t\t\t\t\t\t// TODO: SCTP\n\t\t\t\t\t}\n\t\t\t\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t// Tell the SctpAssociation so it can reset the SCTP stream.\n\t\t\t\t\t\tthis->oldSctpAssociation->DataConsumerClosed(dataConsumer);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Delete it.\n\t\t\t\tdelete dataConsumer;\n\n\t\t\t\trequest->Accept();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\n\t\treturn;\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ERROR(\"unknown method\");\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Transport::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (notification->event)\n\t\t{\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ERROR(\"unknown event '%s'\", notification->eventCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Transport::SetDestroying()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tif (this->sctpAssociation)\n\t\t\t{\n\t\t\t\t// NOTE: We don't invoke `Shutdown()` but `Close()` in the SCTP Association\n\t\t\t\t// because at this point we are closing everything and we won't have any\n\t\t\t\t// chance to complete the SCTP SHUTDOWN + SHUTDOWN_ACK + SHUTDOWN_COMPLETE\n\t\t\t\t// dance, so we invoke `Close()` which just sends a SCTP ABORT.\n\t\t\t\tthis->sctpAssociation->Close();\n\t\t\t}\n\t\t}\n\n\t\tthis->isDestroying = true;\n\t}\n\n\tvoid Transport::Connected()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all Consumers.\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\n\t\t\tconsumer->TransportConnected();\n\t\t}\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tdataConsumer->TransportConnected();\n\t\t}\n\n\t\t// Tell the SctpAssociation.\n\t\tif (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation)\n\t\t{\n\t\t\tthis->sctpAssociation->MayConnect();\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation)\n\t\t{\n\t\t\tthis->oldSctpAssociation->TransportConnected();\n\t\t}\n\n\t\t// Start the RTCP timer.\n\t\tthis->rtcpTimer->Start(static_cast<uint64_t>(RTC::RTCP::MaxVideoIntervalMs / 2));\n\n\t\t// Tell the TransportCongestionControlClient.\n\t\tif (this->tccClient)\n\t\t{\n\t\t\tthis->tccClient->TransportConnected();\n\t\t}\n\n\t\t// Tell the TransportCongestionControlServer.\n\t\tif (this->tccServer)\n\t\t{\n\t\t\tthis->tccServer->TransportConnected();\n\t\t}\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t// Tell the SenderBandwidthEstimator.\n\t\tif (this->senderBwe)\n\t\t{\n\t\t\tthis->senderBwe->TransportConnected();\n\t\t}\n#endif\n\t}\n\n\tvoid Transport::Disconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all Consumers.\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\n\t\t\tconsumer->TransportDisconnected();\n\t\t}\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tdataConsumer->TransportDisconnected();\n\t\t}\n\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\t// Tell the SctpAssociation.\n\t\tif (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation)\n\t\t{\n\t\t\tthis->oldSctpAssociation->TransportDisconnected();\n\t\t}\n\n\t\t// Stop the RTCP timer.\n\t\tthis->rtcpTimer->Stop();\n\n\t\t// Tell the TransportCongestionControlClient.\n\t\tif (this->tccClient)\n\t\t{\n\t\t\tthis->tccClient->TransportDisconnected();\n\t\t}\n\n\t\t// Tell the TransportCongestionControlServer.\n\t\tif (this->tccServer)\n\t\t{\n\t\t\tthis->tccServer->TransportDisconnected();\n\t\t}\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t// Tell the SenderBandwidthEstimator.\n\t\tif (this->senderBwe)\n\t\t{\n\t\t\tthis->senderBwe->TransportDisconnected();\n\t\t}\n#endif\n\t}\n\n\tvoid Transport::ReceiveRtpPacket(RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.recvTransportId = this->id;\n#endif\n\n\t\t// Apply the Transport RTP header extension ids so the RTP listener can use\n\t\t// them.\n\t\tpacket->AssignExtensionIds(this->recvRtpHeaderExtensionIds);\n\n\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\t// Feed the TransportCongestionControlServer.\n\t\tif (this->tccServer)\n\t\t{\n\t\t\tthis->tccServer->IncomingPacket(nowMs, packet);\n\t\t}\n\n\t\t// Get the associated Producer.\n\t\tRTC::Producer* producer = this->rtpListener.GetProducer(packet);\n\n\t\tif (!producer)\n\t\t{\n#ifdef MS_RTC_LOGGER_RTP\n\t\t\tpacket->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::PRODUCER_NOT_FOUND);\n#endif\n\n\t\t\tMS_WARN_TAG(\n\t\t\t  rtp,\n\t\t\t  \"no suitable Producer for received RTP packet [ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \"]\",\n\t\t\t  packet->GetSsrc(),\n\t\t\t  packet->GetPayloadType());\n\n\t\t\t// Tell the child class to remove this SSRC.\n\t\t\tRecvStreamClosed(packet->GetSsrc());\n\n\t\t\tdelete packet;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// MS_DEBUG_DEV(\n\t\t//   \"RTP packet received [ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \", producerId:%s]\",\n\t\t//   packet->GetSsrc(),\n\t\t//   packet->GetPayloadType(),\n\t\t//   producer->id.c_str());\n\n\t\t// Pass the RTP packet to the corresponding Producer.\n\t\tauto result = producer->ReceiveRtpPacket(packet);\n\n\t\tswitch (result)\n\t\t{\n\t\t\tcase RTC::Producer::ReceiveRtpPacketResult::MEDIA:\n\t\t\t{\n\t\t\t\tthis->recvRtpTransmission.Update(packet);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::Producer::ReceiveRtpPacketResult::RETRANSMISSION:\n\t\t\t{\n\t\t\t\tthis->recvRtxTransmission.Update(packet);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::Producer::ReceiveRtpPacketResult::DISCARDED:\n\t\t\t{\n\t\t\t\t// Tell the child class to remove this SSRC.\n\t\t\t\tRecvStreamClosed(packet->GetSsrc());\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\n\t\tdelete packet;\n\t}\n\n\tvoid Transport::ReceiveRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Handle each RTCP packet.\n\t\twhile (packet)\n\t\t{\n\t\t\tHandleRtcpPacket(packet);\n\n\t\t\tauto* previousPacket = packet;\n\n\t\t\tpacket = packet->GetNext();\n\n\t\t\tdelete previousPacket;\n\t\t}\n\t}\n\n\tvoid Transport::ReceiveSctpData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (\n\t\t  (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) ||\n\t\t  (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation))\n\t\t{\n\t\t\tMS_DEBUG_TAG(sctp, \"ignoring SCTP packet (SCTP not enabled)\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass it to the SctpAssociation.\n\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tthis->sctpAssociation->ReceiveSctpData(data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->oldSctpAssociation->ProcessSctpData(data, len);\n\t\t}\n\t}\n\n\t// TODO: SCTP: Remove when we have our own SCTP stack running.\n\tvoid Transport::SendSctpMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  !Settings::configuration.useBuiltInSctpStack,\n\t\t  \"cannot use this method when built-in SCTP stack is enabled\");\n\n\t\tif (!this->oldSctpAssociation)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"SCTP not enabled\");\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->oldSctpAssociation->SendSctpMessage(dataConsumer, msg, len, ppid, cb);\n\t}\n\n\tvoid Transport::SendSctpMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE: The `message` must already have its `streamId` pointing to the same\n\t\t// as in the `dataConsumer` if its type is \"sctp\", or 0 otherwise.\n\n\t\tMS_ASSERT(\n\t\t  Settings::configuration.useBuiltInSctpStack,\n\t\t  \"cannot use this method when built-in SCTP stack is not enabled\");\n\n\t\tif (!this->sctpAssociation)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"SCTP not enabled\");\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false, false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst auto& sctpStreamParameters = dataConsumer->GetSctpStreamParameters();\n\t\tconst RTC::SCTP::SendMessageOptions sendMessageOptions{\n\t\t\t.unordered          = !sctpStreamParameters.ordered,\n\t\t\t.lifetimeMs         = sctpStreamParameters.ordered\n\t\t\t                        ? std::nullopt\n\t\t\t                        : std::optional<uint64_t>(sctpStreamParameters.maxPacketLifeTime),\n\t\t\t.maxRetransmissions = sctpStreamParameters.ordered\n\t\t\t                        ? std::nullopt\n\t\t\t                        : std::optional<uint64_t>(sctpStreamParameters.maxRetransmits),\n\t\t\t// NOTE: We don't set `lifecyleId` in production.\n\t\t};\n\n\t\tconst auto sendStatus =\n\t\t  this->sctpAssociation->SendMessage(std::move(message), sendMessageOptions);\n\n\t\tswitch (sendStatus)\n\t\t{\n\t\t\tcase RTC::SCTP::Types::SendMessageStatus::SUCCESS:\n\t\t\t{\n\t\t\t\tif (cb)\n\t\t\t\t{\n\t\t\t\t\t(*cb)(true, /*sctpSendBufferFull*/ false);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::SCTP::Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION:\n\t\t\t{\n\t\t\t\tconst auto sendStatusStringView = RTC::SCTP::Types::SendMessageStatusToString(sendStatus);\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"failed to send SCTP message [sendStatus:%.*s]\",\n\t\t\t\t  static_cast<int>(sendStatusStringView.size()),\n\t\t\t\t  sendStatusStringView.data());\n\n\t\t\t\tif (cb)\n\t\t\t\t{\n\t\t\t\t\t(*cb)(false, /*sctpSendBufferFull*/ true);\n\t\t\t\t}\n\n\t\t\t\t// TODO: SCTP: We don't want this probably since we have events in Association\n\t\t\t\t// for this.\n\t\t\t\tdataConsumer->SctpAssociationSendBufferFull();\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tconst auto sendStatusStringView = RTC::SCTP::Types::SendMessageStatusToString(sendStatus);\n\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  sctp,\n\t\t\t\t  \"failed to send SCTP message [sendStatus:%.*s]\",\n\t\t\t\t  static_cast<int>(sendStatusStringView.size()),\n\t\t\t\t  sendStatusStringView.data());\n\n\t\t\t\tif (cb)\n\t\t\t\t{\n\t\t\t\t\t(*cb)(false, /*sctpSendBufferFull*/ false);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tdelete cb;\n\t}\n\n\tvoid Transport::CheckNoDataProducer(const std::string& dataProducerId) const\n\t{\n\t\tif (this->mapDataProducers.find(dataProducerId) != this->mapDataProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"a DataProducer with same dataProducerId already exists\");\n\t\t}\n\t}\n\n\tvoid Transport::CheckNoDataConsumer(const std::string& dataConsumerId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->mapDataConsumers.find(dataConsumerId) != this->mapDataConsumers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"a DataConsumer with same dataConsumerId already exists\");\n\t\t}\n\t}\n\n\tRTC::Producer* Transport::GetProducerById(const std::string& producerId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapProducers.find(producerId);\n\n\t\tif (it == this->mapProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Producer not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tRTC::Consumer* Transport::GetConsumerById(const std::string& consumerId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapConsumers.find(consumerId);\n\n\t\tif (it == this->mapConsumers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"Consumer not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tinline RTC::Consumer* Transport::GetConsumerByMediaSsrc(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapSsrcConsumerIt = this->mapSsrcConsumer.find(ssrc);\n\n\t\tif (mapSsrcConsumerIt == this->mapSsrcConsumer.end())\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\n\t\tauto* consumer = mapSsrcConsumerIt->second;\n\n\t\treturn consumer;\n\t}\n\n\tinline RTC::Consumer* Transport::GetConsumerByRtxSsrc(uint32_t ssrc) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto mapRtxSsrcConsumerIt = this->mapRtxSsrcConsumer.find(ssrc);\n\n\t\tif (mapRtxSsrcConsumerIt == this->mapRtxSsrcConsumer.end())\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\n\t\tauto* consumer = mapRtxSsrcConsumerIt->second;\n\n\t\treturn consumer;\n\t}\n\n\tRTC::DataProducer* Transport::GetDataProducerById(const std::string& dataProducerId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapDataProducers.find(dataProducerId);\n\n\t\tif (it == this->mapDataProducers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"DataProducer not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tRTC::DataConsumer* Transport::GetDataConsumerById(const std::string& dataConsumerId) const\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapDataConsumers.find(dataConsumerId);\n\n\t\tif (it == this->mapDataConsumers.end())\n\t\t{\n\t\t\tMS_THROW_ERROR(\"DataConsumer not found\");\n\t\t}\n\n\t\treturn it->second;\n\t}\n\n\tvoid Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (packet->GetType())\n\t\t{\n\t\t\tcase RTC::RTCP::Type::RR:\n\t\t\t{\n\t\t\t\tauto* rr = static_cast<RTC::RTCP::ReceiverReportPacket*>(packet);\n\n\t\t\t\tfor (auto it = rr->Begin(); it != rr->End(); ++it)\n\t\t\t\t{\n\t\t\t\t\tauto& report   = *it;\n\t\t\t\t\tauto* consumer = GetConsumerByMediaSsrc(report->GetSsrc());\n\n\t\t\t\t\tif (!consumer)\n\t\t\t\t\t{\n\t\t\t\t\t\t// Special case for the RTP probator.\n\t\t\t\t\t\tif (report->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Special case for (unused) RTCP-RR from the RTX stream.\n\t\t\t\t\t\tif (GetConsumerByRtxSsrc(report->GetSsrc()) != nullptr)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t  \"no Consumer found for received Receiver Report [ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  report->GetSsrc());\n\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconsumer->ReceiveRtcpReceiverReport(report);\n\t\t\t\t}\n\n\t\t\t\tif (this->tccClient && !this->mapConsumers.empty())\n\t\t\t\t{\n\t\t\t\t\tfloat rtt = 0;\n\n\t\t\t\t\t// Retrieve the RTT from the first active consumer.\n\t\t\t\t\tfor (auto& kv : this->mapConsumers)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* consumer = kv.second;\n\n\t\t\t\t\t\tif (consumer->IsActive())\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\trtt = consumer->GetRtt();\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->tccClient->ReceiveRtcpReceiverReport(rr, rtt, this->shared->GetTimeMsInt64());\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::PSFB:\n\t\t\t{\n\t\t\t\tauto* feedback = static_cast<RTC::RTCP::FeedbackPsPacket*>(packet);\n\n\t\t\t\tswitch (feedback->GetMessageType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* consumer = GetConsumerByMediaSsrc(feedback->GetMediaSsrc());\n\n\t\t\t\t\t\tif (feedback->GetMediaSsrc() == RTC::RTP::ProbationGenerator::Ssrc)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (!consumer)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t  \"no Consumer found for received PLI Feedback packet \"\n\t\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t  \"PLI received, requesting key frame for Consumer \"\n\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\n\t\t\t\t\t\tconsumer->ReceiveKeyFrameRequest(\n\t\t\t\t\t\t  RTC::RTCP::FeedbackPs::MessageType::PLI, feedback->GetMediaSsrc());\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t\t\t{\n\t\t\t\t\t\t// Must iterate FIR items.\n\t\t\t\t\t\tauto* fir = static_cast<RTC::RTCP::FeedbackPsFirPacket*>(packet);\n\n\t\t\t\t\t\tfor (auto it = fir->Begin(); it != fir->End(); ++it)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto& item     = *it;\n\t\t\t\t\t\t\tauto* consumer = GetConsumerByMediaSsrc(item->GetSsrc());\n\n\t\t\t\t\t\t\tif (item->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (!consumer)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t\t  \"no Consumer found for received FIR Feedback packet \"\n\t\t\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \", item ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t\t\t  feedback->GetMediaSsrc(),\n\t\t\t\t\t\t\t\t  item->GetSsrc());\n\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t  \"FIR received, requesting key frame for Consumer \"\n\t\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \", item ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t\t  feedback->GetMediaSsrc(),\n\t\t\t\t\t\t\t  item->GetSsrc());\n\n\t\t\t\t\t\t\tconsumer->ReceiveKeyFrameRequest(feedback->GetMessageType(), item->GetSsrc());\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::AFB:\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* afb = static_cast<RTC::RTCP::FeedbackPsAfbPacket*>(feedback);\n\n\t\t\t\t\t\t// Store REMB info.\n\t\t\t\t\t\tif (afb->GetApplication() == RTC::RTCP::FeedbackPsAfbPacket::Application::REMB)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto* remb = static_cast<RTC::RTCP::FeedbackPsRembPacket*>(afb);\n\n\t\t\t\t\t\t\t// Pass it to the TCC client.\n\t\t\t\t\t\t\tif (this->tccClient && this->tccClient->GetBweType() == RTC::BweType::REMB)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tthis->tccClient->ReceiveEstimatedBitrate(remb->GetBitrate());\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t  \"ignoring unsupported %s Feedback PS AFB packet \"\n\t\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t  RTC::RTCP::FeedbackPsPacket::MessageTypeToString(feedback->GetMessageType()).c_str(),\n\t\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t  \"ignoring unsupported %s Feedback packet \"\n\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  RTC::RTCP::FeedbackPsPacket::MessageTypeToString(feedback->GetMessageType()).c_str(),\n\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::RTPFB:\n\t\t\t{\n\t\t\t\tauto* feedback = static_cast<RTC::RTCP::FeedbackRtpPacket*>(packet);\n\t\t\t\tauto* consumer = GetConsumerByMediaSsrc(feedback->GetMediaSsrc());\n\n\t\t\t\t// If no Consumer is found and this is not a Transport Feedback for the\n\t\t\t\t// probation SSRC or any Consumer RTX SSRC, ignore it.\n\t\t\t\tif (\n\t\t\t\t  !consumer && feedback->GetMessageType() != RTC::RTCP::FeedbackRtp::MessageType::TCC &&\n\t\t\t\t  (feedback->GetMediaSsrc() != RTC::RTP::ProbationGenerator::Ssrc ||\n\t\t\t\t   !GetConsumerByRtxSsrc(feedback->GetMediaSsrc())))\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  rtcp,\n\t\t\t\t\t  \"no Consumer found for received Feedback packet \"\n\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t  feedback->GetMediaSsrc());\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tswitch (feedback->GetMessageType())\n\t\t\t\t{\n\t\t\t\t\tcase RTC::RTCP::FeedbackRtp::MessageType::NACK:\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!consumer)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t  \"no Consumer found for received NACK Feedback packet \"\n\t\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tauto* nackPacket = static_cast<RTC::RTCP::FeedbackRtpNackPacket*>(packet);\n\n\t\t\t\t\t\tconsumer->ReceiveNack(nackPacket);\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tcase RTC::RTCP::FeedbackRtp::MessageType::TCC:\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* feedback = static_cast<RTC::RTCP::FeedbackRtpTransportPacket*>(packet);\n\n\t\t\t\t\t\tif (this->tccClient)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->tccClient->ReceiveRtcpTransportFeedback(feedback);\n\t\t\t\t\t\t}\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t\t\t\t\t// Pass it to the SenderBandwidthEstimator client.\n\t\t\t\t\t\tif (this->senderBwe)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->senderBwe->ReceiveRtcpTransportFeedback(feedback);\n\t\t\t\t\t\t}\n#endif\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t  \"ignoring unsupported %s Feedback packet \"\n\t\t\t\t\t\t  \"[sender ssrc:%\" PRIu32 \", media ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  RTC::RTCP::FeedbackRtpPacket::MessageTypeToString(feedback->GetMessageType()).c_str(),\n\t\t\t\t\t\t  feedback->GetSenderSsrc(),\n\t\t\t\t\t\t  feedback->GetMediaSsrc());\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::SR:\n\t\t\t{\n\t\t\t\tauto* sr = static_cast<RTC::RTCP::SenderReportPacket*>(packet);\n\n\t\t\t\t// Even if Sender Report packet can only contains one report.\n\t\t\t\tfor (auto it = sr->Begin(); it != sr->End(); ++it)\n\t\t\t\t{\n\t\t\t\t\tauto& report   = *it;\n\t\t\t\t\tauto* producer = this->rtpListener.GetProducer(report->GetSsrc());\n\n\t\t\t\t\tif (!producer)\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t  \"no Producer found for received Sender Report [ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t  report->GetSsrc());\n\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tproducer->ReceiveRtcpSenderReport(report);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::SDES:\n\t\t\t{\n\t\t\t\t// According to RFC 3550 section 6.1 \"a CNAME item MUST be included in\n\t\t\t\t// each compound RTCP packet\". So this is true even for compound\n\t\t\t\t// packets sent by endpoints that are not sending any RTP stream to us\n\t\t\t\t// (thus chunks in such a SDES will have an SSCR does not match with\n\t\t\t\t// any Producer created in this Transport).\n\t\t\t\t// Therefore, and given that we do nothing with SDES, just ignore them.\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::BYE:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(rtcp, \"ignoring received RTCP BYE\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::RTCP::Type::XR:\n\t\t\t{\n\t\t\t\tauto* xr = static_cast<RTC::RTCP::ExtendedReportPacket*>(packet);\n\n\t\t\t\tfor (auto it = xr->Begin(); it != xr->End(); ++it)\n\t\t\t\t{\n\t\t\t\t\tauto& report = *it;\n\n\t\t\t\t\tswitch (report->GetType())\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RTCP::ExtendedReportBlock::Type::DLRR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto* dlrr = static_cast<RTC::RTCP::DelaySinceLastRr*>(report);\n\n\t\t\t\t\t\t\tfor (auto it2 = dlrr->Begin(); it2 != dlrr->End(); ++it2)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tauto& ssrcInfo = *it2;\n\n\t\t\t\t\t\t\t\t// SSRC should be filled in the sub-block.\n\t\t\t\t\t\t\t\tif (ssrcInfo->GetSsrc() == 0)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tssrcInfo->SetSsrc(xr->GetSsrc());\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tauto* producer = this->rtpListener.GetProducer(ssrcInfo->GetSsrc());\n\n\t\t\t\t\t\t\t\tif (!producer)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t\t\t\t\t  rtcp,\n\t\t\t\t\t\t\t\t\t  \"no Producer found for received Sender Extended Report [ssrc:%\" PRIu32 \"]\",\n\t\t\t\t\t\t\t\t\t  ssrcInfo->GetSsrc());\n\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tproducer->ReceiveRtcpXrDelaySinceLastRr(ssrcInfo);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RTCP::ExtendedReportBlock::Type::RRT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tauto* rrt = static_cast<RTC::RTCP::ReceiverReferenceTime*>(report);\n\n\t\t\t\t\t\t\tfor (auto& kv : this->mapConsumers)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tauto* consumer = kv.second;\n\n\t\t\t\t\t\t\t\tconsumer->ReceiveRtcpXrReceiverReferenceTime(rrt);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t  rtcp,\n\t\t\t\t  \"unhandled RTCP type received [type:%\" PRIu8 \"]\",\n\t\t\t\t  static_cast<uint8_t>(packet->GetType()));\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Transport::SendRtcp(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::unique_ptr<RTC::RTCP::CompoundPacket> packet{ new RTC::RTCP::CompoundPacket() };\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\tif (DepLibUring::IsEnabled())\n\t\t{\n\t\t\t// Activate liburing usage.\n\t\t\tDepLibUring::SetActive();\n\t\t}\n#endif\n\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\t\t\tauto rtcpAdded = consumer->GetRtcp(packet.get(), nowMs);\n\n\t\t\t// RTCP data couldn't be added because the Compound packet is full.\n\t\t\t// Send the RTCP compound packet and request for RTCP again.\n\t\t\tif (!rtcpAdded)\n\t\t\t{\n\t\t\t\tSendRtcpCompoundPacket(packet.get());\n\n\t\t\t\t// Create a new compount packet.\n\t\t\t\tpacket.reset(new RTC::RTCP::CompoundPacket());\n\n\t\t\t\t// Retrieve the RTCP again.\n\t\t\t\tconsumer->GetRtcp(packet.get(), nowMs);\n\t\t\t}\n\t\t}\n\n\t\tfor (auto& kv : this->mapProducers)\n\t\t{\n\t\t\tauto* producer = kv.second;\n\t\t\tauto rtcpAdded = producer->GetRtcp(packet.get(), nowMs);\n\n\t\t\t// RTCP data couldn't be added because the Compound packet is full.\n\t\t\t// Send the RTCP compound packet and request for RTCP again.\n\t\t\tif (!rtcpAdded)\n\t\t\t{\n\t\t\t\tSendRtcpCompoundPacket(packet.get());\n\n\t\t\t\t// Create a new compount packet.\n\t\t\t\tpacket.reset(new RTC::RTCP::CompoundPacket());\n\n\t\t\t\t// Retrieve the RTCP again.\n\t\t\t\tproducer->GetRtcp(packet.get(), nowMs);\n\t\t\t}\n\t\t}\n\n\t\t// Send the RTCP compound packet if there is any sender or receiver report.\n\t\tif (packet->GetReceiverReportCount() > 0u || packet->GetSenderReportCount() > 0u)\n\t\t{\n\t\t\tSendRtcpCompoundPacket(packet.get());\n\t\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\t\tif (DepLibUring::IsEnabled())\n\t\t{\n\t\t\t// Submit all prepared submission entries.\n\t\t\tDepLibUring::Submit();\n\t\t}\n#endif\n\t}\n\n\tvoid Transport::DistributeAvailableOutgoingBitrate()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->tccClient, \"no TransportCongestionClient\");\n\n\t\tstd::multimap<uint8_t, RTC::Consumer*> multimapPriorityConsumer;\n\n\t\t// Fill the map with Consumers and their priority (if > 0).\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer = kv.second;\n\t\t\tauto priority  = consumer->GetBitratePriority();\n\n\t\t\tif (priority > 0u)\n\t\t\t{\n\t\t\t\tmultimapPriorityConsumer.emplace(priority, consumer);\n\t\t\t}\n\t\t}\n\n\t\t// Nobody wants bitrate. Exit.\n\t\tif (multimapPriorityConsumer.empty())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tbool baseAllocation       = true;\n\t\tuint32_t availableBitrate = this->tccClient->GetAvailableBitrate();\n\n\t\tthis->tccClient->RescheduleNextAvailableBitrateEvent();\n\n\t\tMS_DEBUG_DEV(\"before layer-by-layer iterations [availableBitrate:%\" PRIu32 \"]\", availableBitrate);\n\n\t\t// Redistribute the available bitrate by allowing Consumers to increase\n\t\t// layer by layer. Initially try to spread the bitrate across all\n\t\t// consumers. Then allocate the excess bitrate to Consumers starting\n\t\t// with the highest priorty.\n\t\twhile (availableBitrate > 0u)\n\t\t{\n\t\t\tauto previousAvailableBitrate = availableBitrate;\n\n\t\t\tfor (auto it = multimapPriorityConsumer.rbegin(); it != multimapPriorityConsumer.rend(); ++it)\n\t\t\t{\n\t\t\t\tauto priority  = it->first;\n\t\t\t\tauto* consumer = it->second;\n\t\t\t\tauto bweType   = this->tccClient->GetBweType();\n\n\t\t\t\t// NOLINTNEXTLINE(bugprone-too-small-loop-variable)\n\t\t\t\tfor (uint8_t i{ 1u }; i <= (baseAllocation ? 1u : priority); ++i)\n\t\t\t\t{\n\t\t\t\t\tuint32_t usedBitrate{ 0u };\n\t\t\t\t\tconst bool considerLoss = (bweType == RTC::BweType::REMB);\n\n\t\t\t\t\tusedBitrate = consumer->IncreaseLayer(availableBitrate, considerLoss);\n\n\t\t\t\t\tMS_ASSERT(usedBitrate <= availableBitrate, \"Consumer used more layer bitrate than given\");\n\n\t\t\t\t\tavailableBitrate -= usedBitrate;\n\n\t\t\t\t\t// Exit the loop fast if used bitrate is 0.\n\t\t\t\t\tif (usedBitrate == 0u)\n\t\t\t\t\t{\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If no Consumer used bitrate, exit the loop.\n\t\t\tif (availableBitrate == previousAvailableBitrate)\n\t\t\t{\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbaseAllocation = false;\n\t\t}\n\n\t\tMS_DEBUG_DEV(\"after layer-by-layer iterations [availableBitrate:%\" PRIu32 \"]\", availableBitrate);\n\n\t\t// Finally instruct Consumers to apply their computed layers.\n\t\tfor (auto it = multimapPriorityConsumer.rbegin(); it != multimapPriorityConsumer.rend(); ++it)\n\t\t{\n\t\t\tauto* consumer = it->second;\n\n\t\t\tconsumer->ApplyLayers();\n\t\t}\n\t}\n\n\tvoid Transport::ComputeOutgoingDesiredBitrate(bool forceBitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->tccClient, \"no TransportCongestionClient\");\n\n\t\tuint32_t totalDesiredBitrate{ 0u };\n\n\t\tfor (auto& kv : this->mapConsumers)\n\t\t{\n\t\t\tauto* consumer      = kv.second;\n\t\t\tauto desiredBitrate = consumer->GetDesiredBitrate();\n\n\t\t\ttotalDesiredBitrate += desiredBitrate;\n\t\t}\n\n\t\tMS_DEBUG_DEV(\"total desired bitrate: %\" PRIu32, totalDesiredBitrate);\n\n\t\tthis->tccClient->SetDesiredBitrate(totalDesiredBitrate, forceBitrate);\n\t}\n\n\tinline void Transport::EmitTraceEventProbationType(RTC::RTP::Packet* /*packet*/) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.probation)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO: Missing trace info (RTP packet dump).\n\t\tauto notification = FBS::Transport::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Transport::TraceEventType::PROBATION,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_OUT);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_TRACE,\n\t\t  FBS::Notification::Body::Transport_TraceNotification,\n\t\t  notification);\n\t}\n\n\tinline void Transport::EmitTraceEventBweType(\n\t  RTC::TransportCongestionControlClient::Bitrates& bitrates) const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->traceEventTypes.bwe)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto traceInfo = FBS::Transport::CreateBweTraceInfo(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC\n\t\t    ? FBS::Transport::BweType::TRANSPORT_CC\n\t\t    : FBS::Transport::BweType::REMB,\n\t\t  bitrates.desiredBitrate,\n\t\t  bitrates.effectiveDesiredBitrate,\n\t\t  bitrates.minBitrate,\n\t\t  bitrates.maxBitrate,\n\t\t  bitrates.startBitrate,\n\t\t  bitrates.maxPaddingBitrate,\n\t\t  bitrates.availableBitrate);\n\n\t\tauto notification = FBS::Transport::CreateTraceNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::Transport::TraceEventType::BWE,\n\t\t  this->shared->GetTimeMs(),\n\t\t  FBS::Common::TraceDirection::DIRECTION_OUT,\n\t\t  FBS::Transport::TraceInfo::BweTraceInfo,\n\t\t  traceInfo.Union());\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_TRACE,\n\t\t  FBS::Notification::Body::Transport_TraceNotification,\n\t\t  notification);\n\t}\n\n\tvoid Transport::OnProducerPaused(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerPaused(this, producer);\n\t}\n\n\tvoid Transport::OnProducerResumed(RTC::Producer* producer)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerResumed(this, producer);\n\t}\n\n\tvoid Transport::OnProducerNewRtpStream(\n\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerNewRtpStream(this, producer, rtpStream, mappedSsrc);\n\t}\n\n\tvoid Transport::OnProducerRtpStreamScore(\n\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerRtpStreamScore(this, producer, rtpStream, score, previousScore);\n\t}\n\n\tvoid Transport::OnProducerRtcpSenderReport(\n\t  RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerRtcpSenderReport(this, producer, rtpStream, first);\n\t}\n\n\tvoid Transport::OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportProducerRtpPacketReceived(this, producer, packet);\n\t}\n\n\tvoid Transport::OnProducerSendRtcpPacket(RTC::Producer* /*producer*/, RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendRtcpPacket(packet);\n\t}\n\n\tvoid Transport::OnProducerNeedWorstRemoteFractionLost(\n\t  RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportNeedWorstRemoteFractionLost(\n\t\t  this, producer, mappedSsrc, worstRemoteFractionLost);\n\t}\n\n\tvoid Transport::OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n#ifdef MS_RTC_LOGGER_RTP\n\t\tpacket->logger.sendTransportId = this->id;\n\t\tpacket->logger.Sent();\n#endif\n\n\t\t// Update abs-send-time if present.\n\t\tpacket->UpdateAbsSendTime(this->shared->GetTimeMs());\n\n\t\t// Update transport wide sequence number if present.\n\t\tif (\n\t\t  this->tccClient && this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC &&\n\t\t  packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1))\n\t\t{\n\t\t\tthis->transportWideCcSeq++;\n\n\t\t\twebrtc::RtpPacketSendInfo packetInfo;\n\n\t\t\tpacketInfo.ssrc                      = packet->GetSsrc();\n\t\t\tpacketInfo.transport_sequence_number = this->transportWideCcSeq;\n\t\t\tpacketInfo.has_rtp_sequence_number   = true;\n\t\t\tpacketInfo.rtp_sequence_number       = packet->GetSequenceNumber();\n\t\t\tpacketInfo.length                    = packet->GetLength();\n\t\t\tpacketInfo.pacing_info               = this->tccClient->GetPacingInfo();\n\n\t\t\t// Indicate the pacer (and prober) that a packet is to be sent.\n\t\t\tthis->tccClient->InsertPacket(packetInfo);\n\n\t\t\t// When using WebRtcServer, the lifecycle of a RTC::UdpSocket maybe longer\n\t\t\t// than WebRtcTransport so there is a chance for the send callback to be\n\t\t\t// invoked *after* the WebRtcTransport has been closed (freed). To avoid\n\t\t\t// invalid memory access we need to use weak_ptr. Same applies in other\n\t\t\t// send callbacks.\n\t\t\tconst std::weak_ptr<RTC::TransportCongestionControlClient> tccClientWeakPtr(this->tccClient);\n\n\t\t\tauto* shared = this->shared;\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t\tstd::weak_ptr<RTC::SenderBandwidthEstimator> senderBweWeakPtr(this->senderBwe);\n\t\t\tRTC::SenderBandwidthEstimator::SentInfo sentInfo;\n\n\t\t\tsentInfo.wideSeq     = this->transportWideCcSeq;\n\t\t\tsentInfo.size        = packet->GetLength();\n\t\t\tsentInfo.sendingAtMs = this->shared->GetTimeMs();\n\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\n\t\t\t\t\t  auto senderBwe = senderBweWeakPtr.lock();\n\n\t\t\t\t\t  if (senderBwe)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  sentInfo.sentAtMs = shared->GetTimeMs();\n\t\t\t\t\t\t  senderBwe->RtpPacketSent(sentInfo);\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(consumer, packet, cb);\n#else\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo](bool sent)\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(consumer, packet, cb);\n#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSendRtpPacket(consumer, packet);\n\t\t}\n\n\t\tthis->sendRtpTransmission.Update(packet);\n\t}\n\n\tvoid Transport::OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Update abs-send-time if present.\n\t\tpacket->UpdateAbsSendTime(this->shared->GetTimeMs());\n\n\t\t// Update transport wide sequence number if present.\n\t\tif (\n\t\t  this->tccClient && this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC &&\n\t\t  packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1))\n\t\t{\n\t\t\tthis->transportWideCcSeq++;\n\n\t\t\twebrtc::RtpPacketSendInfo packetInfo;\n\n\t\t\tpacketInfo.ssrc                      = packet->GetSsrc();\n\t\t\tpacketInfo.transport_sequence_number = this->transportWideCcSeq;\n\t\t\tpacketInfo.has_rtp_sequence_number   = true;\n\t\t\tpacketInfo.rtp_sequence_number       = packet->GetSequenceNumber();\n\t\t\tpacketInfo.length                    = packet->GetLength();\n\t\t\tpacketInfo.pacing_info               = this->tccClient->GetPacingInfo();\n\n\t\t\t// Indicate the pacer (and prober) that a packet is to be sent.\n\t\t\tthis->tccClient->InsertPacket(packetInfo);\n\n\t\t\tconst std::weak_ptr<RTC::TransportCongestionControlClient> tccClientWeakPtr(this->tccClient);\n\n\t\t\tauto* shared = this->shared;\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t\tstd::weak_ptr<RTC::SenderBandwidthEstimator> senderBweWeakPtr = this->senderBwe;\n\t\t\tRTC::SenderBandwidthEstimator::SentInfo sentInfo;\n\n\t\t\tsentInfo.wideSeq     = this->transportWideCcSeq;\n\t\t\tsentInfo.size        = packet->GetLength();\n\t\t\tsentInfo.sendingAtMs = this->shared->GetTimeMs();\n\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\n\t\t\t\t\t  auto senderBwe = senderBweWeakPtr.lock();\n\n\t\t\t\t\t  if (senderBwe)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  sentInfo.sentAtMs = shared->GetTimeMs();\n\t\t\t\t\t\t  senderBwe->RtpPacketSent(sentInfo);\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(consumer, packet, cb);\n#else\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo](bool sent)\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(consumer, packet, cb);\n#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\tSendRtpPacket(consumer, packet);\n\t\t}\n\n\t\tthis->sendRtxTransmission.Update(packet);\n\t}\n\n\tvoid Transport::OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"ignoring key rame request (transport not connected)\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->listener->OnTransportConsumerKeyFrameRequested(this, consumer, mappedSsrc);\n\t}\n\n\tvoid Transport::OnConsumerNeedBitrateChange(RTC::Consumer* /*consumer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->tccClient, \"no TransportCongestionClient\");\n\n\t\tDistributeAvailableOutgoingBitrate();\n\t\tComputeOutgoingDesiredBitrate();\n\t}\n\n\tvoid Transport::OnConsumerNeedZeroBitrate(RTC::Consumer* /*consumer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(this->tccClient, \"no TransportCongestionClient\");\n\n\t\tDistributeAvailableOutgoingBitrate();\n\n\t\t// This may be the latest active Consumer with BWE. If so we have to stop probation.\n\t\tComputeOutgoingDesiredBitrate(/*forceBitrate*/ true);\n\t}\n\n\tvoid Transport::OnConsumerProducerClosed(RTC::Consumer* consumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove it from the maps.\n\t\tthis->mapConsumers.erase(consumer->id);\n\n\t\tfor (auto ssrc : consumer->GetMediaSsrcs())\n\t\t{\n\t\t\tthis->mapSsrcConsumer.erase(ssrc);\n\n\t\t\t// Tell the child class to clear associated SSRCs.\n\t\t\tSendStreamClosed(ssrc);\n\t\t}\n\n\t\tfor (auto ssrc : consumer->GetRtxSsrcs())\n\t\t{\n\t\t\tthis->mapRtxSsrcConsumer.erase(ssrc);\n\n\t\t\t// Tell the child class to clear associated SSRCs.\n\t\t\tSendStreamClosed(ssrc);\n\t\t}\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTransportConsumerProducerClosed(this, consumer);\n\n\t\t// Delete it.\n\t\tdelete consumer;\n\n\t\t// This may be the latest active Consumer with BWE. If so we have to stop probation.\n\t\tif (this->tccClient)\n\t\t{\n\t\t\tComputeOutgoingDesiredBitrate(/*forceBitrate*/ true);\n\t\t}\n\t}\n\n\tvoid Transport::OnDataProducerMessageReceived(\n\t  RTC::DataProducer* dataProducer,\n\t  const uint8_t* msg,\n\t  size_t len,\n\t  uint32_t ppid,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportDataProducerMessageReceived(\n\t\t  this, dataProducer, msg, len, ppid, subchannels, requiredSubchannel);\n\t}\n\n\tvoid Transport::OnDataProducerMessageReceived(\n\t  RTC::DataProducer* dataProducer,\n\t  RTC::SCTP::Message message,\n\t  std::vector<uint16_t>& subchannels,\n\t  std::optional<uint16_t> requiredSubchannel)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportDataProducerMessageReceived(\n\t\t  this, dataProducer, std::move(message), subchannels, requiredSubchannel);\n\t}\n\n\tvoid Transport::OnDataProducerPaused(RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportDataProducerPaused(this, dataProducer);\n\t}\n\n\tvoid Transport::OnDataProducerResumed(RTC::DataProducer* dataProducer)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->listener->OnTransportDataProducerResumed(this, dataProducer);\n\t}\n\n\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tvoid Transport::OnDataConsumerSendMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendMessage(dataConsumer, msg, len, ppid, cb);\n\t}\n\n\tvoid Transport::OnDataConsumerSendMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendMessage(dataConsumer, std::move(message), cb);\n\t}\n\n\tvoid Transport::OnDataConsumerNeedBufferedAmount(\n\t  RTC::DataConsumer* /*dataConsumer*/, uint32_t& bufferedAmount)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation)\n\t\t{\n\t\t\t// TODO: SCTP: Let's see how to obtain `streamId` argument from the DataConsumer.\n\t\t\t// bufferedAmount = this->sctpAssociation->GetStreamBufferedAmount(streamId);\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation)\n\t\t{\n\t\t\t// NOTE: The underlaying SCTP association uses a common send buffer for all\n\t\t\t// data consumers, hence the value given by this method indicates the data\n\t\t\t// buffered for all data consumers in the transport.\n\t\t\tbufferedAmount = this->oldSctpAssociation->GetSctpBufferedAmount();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tbufferedAmount = 0;\n\t\t}\n\t}\n\n\tvoid Transport::OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Remove it from the maps.\n\t\tthis->mapDataConsumers.erase(dataConsumer->id);\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTransportDataConsumerDataProducerClosed(this, dataConsumer);\n\n\t\tif (Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tif (this->sctpAssociation && dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\t// TODO: SCTP\n\t\t\t}\n\t\t}\n\t\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\t\telse\n\t\t{\n\t\t\tif (this->oldSctpAssociation && dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\t// Tell the SctpAssociation so it can reset the SCTP stream.\n\t\t\t\tthis->oldSctpAssociation->DataConsumerClosed(dataConsumer);\n\t\t\t}\n\t\t}\n\n\t\t// Delete it.\n\t\tdelete dataConsumer;\n\t}\n\n\tbool Transport::OnAssociationSendData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ignore if destroying.\n\t\t// NOTE: This is because when the child class (i.e. WebRtcTransport) is deleted,\n\t\t// its destructor is called first and then the parent Transport's destructor,\n\t\t// and we would end here calling SendData() which is an abstract method.\n\t\tif (this->isDestroying)\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring sending data because Transport is being destroying\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn SendData(data, len);\n\t}\n\n\tvoid Transport::OnAssociationConnecting()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CONNECTING);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnAssociationConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationConnected();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CONNECTED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\n\t\t// TODO: SCTP: REMOVE\n\t\tMS_DUMP(\"---- SCTP association connected, dump():\");\n\t\tthis->sctpAssociation->Dump();\n\t}\n\n\tvoid Transport::OnAssociationFailed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind);\n\n\t\tMS_WARN_TAG(\n\t\t  sctp,\n\t\t  \"SCTP association failed [errorKind:%.*s, message:%.*s]\",\n\t\t  static_cast<int>(errorKindStringView.size()),\n\t\t  errorKindStringView.data(),\n\t\t  static_cast<int>(errorMessage.size()),\n\t\t  errorMessage.data());\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationClosed();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::FAILED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnAssociationClosed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (errorKind != RTC::SCTP::Types::ErrorKind::SUCCESS)\n\t\t{\n\t\t\tconst auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind);\n\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"SCTP association closed [errorKind:%.*s, message:%.*s]\",\n\t\t\t  static_cast<int>(errorKindStringView.size()),\n\t\t\t  errorKindStringView.data(),\n\t\t\t  static_cast<int>(errorMessage.size()),\n\t\t\t  errorMessage.data());\n\t\t}\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationClosed();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CLOSED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnAssociationRestarted()\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(sctp, \"SCTP association restarted\");\n\t}\n\n\tvoid Transport::OnAssociationError(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind);\n\n\t\tMS_WARN_TAG(\n\t\t  sctp,\n\t\t  \"SCTP association error [errorKind:%.*s, message:%.*s]\",\n\t\t  static_cast<int>(errorKindStringView.size()),\n\t\t  errorKindStringView.data(),\n\t\t  static_cast<int>(errorMessage.size()),\n\t\t  errorMessage.data());\n\t}\n\n\tvoid Transport::OnAssociationMessageReceived(RTC::SCTP::Message message)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::DataProducer* dataProducer = this->sctpListener.GetDataProducer(message.GetStreamId());\n\n\t\tif (!dataProducer)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"no suitable DataProducer for received SCTP message [streamId:%\" PRIu16 \"]\",\n\t\t\t  message.GetStreamId());\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the SCTP message to the corresponding DataProducer.\n\t\ttry\n\t\t{\n\t\t\tstatic thread_local std::vector<uint16_t> emptySubchannels;\n\n\t\t\tdataProducer->ReceiveMessage(\n\t\t\t  std::move(message), emptySubchannels, /*requiredSubchannel*/ std::nullopt);\n\t\t}\n\t\tcatch (std::exception& error)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"DataProducer::ReceiveMessage() failed for received SCTP message [streamId:%\" PRIu16 \"]: %s\",\n\t\t\t  message.GetStreamId(),\n\t\t\t  error.what());\n\t\t}\n\t}\n\n\tvoid Transport::OnAssociationStreamsResetPerformed(std::span<const uint16_t> /*outboundStreamIds*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// TODO: SCTP\n\t}\n\n\tvoid Transport::OnAssociationStreamsResetFailed(\n\t  std::span<const uint16_t> /*outboundStreamIds*/, std::string_view /*errorMessage*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// TODO: SCTP\n\t}\n\n\tvoid Transport::OnAssociationInboundStreamsReset(std::span<const uint16_t> /*inboundStreamIds*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// TODO: SCTP\n\t}\n\n\tvoid Transport::OnAssociationStreamBufferedAmountLow(uint16_t /*streamId*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// TODO: SCTP\n\t}\n\n\tvoid Transport::OnAssociationTotalBufferedAmountLow()\n\t{\n\t\tMS_TRACE();\n\n\t\t// TODO: SCTP\n\t}\n\n\tbool Transport::OnAssociationIsTransportReadyForSctp()\n\t{\n\t\tMS_TRACE();\n\n\t\t// We are ready for SCTP traffic if the transport is connected (e.g. the\n\t\t// WebRtcTransport has ICE and DTLS connected) and there is at least a\n\t\t// DataProducer or DataConsumer.\n\t\t//\n\t\t// NOTE: We don't want to start SCTP connection if there are no DataProducers\n\t\t// and DataConsumers because the peer (e.g. a browser) may have not started\n\t\t// its SCTP stack (e.g. no \"m=application\" media section in its SDP) so if we\n\t\t// initiate the SCTP connection it would fail after some time.\n\t\treturn IsConnected() && (this->mapDataProducers.size() > 0 || this->mapDataConsumers.size() > 0);\n\t}\n\n\t// TODO: SCTP: Add OnAssociationLifecycleMessageXxxxxx() methods.\n\n\tvoid Transport::OnSctpAssociationConnecting(RTC::SctpAssociation* /*sctpAssociation*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CONNECTING);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnSctpAssociationConnected(RTC::SctpAssociation* /*sctpAssociation*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationConnected();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CONNECTED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnSctpAssociationFailed(RTC::SctpAssociation* /*sctpAssociation*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationClosed();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::FAILED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnSctpAssociationClosed(RTC::SctpAssociation* /*sctpAssociation*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Tell all DataConsumers.\n\t\tfor (auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SctpAssociationClosed();\n\t\t\t}\n\t\t}\n\n\t\t// Notify the Node Transport.\n\t\tauto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::SctpAssociation::SctpState::CLOSED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE,\n\t\t  FBS::Notification::Body::Transport_SctpStateChangeNotification,\n\t\t  sctpStateChangeOffset);\n\t}\n\n\tvoid Transport::OnSctpAssociationSendData(\n\t  RTC::SctpAssociation* /*sctpAssociation*/, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ignore if destroying.\n\t\t// NOTE: This is because when the child class (i.e. WebRtcTransport) is deleted,\n\t\t// its destructor is called first and then the parent Transport's destructor,\n\t\t// and we would end here calling SendSctpData() which is an abstract method.\n\t\tif (this->isDestroying)\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring sending data because Transport is being destroying\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tSendData(data, len);\n\t}\n\n\tvoid Transport::OnSctpAssociationMessageReceived(\n\t  RTC::SctpAssociation* /*sctpAssociation*/,\n\t  uint16_t streamId,\n\t  const uint8_t* msg,\n\t  size_t len,\n\t  uint32_t ppid)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::DataProducer* dataProducer = this->sctpListener.GetDataProducer(streamId);\n\n\t\tif (!dataProducer)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp, \"no suitable DataProducer for received SCTP message [streamId:%\" PRIu16 \"]\", streamId);\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the SCTP message to the corresponding DataProducer.\n\t\ttry\n\t\t{\n\t\t\tstatic thread_local std::vector<uint16_t> emptySubchannels;\n\n\t\t\tdataProducer->ReceiveMessage(\n\t\t\t  msg, len, ppid, emptySubchannels, /*requiredSubchannel*/ std::nullopt);\n\t\t}\n\t\tcatch (std::exception& error)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  sctp,\n\t\t\t  \"DataProducer::ReceiveMessage() failed for received SCTP message [streamId:%\" PRIu16 \"]: %s\",\n\t\t\t  streamId,\n\t\t\t  error.what());\n\t\t}\n\t}\n\n\tvoid Transport::OnSctpAssociationBufferedAmount(\n\t  RTC::SctpAssociation* /*sctpAssociation*/, uint32_t bufferedAmount)\n\t{\n\t\tMS_TRACE();\n\n\t\tfor (const auto& kv : this->mapDataConsumers)\n\t\t{\n\t\t\tauto* dataConsumer = kv.second;\n\n\t\t\tif (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP)\n\t\t\t{\n\t\t\t\tdataConsumer->SetSctpAssociationBufferedAmount(bufferedAmount);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid Transport::OnTransportCongestionControlClientBitrates(\n\t  RTC::TransportCongestionControlClient* /*tccClient*/,\n\t  RTC::TransportCongestionControlClient::Bitrates& bitrates)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\"outgoing available bitrate:%\" PRIu32, bitrates.availableBitrate);\n\n\t\tDistributeAvailableOutgoingBitrate();\n\t\tComputeOutgoingDesiredBitrate();\n\n\t\t// May emit 'trace' event.\n\t\tEmitTraceEventBweType(bitrates);\n\t}\n\n\tvoid Transport::OnTransportCongestionControlClientSendRtpPacket(\n\t  RTC::TransportCongestionControlClient* /*tccClient*/,\n\t  RTC::RTP::Packet* packet,\n\t  const webrtc::PacedPacketInfo& pacingInfo)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Update abs-send-time if present.\n\t\tpacket->UpdateAbsSendTime(this->shared->GetTimeMs());\n\n\t\t// Update transport wide sequence number if present.\n\t\tif (\n\t\t  this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC &&\n\t\t  packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1))\n\t\t{\n\t\t\tthis->transportWideCcSeq++;\n\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventProbationType(packet);\n\n\t\t\twebrtc::RtpPacketSendInfo packetInfo;\n\n\t\t\tpacketInfo.ssrc                      = packet->GetSsrc();\n\t\t\tpacketInfo.transport_sequence_number = this->transportWideCcSeq;\n\t\t\tpacketInfo.has_rtp_sequence_number   = true;\n\t\t\tpacketInfo.rtp_sequence_number       = packet->GetSequenceNumber();\n\t\t\tpacketInfo.length                    = packet->GetLength();\n\t\t\tpacketInfo.pacing_info               = pacingInfo;\n\n\t\t\t// Indicate the pacer (and prober) that a packet is to be sent.\n\t\t\tthis->tccClient->InsertPacket(packetInfo);\n\n\t\t\tconst std::weak_ptr<RTC::TransportCongestionControlClient> tccClientWeakPtr(this->tccClient);\n\n\t\t\tauto* shared = this->shared;\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\t\t\tstd::weak_ptr<RTC::SenderBandwidthEstimator> senderBweWeakPtr = this->senderBwe;\n\t\t\tRTC::SenderBandwidthEstimator::SentInfo sentInfo;\n\n\t\t\tsentInfo.wideSeq     = this->transportWideCcSeq;\n\t\t\tsentInfo.size        = packet->GetLength();\n\t\t\tsentInfo.isProbation = true;\n\t\t\tsentInfo.sendingAtMs = this->shared->GetTimeMs();\n\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\n\t\t\t\t\t  auto senderBwe = senderBweWeakPtr.lock();\n\n\t\t\t\t\t  if (senderBwe)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  sentInfo.sentAtMs = shared->GetTimeMs();\n\t\t\t\t\t\t  senderBwe->RtpPacketSent(sentInfo);\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(nullptr, packet, cb);\n#else\n\t\t\tconst auto* cb = new onSendCallback(\n\t\t\t  [tccClientWeakPtr, shared, packetInfo](bool sent)\n\t\t\t  {\n\t\t\t\t  if (sent)\n\t\t\t\t  {\n\t\t\t\t\t  auto tccClient = tccClientWeakPtr.lock();\n\n\t\t\t\t\t  if (tccClient)\n\t\t\t\t\t  {\n\t\t\t\t\t\t  tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64());\n\t\t\t\t\t  }\n\t\t\t\t  }\n\t\t\t  });\n\n\t\t\tSendRtpPacket(nullptr, packet, cb);\n#endif\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// May emit 'trace' event.\n\t\t\tEmitTraceEventProbationType(packet);\n\n\t\t\tSendRtpPacket(nullptr, packet);\n\t\t}\n\n\t\tthis->sendProbationTransmission.Update(packet);\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"probation sent [seq:%\" PRIu16 \", wideSeq:%\" PRIu16 \", size:%zu, bitrate:%\" PRIu32 \"]\",\n\t\t  packet->GetSequenceNumber(),\n\t\t  this->transportWideCcSeq,\n\t\t  packet->GetLength(),\n\t\t  this->sendProbationTransmission.GetBitrate(this->shared->GetTimeMs()));\n\t}\n\n\tvoid Transport::OnTransportCongestionControlServerSendRtcpPacket(\n\t  RTC::TransportCongestionControlServer* /*tccServer*/, RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\tSendRtcpPacket(packet);\n\t}\n\n#ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR\n\tvoid Transport::OnSenderBandwidthEstimatorAvailableBitrate(\n\t  RTC::SenderBandwidthEstimator* /*senderBwe*/,\n\t  uint32_t availableBitrate,\n\t  uint32_t previousAvailableBitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"outgoing available bitrate [now:%\" PRIu32 \", before:%\" PRIu32 \"]\",\n\t\t  availableBitrate,\n\t\t  previousAvailableBitrate);\n\n\t\t// TODO: Uncomment once just SenderBandwidthEstimator is used.\n\t\t// DistributeAvailableOutgoingBitrate();\n\t\t// ComputeOutgoingDesiredBitrate();\n\t}\n#endif\n\n\tvoid Transport::OnTimer(TimerHandleInterface* timer)\n\t{\n\t\tMS_TRACE();\n\n\t\t// RTCP timer.\n\t\tif (timer == this->rtcpTimer)\n\t\t{\n\t\t\tauto interval        = static_cast<uint64_t>(RTC::RTCP::MaxVideoIntervalMs);\n\t\t\tconst uint64_t nowMs = this->shared->GetTimeMs();\n\n\t\t\tSendRtcp(nowMs);\n\n\t\t\t/*\n\t\t\t * The interval between RTCP packets is varied randomly over the range\n\t\t\t * [1.0, 1.5] times the calculated interval to avoid unintended\n\t\t\t * synchronization of all participants.\n\t\t\t */\n\t\t\tinterval *= static_cast<float>(Utils::Crypto::GetRandomUInt<uint16_t>(10, 15)) / 10;\n\n\t\t\tthis->rtcpTimer->Start(interval);\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TransportCongestionControlClient.cpp",
    "content": "#define MS_CLASS \"RTC::TransportCongestionControlClient\"\n// #define MS_LOG_DEV_LEVEL 3\n#define USE_TREND_CALCULATOR\n\n#include \"RTC/TransportCongestionControlClient.hpp\"\n#include \"Logger.hpp\"\n#include <libwebrtc/api/transport/network_types.h> // webrtc::TargetRateConstraints\n#include <limits>                                  // std::numeric_limits\n\nnamespace RTC\n{\n\t/* Static. */\n\n\t// NOTE: TransportCongestionControlMinOutgoingBitrate is defined in\n\t// TransportCongestionControlClient.hpp and exposed publicly.\n\tstatic constexpr float MaxBitrateMarginFactor{ 0.1f };\n\tstatic constexpr float MaxBitrateIncrementFactor{ 1.35f };\n\tstatic constexpr float MaxPaddingBitrateFactor{ 0.85f };\n\tstatic constexpr uint64_t AvailableBitrateEventInterval{ 1000u }; // In ms.\n\tstatic constexpr size_t PacketLossHistogramLength{ 24 };\n\n\t/* Instance methods. */\n\n\tTransportCongestionControlClient::TransportCongestionControlClient(\n\t  RTC::TransportCongestionControlClient::Listener* listener,\n\t  SharedInterface* shared,\n\t  RTC::BweType bweType,\n\t  uint32_t initialAvailableBitrate,\n\t  uint32_t maxOutgoingBitrate,\n\t  uint32_t minOutgoingBitrate)\n\t  : listener(listener),\n\t    shared(shared),\n\t    bweType(bweType),\n\t    initialAvailableBitrate(\n\t      std::max<uint32_t>(\n\t        initialAvailableBitrate, RTC::TransportCongestionControlMinOutgoingBitrate)),\n\t    maxOutgoingBitrate(maxOutgoingBitrate),\n\t    minOutgoingBitrate(minOutgoingBitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\twebrtc::GoogCcFactoryConfig config;\n\n\t\t// Provide RTCP feedback as well as Receiver Reports.\n\t\tconfig.feedback_only = true;\n\n\t\tthis->controllerFactory = new webrtc::GoogCcNetworkControllerFactory(std::move(config));\n\t}\n\n\tTransportCongestionControlClient::~TransportCongestionControlClient()\n\t{\n\t\tMS_TRACE();\n\n\t\tdelete this->controllerFactory;\n\t\tthis->controllerFactory = nullptr;\n\n\t\tDestroyController();\n\t}\n\n\tvoid TransportCongestionControlClient::InitializeController()\n\t{\n\t\tMS_ASSERT(this->rtpTransportControllerSend == nullptr, \"transport controller already initialized\");\n\n\t\twebrtc::BitrateConstraints bitrateConfig;\n\t\tbitrateConfig.start_bitrate_bps = static_cast<int>(this->initialAvailableBitrate);\n\n\t\tthis->rtpTransportControllerSend =\n\t\t  new webrtc::RtpTransportControllerSend(this, nullptr, this->controllerFactory, bitrateConfig);\n\n\t\tthis->rtpTransportControllerSend->RegisterTargetTransferRateObserver(this);\n\n\t\tthis->probationGenerator = new RTC::RTP::ProbationGenerator();\n\n\t\t// This makes sure that periodic probing is used when the application is send\n\t\t// less bitrate than needed to measure the bandwidth estimation.  (f.e. when\n\t\t// videos are muted or using screensharing with still images)\n\t\tthis->rtpTransportControllerSend->EnablePeriodicAlrProbing(true);\n\n\t\tthis->processTimer = this->shared->CreateTimer(this);\n\n\t\tthis->processTimer->Start(\n\t\t  std::min(\n\t\t    // Depends on probation being done and WebRTC-Pacer-MinPacketLimitMs field trial.\n\t\t    this->rtpTransportControllerSend->packet_sender()->TimeUntilNextProcess(),\n\t\t    // Fixed value (25ms), libwebrtc/api/transport/goog_cc_factory.cc.\n\t\t    this->controllerFactory->GetProcessInterval().ms()));\n\t}\n\n\tvoid TransportCongestionControlClient::DestroyController()\n\t{\n\t\tdelete this->rtpTransportControllerSend;\n\t\tthis->rtpTransportControllerSend = nullptr;\n\n\t\tdelete this->probationGenerator;\n\t\tthis->probationGenerator = nullptr;\n\n\t\tdelete this->processTimer;\n\t\tthis->processTimer = nullptr;\n\t}\n\n\tvoid TransportCongestionControlClient::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\tInitializeController();\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->OnNetworkAvailability(true);\n\t}\n\n\tvoid TransportCongestionControlClient::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n#ifdef USE_TREND_CALCULATOR\n\t\tconst auto nowMs = this->shared->GetTimeMsInt64();\n#endif\n\n\t\tthis->bitrates.desiredBitrate          = 0u;\n\t\tthis->bitrates.effectiveDesiredBitrate = 0u;\n\n#ifdef USE_TREND_CALCULATOR\n\t\tthis->desiredBitrateTrend.ForceUpdate(0u, nowMs);\n#endif\n\n\t\tthis->rtpTransportControllerSend->OnNetworkAvailability(false);\n\t}\n\n\tvoid TransportCongestionControlClient::InsertPacket(webrtc::RtpPacketSendInfo& packetInfo)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->packet_sender()->InsertPacket(packetInfo.length);\n\t\tthis->rtpTransportControllerSend->OnAddPacket(packetInfo);\n\t}\n\n\twebrtc::PacedPacketInfo TransportCongestionControlClient::GetPacingInfo()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn {};\n\t\t}\n\n\t\treturn this->rtpTransportControllerSend->packet_sender()->GetPacingInfo();\n\t}\n\n\tvoid TransportCongestionControlClient::PacketSent(\n\t  const webrtc::RtpPacketSendInfo& packetInfo, int64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Notify the transport feedback adapter about the sent packet.\n\t\tconst rtc::SentPacket sentPacket(packetInfo.transport_sequence_number, nowMs);\n\t\tthis->rtpTransportControllerSend->OnSentPacket(sentPacket, packetInfo.length);\n\t}\n\n\tvoid TransportCongestionControlClient::ReceiveEstimatedBitrate(uint32_t bitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->OnReceivedEstimatedBitrate(bitrate);\n\t}\n\n\tvoid TransportCongestionControlClient::ReceiveRtcpReceiverReport(\n\t  RTC::RTCP::ReceiverReportPacket* packet, float rtt, int64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\twebrtc::ReportBlockList reportBlockList;\n\n\t\tfor (auto it = packet->Begin(); it != packet->End(); ++it)\n\t\t{\n\t\t\tauto& report = *it;\n\n\t\t\treportBlockList.emplace_back(\n\t\t\t  packet->GetSsrc(),\n\t\t\t  report->GetSsrc(),\n\t\t\t  report->GetFractionLost(),\n\t\t\t  report->GetTotalLost(),\n\t\t\t  report->GetLastSeq(),\n\t\t\t  report->GetJitter(),\n\t\t\t  report->GetLastSenderReport(),\n\t\t\t  report->GetDelaySinceLastSenderReport());\n\t\t}\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->OnReceivedRtcpReceiverReport(\n\t\t  reportBlockList, static_cast<int64_t>(rtt), nowMs);\n\t}\n\n\tvoid TransportCongestionControlClient::ReceiveRtcpTransportFeedback(\n\t  const RTC::RTCP::FeedbackRtpTransportPacket* feedback)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Update packet loss history.\n\t\tconst size_t expectedPackets = feedback->GetPacketStatusCount();\n\t\tsize_t lostPackets           = 0;\n\n\t\tfor (const auto& result : feedback->GetPacketResults())\n\t\t{\n\t\t\tif (!result.received)\n\t\t\t{\n\t\t\t\tlostPackets += 1;\n\t\t\t}\n\t\t}\n\n\t\tif (expectedPackets > 0)\n\t\t{\n\t\t\tthis->UpdatePacketLoss(static_cast<double>(lostPackets) / expectedPackets);\n\t\t}\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->OnTransportFeedback(*feedback);\n\t}\n\n\tvoid TransportCongestionControlClient::UpdatePacketLoss(double packetLoss)\n\t{\n\t\t// Add the lost into the histogram.\n\t\tif (this->packetLossHistory.size() == PacketLossHistogramLength)\n\t\t{\n\t\t\tthis->packetLossHistory.pop_front();\n\t\t}\n\n\t\tthis->packetLossHistory.push_back(packetLoss);\n\n\t\t/*\n\t\t * Scoring mechanism is a weighted average.\n\t\t *\n\t\t * The more recent the score is, the more weight it has.\n\t\t * The oldest score has a weight of 1 and subsequent scores weight is\n\t\t * increased by one sequentially.\n\t\t *\n\t\t * Ie:\n\t\t * - scores: [1,2,3,4]\n\t\t * - this->scores = ((1) + (2+2) + (3+3+3) + (4+4+4+4)) / 10 = 2.8 => 3\n\t\t */\n\n\t\tsize_t weight{ 0 };\n\t\tsize_t samples{ 0 };\n\t\tdouble totalPacketLoss{ 0 };\n\n\t\tfor (auto packetLossEntry : this->packetLossHistory)\n\t\t{\n\t\t\tweight++;\n\t\t\tsamples += weight;\n\t\t\ttotalPacketLoss += weight * packetLossEntry;\n\t\t}\n\n\t\t// clang-tidy \"thinks\" that this can lead to division by zero but we are\n\t\t// smarter.\n\t\t// NOLINTNEXTLINE(clang-analyzer-core.DivideZero)\n\t\tthis->packetLoss = totalPacketLoss / samples;\n\t}\n\n\tvoid TransportCongestionControlClient::SetMaxOutgoingBitrate(uint32_t maxBitrate)\n\t{\n\t\tthis->maxOutgoingBitrate = maxBitrate;\n\n\t\tApplyBitrateUpdates();\n\n\t\tif (this->maxOutgoingBitrate > 0u)\n\t\t{\n\t\t\tthis->bitrates.availableBitrate =\n\t\t\t  std::min<uint32_t>(this->maxOutgoingBitrate, this->bitrates.availableBitrate);\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlClient::SetMinOutgoingBitrate(uint32_t minBitrate)\n\t{\n\t\tthis->minOutgoingBitrate = minBitrate;\n\n\t\tApplyBitrateUpdates();\n\n\t\tthis->bitrates.minBitrate = std::max<uint32_t>(\n\t\t  this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate);\n\t}\n\n\tvoid TransportCongestionControlClient::SetDesiredBitrate(uint32_t desiredBitrate, bool force)\n\t{\n\t\tMS_TRACE();\n\n#ifdef USE_TREND_CALCULATOR\n\t\tconst auto nowMs = this->shared->GetTimeMsInt64();\n#endif\n\n\t\t// Manage it via trending and increase it a bit to avoid immediate oscillations.\n#ifdef USE_TREND_CALCULATOR\n\t\tif (!force)\n\t\t{\n\t\t\tthis->desiredBitrateTrend.Update(desiredBitrate, nowMs);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->desiredBitrateTrend.ForceUpdate(desiredBitrate, nowMs);\n\t\t}\n#endif\n\n\t\tthis->bitrates.desiredBitrate = desiredBitrate;\n\n#ifdef USE_TREND_CALCULATOR\n\t\tthis->bitrates.effectiveDesiredBitrate = this->desiredBitrateTrend.GetValue();\n#else\n\t\tthis->bitrates.effectiveDesiredBitrate = desiredBitrate;\n#endif\n\n\t\tthis->bitrates.minBitrate = std::max<uint32_t>(\n\t\t  this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate);\n\n\t\t// NOTE: Setting 'startBitrate' to 'availableBitrate' has proven to generate\n\t\t// more stable values.\n\t\tthis->bitrates.startBitrate = std::max<uint32_t>(\n\t\t  RTC::TransportCongestionControlMinOutgoingBitrate, this->bitrates.availableBitrate);\n\n\t\tApplyBitrateUpdates();\n\t}\n\n\tvoid TransportCongestionControlClient::ApplyBitrateUpdates()\n\t{\n\t\tauto currentMaxBitrate = this->bitrates.maxBitrate;\n\t\tuint32_t newMaxBitrate = 0;\n\n#ifdef USE_TREND_CALCULATOR\n\t\tif (this->desiredBitrateTrend.GetValue() > 0u)\n#else\n\t\tif (this->bitrates.desiredBitrate > 0u)\n#endif\n\t\t{\n\t\t\tnewMaxBitrate = std::max<uint32_t>(\n\t\t\t  this->initialAvailableBitrate,\n#ifdef USE_TREND_CALCULATOR\n\t\t\t  this->desiredBitrateTrend.GetValue() * MaxBitrateIncrementFactor);\n#else\n\t\t\t  this->bitrates.desiredBitrate * MaxBitrateIncrementFactor);\n#endif\n\n\t\t\t// If max bitrate requested didn't change by more than a small % keep the\n\t\t\t// previous settings to avoid constant small fluctuations requiring extra\n\t\t\t// probing and making the estimation less stable (requires constant\n\t\t\t// redistribution of bitrate accross consumers).\n\t\t\tauto maxBitrateMargin = newMaxBitrate * MaxBitrateMarginFactor;\n\t\t\tif (currentMaxBitrate > newMaxBitrate - maxBitrateMargin && currentMaxBitrate < newMaxBitrate + maxBitrateMargin)\n\t\t\t{\n\t\t\t\tnewMaxBitrate = currentMaxBitrate;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tnewMaxBitrate = this->initialAvailableBitrate;\n\t\t}\n\n\t\tif (this->maxOutgoingBitrate > 0u)\n\t\t{\n\t\t\tnewMaxBitrate = std::min<uint32_t>(this->maxOutgoingBitrate, newMaxBitrate);\n\t\t}\n\n\t\tif (newMaxBitrate != currentMaxBitrate)\n\t\t{\n\t\t\tthis->bitrates.maxPaddingBitrate = newMaxBitrate * MaxPaddingBitrateFactor;\n\t\t\tthis->bitrates.maxBitrate        = newMaxBitrate;\n\t\t}\n\n\t\tthis->bitrates.minBitrate = std::max<uint32_t>(\n\t\t  this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate);\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"[desiredBitrate:%\" PRIu32 \", desiredBitrateTrend:%\" PRIu32 \", startBitrate:%\" PRIu32\n\t\t  \", minBitrate:%\" PRIu32 \", maxBitrate:%\" PRIu32 \", maxPaddingBitrate:%\" PRIu32 \"]\",\n\t\t  this->bitrates.desiredBitrate,\n\t\t  this->desiredBitrateTrend.GetValue(),\n\t\t  this->bitrates.startBitrate,\n\t\t  this->bitrates.minBitrate,\n\t\t  this->bitrates.maxBitrate,\n\t\t  this->bitrates.maxPaddingBitrate);\n\n\t\tif (this->rtpTransportControllerSend == nullptr)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->rtpTransportControllerSend->SetAllocatedSendBitrateLimits(\n\t\t  this->bitrates.minBitrate, this->bitrates.maxPaddingBitrate, this->bitrates.maxBitrate);\n\n\t\twebrtc::TargetRateConstraints constraints;\n\n\t\tconstraints.at_time       = webrtc::Timestamp::ms(this->shared->GetTimeMs());\n\t\tconstraints.min_data_rate = webrtc::DataRate::bps(this->bitrates.minBitrate);\n\t\tconstraints.max_data_rate = webrtc::DataRate::bps(this->bitrates.maxBitrate);\n\t\tconstraints.starting_rate = webrtc::DataRate::bps(this->bitrates.startBitrate);\n\n\t\tthis->rtpTransportControllerSend->SetClientBitratePreferences(constraints);\n\t}\n\n\tuint32_t TransportCongestionControlClient::GetAvailableBitrate() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->bitrates.availableBitrate;\n\t}\n\n\tdouble TransportCongestionControlClient::GetPacketLoss() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->packetLoss;\n\t}\n\n\tvoid TransportCongestionControlClient::RescheduleNextAvailableBitrateEvent()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs();\n\t}\n\n\tvoid TransportCongestionControlClient::MayEmitAvailableBitrateEvent(uint32_t previousAvailableBitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint64_t nowMs = this->shared->GetTimeMsInt64();\n\t\tbool notify{ false };\n\n\t\t// Ignore if first event.\n\t\t// NOTE: Otherwise it will make the Transport crash since this event also happens\n\t\t// during the constructor of this class.\n\t\tif (this->lastAvailableBitrateEventAtMs == 0u)\n\t\t{\n\t\t\tthis->lastAvailableBitrateEventAtMs = nowMs;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Emit if this is the first valid event.\n\t\tif (!this->availableBitrateEventCalled)\n\t\t{\n\t\t\tthis->availableBitrateEventCalled = true;\n\n\t\t\tnotify = true;\n\t\t}\n\t\t// Emit event if AvailableBitrateEventInterval elapsed.\n\t\telse if (nowMs - this->lastAvailableBitrateEventAtMs >= AvailableBitrateEventInterval)\n\t\t{\n\t\t\tnotify = true;\n\t\t}\n\t\t// Also emit the event fast if we detect a high BWE value decrease.\n\t\telse if (this->bitrates.availableBitrate < previousAvailableBitrate * 0.75)\n\t\t{\n\t\t\tMS_WARN_TAG(\n\t\t\t  bwe,\n\t\t\t  \"high BWE value decrease detected, notifying the listener [now:%\" PRIu32 \", before:%\" PRIu32\n\t\t\t  \"]\",\n\t\t\t  this->bitrates.availableBitrate,\n\t\t\t  previousAvailableBitrate);\n\n\t\t\tnotify = true;\n\t\t}\n\t\t// Also emit the event fast if we detect a high BWE value increase.\n\t\telse if (this->bitrates.availableBitrate > previousAvailableBitrate * 1.50)\n\t\t{\n\t\t\tMS_DEBUG_TAG(\n\t\t\t  bwe,\n\t\t\t  \"high BWE value increase detected, notifying the listener [now:%\" PRIu32 \", before:%\" PRIu32\n\t\t\t  \"]\",\n\t\t\t  this->bitrates.availableBitrate,\n\t\t\t  previousAvailableBitrate);\n\n\t\t\tnotify = true;\n\t\t}\n\n\t\tif (notify)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"notifying the listener with new available bitrate:%\" PRIu32,\n\t\t\t  this->bitrates.availableBitrate);\n\n\t\t\tthis->lastAvailableBitrateEventAtMs = nowMs;\n\n\t\t\tthis->listener->OnTransportCongestionControlClientBitrates(this, this->bitrates);\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlClient::OnTargetTransferRate(webrtc::TargetTransferRate targetTransferRate)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOTE: The same value as 'this->initialAvailableBitrate' is received\n\t\t// periodically regardless of the real available bitrate. Skip such value\n\t\t// except for the first time this event is called.\n\t\tif (this->availableBitrateEventCalled && targetTransferRate.target_rate.bps() == this->initialAvailableBitrate)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto previousAvailableBitrate = this->bitrates.availableBitrate;\n\n\t\t// Update availableBitrate.\n\t\t// NOTE: Just in case.\n\t\tif (targetTransferRate.target_rate.bps() > std::numeric_limits<uint32_t>::max())\n\t\t{\n\t\t\tthis->bitrates.availableBitrate = std::numeric_limits<uint32_t>::max();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->bitrates.availableBitrate = static_cast<uint32_t>(targetTransferRate.target_rate.bps());\n\t\t}\n\n\t\tMS_DEBUG_DEV(\"new available bitrate:%\" PRIu32, this->bitrates.availableBitrate);\n\n\t\tMayEmitAvailableBitrateEvent(previousAvailableBitrate);\n\t}\n\n\t// Called from PacedSender in order to send probation packets.\n\tvoid TransportCongestionControlClient::SendPacket(\n\t  RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Send the packet.\n\t\tthis->listener->OnTransportCongestionControlClientSendRtpPacket(this, packet, pacingInfo);\n\t}\n\n\tRTC::RTP::Packet* TransportCongestionControlClient::GeneratePadding(size_t size)\n\t{\n\t\tMS_TRACE();\n\t\tMS_ASSERT(this->probationGenerator, \"probation generator not initialized\")\n\n\t\treturn this->probationGenerator->GetNextPacket(size);\n\t}\n\n\tvoid TransportCongestionControlClient::OnTimer(TimerHandleInterface* timer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (timer == this->processTimer)\n\t\t{\n\t\t\t// Time to call RtpTransportControllerSend::Process().\n\t\t\tthis->rtpTransportControllerSend->Process();\n\n\t\t\t// Time to call PacedSender::Process().\n\t\t\tthis->rtpTransportControllerSend->packet_sender()->Process();\n\n\t\t\tthis->processTimer->Start(\n\t\t\t  std::min<uint64_t>(\n\t\t\t    // Depends on probation being done and WebRTC-Pacer-MinPacketLimitMs field trial.\n\t\t\t    this->rtpTransportControllerSend->packet_sender()->TimeUntilNextProcess(),\n\t\t\t    // Fixed value (25ms), libwebrtc/api/transport/goog_cc_factory.cc.\n\t\t\t    this->controllerFactory->GetProcessInterval().ms()));\n\n\t\t\tMayEmitAvailableBitrateEvent(this->bitrates.availableBitrate);\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TransportCongestionControlServer.cpp",
    "content": "#define MS_CLASS \"RTC::TransportCongestionControlServer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/TransportCongestionControlServer.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms.\n\tstatic constexpr uint64_t LimitationRembInterval{ 1500u };         // In ms.\n\tstatic constexpr uint64_t PacketArrivalTimestampWindow{ 500u };    // In ms.\n\tstatic constexpr uint8_t UnlimitedRembNumPackets{ 4u };\n\tstatic constexpr size_t PacketLossHistogramLength{ 24 };\n\n\t/* Instance methods. */\n\n\tTransportCongestionControlServer::TransportCongestionControlServer(\n\t  RTC::TransportCongestionControlServer::Listener* listener,\n\t  SharedInterface* shared,\n\t  RTC::BweType bweType,\n\t  size_t maxRtcpPacketLen)\n\t  : listener(listener), shared(shared), bweType(bweType), maxRtcpPacketLen(maxRtcpPacketLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (this->bweType)\n\t\t{\n\t\t\tcase RTC::BweType::TRANSPORT_CC:\n\t\t\t{\n\t\t\t\t// Create a feedback packet.\n\t\t\t\tResetTransportCcFeedback(0u);\n\n\t\t\t\t// Create the feedback send periodic timer.\n\t\t\t\tthis->transportCcFeedbackSendPeriodicTimer = this->shared->CreateTimer(this);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::BweType::REMB:\n\t\t\t{\n\t\t\t\tthis->rembServer = new webrtc::RemoteBitrateEstimatorAbsSendTime(this);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tTransportCongestionControlServer::~TransportCongestionControlServer()\n\t{\n\t\tMS_TRACE();\n\n\t\tdelete this->transportCcFeedbackSendPeriodicTimer;\n\t\tthis->transportCcFeedbackSendPeriodicTimer = nullptr;\n\n\t\t// Delete REMB server.\n\t\tdelete this->rembServer;\n\t\tthis->rembServer = nullptr;\n\t}\n\n\tvoid TransportCongestionControlServer::TransportConnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (this->bweType)\n\t\t{\n\t\t\tcase RTC::BweType::TRANSPORT_CC:\n\t\t\t{\n\t\t\t\tthis->transportCcFeedbackSendPeriodicTimer->Start(\n\t\t\t\t  TransportCcFeedbackSendInterval, TransportCcFeedbackSendInterval);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlServer::TransportDisconnected()\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (this->bweType)\n\t\t{\n\t\t\tcase RTC::BweType::TRANSPORT_CC:\n\t\t\t{\n\t\t\t\tthis->transportCcFeedbackSendPeriodicTimer->Stop();\n\n\t\t\t\t// Create a new feedback packet.\n\t\t\t\tResetTransportCcFeedback(this->transportCcFeedbackPacketCount);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:;\n\t\t}\n\t}\n\n\tdouble TransportCongestionControlServer::GetPacketLoss() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->packetLoss;\n\t}\n\n\tvoid TransportCongestionControlServer::IncomingPacket(uint64_t nowMs, const RTC::RTP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (this->bweType)\n\t\t{\n\t\t\tcase RTC::BweType::TRANSPORT_CC:\n\t\t\t{\n\t\t\t\tuint16_t wideSeqNumber{ 0 };\n\n\t\t\t\tif (!packet->ReadTransportWideCc01(wideSeqNumber))\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Only insert the packet when receiving it for the first time.\n\t\t\t\tif (!this->mapPacketArrivalTimes.try_emplace(wideSeqNumber, nowMs).second)\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// We may receive packets with sequence number lower than the one in\n\t\t\t\t// previous tcc feedback, these packets may have been reported as lost\n\t\t\t\t// previously, therefore we need to reset the start sequence num for the\n\t\t\t\t// next tcc feedback.\n\t\t\t\tif (\n\t\t\t\t  !this->transportWideSeqNumberReceived ||\n\t\t\t\t  RTC::SeqManager<uint16_t>::IsSeqLowerThan(\n\t\t\t\t    wideSeqNumber, this->transportCcFeedbackWideSeqNumStart))\n\t\t\t\t{\n\t\t\t\t\tthis->transportCcFeedbackWideSeqNumStart = wideSeqNumber;\n\t\t\t\t}\n\n\t\t\t\tthis->transportWideSeqNumberReceived = true;\n\n\t\t\t\tMayDropOldPacketArrivalTimes(wideSeqNumber, nowMs);\n\n\t\t\t\t// Update the RTCP media SSRC of the ongoing Transport-CC Feedback packet.\n\t\t\t\tthis->transportCcFeedbackSenderSsrc = 0u;\n\t\t\t\tthis->transportCcFeedbackMediaSsrc  = packet->GetSsrc();\n\n\t\t\t\tMaySendLimitationRembFeedback(nowMs);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase RTC::BweType::REMB:\n\t\t\t{\n\t\t\t\tuint32_t absSendTime{ 0 };\n\n\t\t\t\tif (!packet->ReadAbsSendTime(absSendTime))\n\t\t\t\t{\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// NOTE: nowMs is uint64_t but we need to \"convert\" it to int64_t before\n\t\t\t\t// we give it to libwebrtc lib (althought this is implicit in the\n\t\t\t\t// conversion so it would be converted within the method call).\n\t\t\t\tauto nowMsInt64 = static_cast<int64_t>(nowMs);\n\n\t\t\t\tthis->rembServer->IncomingPacket(nowMsInt64, packet->GetPayloadLength(), *packet, absSendTime);\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlServer::FillAndSendTransportCcFeedback()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->transportWideSeqNumberReceived)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto it = this->mapPacketArrivalTimes.lower_bound(this->transportCcFeedbackWideSeqNumStart);\n\n\t\tif (it == this->mapPacketArrivalTimes.end())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tfor (; it != this->mapPacketArrivalTimes.end(); ++it)\n\t\t{\n\t\t\tauto sequenceNumber = it->first;\n\t\t\tauto timestamp      = it->second;\n\n\t\t\t// If the base is not set in this packet let's set it.\n\t\t\t// NOTE: This maybe needed many times during this loop since the current\n\t\t\t// feedback packet maybe a fresh new one if the previous one was full (so\n\t\t\t// already sent) or failed to be built.\n\t\t\tif (!this->transportCcFeedbackPacket->IsBaseSet())\n\t\t\t{\n\t\t\t\t// Set base sequence num and reference time.\n\t\t\t\tthis->transportCcFeedbackPacket->SetBase(this->transportCcFeedbackWideSeqNumStart, timestamp);\n\t\t\t}\n\n\t\t\tauto result = this->transportCcFeedbackPacket->AddPacket(\n\t\t\t  sequenceNumber, timestamp, this->maxRtcpPacketLen);\n\n\t\t\tswitch (result)\n\t\t\t{\n\t\t\t\tcase RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::SUCCESS:\n\t\t\t\t{\n\t\t\t\t\t// If the feedback packet is full, send it now.\n\t\t\t\t\tif (this->transportCcFeedbackPacket->IsFull())\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_DEBUG_DEV(\"transport-cc feedback packet is full, sending feedback now\");\n\n\t\t\t\t\t\tauto sent = SendTransportCcFeedback();\n\n\t\t\t\t\t\tif (sent)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t++this->transportCcFeedbackPacketCount;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create a new feedback packet.\n\t\t\t\t\t\tResetTransportCcFeedback(this->transportCcFeedbackPacketCount);\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::MAX_SIZE_EXCEEDED:\n\t\t\t\t{\n\t\t\t\t\t// Send ongoing feedback packet.\n\t\t\t\t\tauto sent = SendTransportCcFeedback();\n\n\t\t\t\t\tif (sent)\n\t\t\t\t\t{\n\t\t\t\t\t\t++this->transportCcFeedbackPacketCount;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create a new feedback packet.\n\t\t\t\t\tResetTransportCcFeedback(this->transportCcFeedbackPacketCount);\n\n\t\t\t\t\t// Decrease iterator to add current packet again.\n\t\t\t\t\t--it;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::FATAL:\n\t\t\t\t{\n\t\t\t\t\t// Create a new feedback packet.\n\t\t\t\t\t// NOTE: Do not increment packet count it since the previous ongoing\n\t\t\t\t\t// feedback packet was not sent.\n\t\t\t\t\tResetTransportCcFeedback(this->transportCcFeedbackPacketCount);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// It may happen that the packet is empty (no deltas) but in that case\n\t\t// SendTransportCcFeedback() won't send it so we are safe.\n\t\tauto sent = SendTransportCcFeedback();\n\n\t\tif (sent)\n\t\t{\n\t\t\t++this->transportCcFeedbackPacketCount;\n\t\t}\n\n\t\t// Create a new feedback packet.\n\t\tResetTransportCcFeedback(this->transportCcFeedbackPacketCount);\n\t}\n\n\tvoid TransportCongestionControlServer::SetMaxIncomingBitrate(uint32_t bitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto previousMaxIncomingBitrate = this->maxIncomingBitrate;\n\n\t\tthis->maxIncomingBitrate = bitrate;\n\n\t\tif (previousMaxIncomingBitrate != 0u && this->maxIncomingBitrate == 0u)\n\t\t{\n\t\t\t// This is to ensure that we send N REMB packets with bitrate 0 (unlimited).\n\t\t\tthis->unlimitedRembCounter = UnlimitedRembNumPackets;\n\n\t\t\tauto nowMs = this->shared->GetTimeMs();\n\n\t\t\tMaySendLimitationRembFeedback(nowMs);\n\t\t}\n\t}\n\n\tbool TransportCongestionControlServer::SendTransportCcFeedback()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportCcFeedbackPacket->Finish();\n\n\t\tif (!this->transportCcFeedbackPacket->IsSerializable())\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"couldn't send feedback-cc packet because it is not serializable\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\tauto latestWideSeqNumber = this->transportCcFeedbackPacket->GetLatestSequenceNumber();\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTransportCongestionControlServerSendRtcpPacket(\n\t\t  this, this->transportCcFeedbackPacket.get());\n\n\t\t// Update packet loss history.\n\t\tconst size_t expectedPackets = this->transportCcFeedbackPacket->GetPacketStatusCount();\n\t\tsize_t lostPackets           = 0;\n\n\t\tfor (const auto& result : this->transportCcFeedbackPacket->GetPacketResults())\n\t\t{\n\t\t\tif (!result.received)\n\t\t\t{\n\t\t\t\tlostPackets += 1;\n\t\t\t}\n\t\t}\n\n\t\tif (expectedPackets > 0)\n\t\t{\n\t\t\tthis->UpdatePacketLoss(static_cast<double>(lostPackets) / expectedPackets);\n\t\t}\n\n\t\tthis->transportCcFeedbackWideSeqNumStart = latestWideSeqNumber + 1;\n\n\t\treturn true;\n\t}\n\n\tvoid TransportCongestionControlServer::MayDropOldPacketArrivalTimes(uint16_t seqNum, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ignore nowMs value if it's smaller than PacketArrivalTimestampWindow in\n\t\t// order to avoid negative values (should never happen) and return early if\n\t\t// the condition is met.\n\t\tif (nowMs >= PacketArrivalTimestampWindow)\n\t\t{\n\t\t\tconst uint64_t expiryTimestamp = nowMs - PacketArrivalTimestampWindow;\n\t\t\tauto it                        = this->mapPacketArrivalTimes.begin();\n\n\t\t\twhile (it != this->mapPacketArrivalTimes.end() &&\n\t\t\t       it->first != this->transportCcFeedbackWideSeqNumStart &&\n\t\t\t       RTC::SeqManager<uint16_t>::IsSeqLowerThan(it->first, seqNum) &&\n\t\t\t       it->second <= expiryTimestamp)\n\t\t\t{\n\t\t\t\tit = this->mapPacketArrivalTimes.erase(it);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlServer::MaySendLimitationRembFeedback(uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\t// May fix unlimitedRembCounter.\n\t\tif (this->unlimitedRembCounter > 0u && this->maxIncomingBitrate != 0u)\n\t\t{\n\t\t\tthis->unlimitedRembCounter = 0u;\n\t\t}\n\n\t\t// In case this is the first unlimited REMB packet, send it fast.\n\t\tif (\n\t\t  ((this->bweType != RTC::BweType::REMB && this->maxIncomingBitrate != 0u) ||\n\t\t   this->unlimitedRembCounter > 0u) &&\n\t\t  (nowMs - this->limitationRembSentAtMs > LimitationRembInterval ||\n\t\t   this->unlimitedRembCounter == UnlimitedRembNumPackets))\n\t\t{\n\t\t\tMS_DEBUG_DEV(\n\t\t\t  \"sending limitation RTCP REMB packet [bitrate:%\" PRIu32 \"]\", this->maxIncomingBitrate);\n\n\t\t\tRTC::RTCP::FeedbackPsRembPacket packet(0u, 0u);\n\n\t\t\tpacket.SetBitrate(this->maxIncomingBitrate);\n\t\t\tpacket.Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\t\t// Notify the listener.\n\t\t\tthis->listener->OnTransportCongestionControlServerSendRtcpPacket(this, &packet);\n\n\t\t\tthis->limitationRembSentAtMs = nowMs;\n\n\t\t\tif (this->unlimitedRembCounter > 0u)\n\t\t\t{\n\t\t\t\tthis->unlimitedRembCounter--;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid TransportCongestionControlServer::UpdatePacketLoss(double packetLoss)\n\t{\n\t\t// Add the lost into the histogram.\n\t\tif (this->packetLossHistory.size() == PacketLossHistogramLength)\n\t\t{\n\t\t\tthis->packetLossHistory.pop_front();\n\t\t}\n\n\t\tthis->packetLossHistory.push_back(packetLoss);\n\n\t\t// Calculate a weighted average\n\t\tsize_t weight{ 0 };\n\t\tsize_t samples{ 0 };\n\t\tdouble totalPacketLoss{ 0 };\n\n\t\tfor (auto packetLossEntry : this->packetLossHistory)\n\t\t{\n\t\t\tweight++;\n\t\t\tsamples += weight;\n\t\t\ttotalPacketLoss += weight * packetLossEntry;\n\t\t}\n\n\t\t// clang-tidy \"thinks\" that this can lead to division by zero but we are\n\t\t// smarter.\n\t\t// NOLINTNEXTLINE(clang-analyzer-core.DivideZero)\n\t\tthis->packetLoss = totalPacketLoss / samples;\n\t}\n\n\tvoid TransportCongestionControlServer::ResetTransportCcFeedback(uint8_t feedbackPacketCount)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->transportCcFeedbackPacket.reset(new RTC::RTCP::FeedbackRtpTransportPacket(\n\t\t  this->transportCcFeedbackSenderSsrc, this->transportCcFeedbackMediaSsrc));\n\n\t\tthis->transportCcFeedbackPacket->SetFeedbackPacketCount(feedbackPacketCount);\n\t}\n\n\tvoid TransportCongestionControlServer::OnRembServerAvailableBitrate(\n\t  const webrtc::RemoteBitrateEstimator* /*rembServer*/,\n\t  const std::vector<uint32_t>& ssrcs,\n\t  uint32_t availableBitrate)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Limit announced bitrate if requested via API.\n\t\tif (this->maxIncomingBitrate != 0u)\n\t\t{\n\t\t\tavailableBitrate = std::min(availableBitrate, this->maxIncomingBitrate);\n\t\t}\n\n#if MS_LOG_DEV_LEVEL == 3\n\t\tstd::ostringstream ssrcsStream;\n\n\t\tif (!ssrcs.empty())\n\t\t{\n\t\t\tstd::copy(ssrcs.begin(), ssrcs.end() - 1, std::ostream_iterator<uint32_t>(ssrcsStream, \",\"));\n\t\t\tssrcsStream << ssrcs.back();\n\t\t}\n\n\t\tMS_DEBUG_DEV(\n\t\t  \"sending RTCP REMB packet [bitrate:%\" PRIu32 \", ssrcs:%s]\",\n\t\t  availableBitrate,\n\t\t  ssrcsStream.str().c_str());\n#endif\n\n\t\tRTC::RTCP::FeedbackPsRembPacket packet(0u, 0u);\n\n\t\tpacket.SetBitrate(availableBitrate);\n\t\tpacket.SetSsrcs(ssrcs);\n\t\tpacket.Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTransportCongestionControlServerSendRtcpPacket(this, &packet);\n\t}\n\n\tvoid TransportCongestionControlServer::OnTimer(TimerHandleInterface* timer)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (timer == this->transportCcFeedbackSendPeriodicTimer)\n\t\t{\n\t\t\tFillAndSendTransportCcFeedback();\n\t\t}\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TransportTuple.cpp",
    "content": "#define MS_CLASS \"RTC::TransportTuple\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/TransportTuple.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcpy()\n\nnamespace RTC\n{\n\t/* Static methods. */\n\n\tTransportTuple::Protocol TransportTuple::ProtocolFromFbs(FBS::Transport::Protocol protocol)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase FBS::Transport::Protocol::UDP:\n\t\t\t{\n\t\t\t\treturn TransportTuple::Protocol::UDP;\n\t\t\t}\n\n\t\t\tcase FBS::Transport::Protocol::TCP:\n\t\t\t{\n\t\t\t\treturn TransportTuple::Protocol::TCP;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tFBS::Transport::Protocol TransportTuple::ProtocolToFbs(TransportTuple::Protocol protocol)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (protocol)\n\t\t{\n\t\t\tcase TransportTuple::Protocol::UDP:\n\t\t\t{\n\t\t\t\treturn FBS::Transport::Protocol::UDP;\n\t\t\t}\n\n\t\t\tcase TransportTuple::Protocol::TCP:\n\t\t\t{\n\t\t\t\treturn FBS::Transport::Protocol::TCP;\n\t\t\t}\n\n\t\t\t\tNO_DEFAULT_GCC();\n\t\t}\n\t}\n\n\tuint64_t TransportTuple::GenerateFnv1aHash(const uint8_t* data, size_t size)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint64_t fnvOffsetBasis = 14695981039346656037ull;\n\t\tconst uint64_t fnvPrime       = 1099511628211ull;\n\t\tuint64_t hash                 = fnvOffsetBasis;\n\n\t\tfor (size_t i = 0; i < size; ++i)\n\t\t{\n\t\t\thash = (hash ^ data[i]) * fnvPrime;\n\t\t}\n\n\t\treturn hash;\n\t}\n\n\t/* Instance methods. */\n\n\tvoid TransportTuple::CloseTcpConnection()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->protocol == Protocol::UDP)\n\t\t{\n\t\t\tMS_ABORT(\"cannot delete a UDP socket\");\n\t\t}\n\n\t\tthis->tcpConnection->TriggerClose();\n\t}\n\n\tflatbuffers::Offset<FBS::Transport::Tuple> TransportTuple::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\tint family;\n\t\tstd::string localIp;\n\t\tuint16_t localPort;\n\n\t\tUtils::IP::GetAddressInfo(GetLocalAddress(), family, localIp, localPort);\n\n\t\tstd::string remoteIp;\n\t\tuint16_t remotePort;\n\n\t\tUtils::IP::GetAddressInfo(GetRemoteAddress(), family, remoteIp, remotePort);\n\n\t\tauto protocol = TransportTuple::ProtocolToFbs(GetProtocol());\n\n\t\treturn FBS::Transport::CreateTupleDirect(\n\t\t  builder,\n\t\t  (this->localAnnouncedAddress.empty() ? localIp : this->localAnnouncedAddress).c_str(),\n\t\t  localPort,\n\t\t  remoteIp.c_str(),\n\t\t  remotePort,\n\t\t  protocol);\n\t}\n\n\tvoid TransportTuple::Dump(int indentation) const\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DUMP_CLEAN(indentation, \"<TransportTuple>\");\n\n\t\tint family;\n\t\tstd::string ip;\n\t\tuint16_t port;\n\n\t\tUtils::IP::GetAddressInfo(GetLocalAddress(), family, ip, port);\n\n\t\tMS_DUMP_CLEAN(indentation, \"  localIp: %s\", ip.c_str());\n\t\tMS_DUMP_CLEAN(indentation, \"  localPort: %\" PRIu16, port);\n\n\t\tUtils::IP::GetAddressInfo(GetRemoteAddress(), family, ip, port);\n\n\t\tMS_DUMP_CLEAN(indentation, \"  remoteIp: %s\", ip.c_str());\n\t\tMS_DUMP_CLEAN(indentation, \"  remotePort: %\" PRIu16, port);\n\n\t\tswitch (GetProtocol())\n\t\t{\n\t\t\tcase Protocol::UDP:\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  protocol: udp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Protocol::TCP:\n\t\t\t{\n\t\t\t\tMS_DUMP_CLEAN(indentation, \"  protocol: tcp\");\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tMS_DUMP_CLEAN(indentation, \"  hash: %\" PRIu64, this->hash);\n\n\t\tMS_DUMP_CLEAN(indentation, \"</TransportTuple>\");\n\t}\n\n\tvoid TransportTuple::GenerateHash()\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* localSockAddr  = GetLocalAddress();\n\t\tconst auto* remoteSockAddr = GetRemoteAddress();\n\n\t\t// Maximum buffer length for two IPv6 addresses and ports plus protocol.\n\t\tstatic constexpr size_t BufferSize = ((16 + 2) * 2) + 1;\n\t\tuint8_t buffer[BufferSize]         = {};\n\t\tsize_t idx                         = 0;\n\n\t\tauto appendSockAddr = [&](const struct sockaddr* addr)\n\t\t{\n\t\t\tif (addr->sa_family == AF_INET)\n\t\t\t{\n\t\t\t\tconst auto* in      = reinterpret_cast<const struct sockaddr_in*>(addr);\n\t\t\t\tconst auto* ip      = reinterpret_cast<const uint8_t*>(&in->sin_addr.s_addr);\n\t\t\t\tconst uint16_t port = ntohs(in->sin_port);\n\n\t\t\t\tstd::memcpy(buffer + idx, ip, 4);\n\t\t\t\tidx += 4;\n\t\t\t\tbuffer[idx++] = (port >> 8) & 0xFF;\n\t\t\t\tbuffer[idx++] = port & 0xFF;\n\t\t\t}\n\t\t\telse if (addr->sa_family == AF_INET6)\n\t\t\t{\n\t\t\t\tconst auto* in6     = reinterpret_cast<const struct sockaddr_in6*>(addr);\n\t\t\t\tconst auto* ip      = reinterpret_cast<const uint8_t*>(&in6->sin6_addr);\n\t\t\t\tconst uint16_t port = ntohs(in6->sin6_port);\n\n\t\t\t\tstd::memcpy(buffer + idx, ip, 16);\n\t\t\t\tidx += 16;\n\t\t\t\tbuffer[idx++] = (port >> 8) & 0xFF;\n\t\t\t\tbuffer[idx++] = port & 0xFF;\n\t\t\t}\n\t\t};\n\n\t\tappendSockAddr(localSockAddr);\n\t\tappendSockAddr(remoteSockAddr);\n\n\t\tbuffer[idx] = static_cast<uint8_t>(this->protocol);\n\n\t\tthis->hash = TransportTuple::GenerateFnv1aHash(buffer, idx + 1);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/TrendCalculator.cpp",
    "content": "#define MS_CLASS \"RTC::TrendCalculator\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/TrendCalculator.hpp\"\n#include \"Logger.hpp\"\n\nnamespace RTC\n{\n\tTrendCalculator::TrendCalculator(float decreaseFactor) : decreaseFactor(decreaseFactor)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tvoid TrendCalculator::Update(uint32_t value, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->value == 0u)\n\t\t{\n\t\t\tthis->value                   = value;\n\t\t\tthis->highestValue            = value;\n\t\t\tthis->highestValueUpdatedAtMs = nowMs;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If new value is bigger or equal than current one, use it.\n\t\tif (value >= this->value)\n\t\t{\n\t\t\tthis->value                   = value;\n\t\t\tthis->highestValue            = value;\n\t\t\tthis->highestValueUpdatedAtMs = nowMs;\n\t\t}\n\t\t// Otherwise decrease current value.\n\t\telse\n\t\t{\n\t\t\tconst uint64_t elapsedMs = nowMs - this->highestValueUpdatedAtMs;\n\t\t\tauto subtraction =\n\t\t\t  static_cast<uint32_t>(this->highestValue * this->decreaseFactor * (elapsedMs / 1000.0));\n\n\t\t\tthis->value = std::max<uint32_t>(\n\t\t\t  value, this->highestValue > subtraction ? (this->highestValue - subtraction) : value);\n\t\t}\n\t}\n\n\tvoid TrendCalculator::ForceUpdate(uint32_t value, uint64_t nowMs)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->value                   = value;\n\t\tthis->highestValue            = value;\n\t\tthis->highestValueUpdatedAtMs = nowMs;\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/UdpSocket.cpp",
    "content": "#define MS_CLASS \"RTC::UdpSocket\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/UdpSocket.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/PortManager.hpp\"\n#include <string>\n\nnamespace RTC\n{\n\t/* Instance methods. */\n\n\tUdpSocket::UdpSocket(\n\t  Listener* listener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags)\n\t  : // This may throw.\n\t    ::UdpSocketHandle::UdpSocketHandle(RTC::PortManager::BindUdp(ip, port, flags)),\n\t    listener(listener),\n\t    fixedPort(true)\n\t{\n\t\tMS_TRACE();\n\t}\n\n\tUdpSocket::UdpSocket(\n\t  Listener* listener,\n\t  std::string& ip,\n\t  uint16_t minPort,\n\t  uint16_t maxPort,\n\t  RTC::Transport::SocketFlags& flags,\n\t  uint64_t& portRangeHash)\n\t  : // This may throw.\n\t    ::UdpSocketHandle::UdpSocketHandle(\n\t      RTC::PortManager::BindUdp(ip, minPort, maxPort, flags, portRangeHash)),\n\t    listener(listener)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->portRangeHash = portRangeHash;\n\t}\n\n\tUdpSocket::~UdpSocket()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->fixedPort)\n\t\t{\n\t\t\tRTC::PortManager::Unbind(this->portRangeHash, this->localPort);\n\t\t}\n\t}\n\n\tvoid UdpSocket::UserOnUdpDatagramReceived(\n\t  const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->listener)\n\t\t{\n\t\t\tMS_ERROR(\"no listener set\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Notify the reader.\n\t\tthis->listener->OnUdpSocketPacketReceived(this, data, len, bufferLen, addr);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/WebRtcServer.cpp",
    "content": "#include \"SharedInterface.hpp\"\n#define MS_CLASS \"RTC::WebRtcServer\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include \"RTC/WebRtcServer.hpp\"\n#include <cmath> // std::pow()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint16_t IceCandidateDefaultLocalPriority{ 10000 };\n\t// We just provide \"host\" candidates so type preference is fixed.\n\tstatic constexpr uint16_t IceTypePreference{ 64 };\n\t// We do not support non rtcp-mux so component is always 1.\n\tstatic constexpr uint16_t IceComponent{ 1 };\n\n\tstatic inline uint32_t generateIceCandidatePriority(uint16_t localPreference)\n\t{\n\t\tMS_TRACE();\n\n\t\treturn (std::pow(2, 24) * IceTypePreference) + (std::pow(2, 8) * localPreference) +\n\t\t       (std::pow(2, 0) * (256 - IceComponent));\n\t}\n\n\t/* Class methods. */\n\n\tinline std::string WebRtcServer::GetLocalIceUsernameFragmentFromReceivedStunPacket(\n\t  const RTC::ICE::StunPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Here we inspect the USERNAME attribute of a received STUN request and\n\t\t// extract its remote usernameFragment (the one given to our IceServer as\n\t\t// local usernameFragment) which is the first value in the attribute value\n\t\t// before the \":\" symbol.\n\n\t\tstd::string username{ packet->GetUsername() };\n\t\tconst size_t colonPos = username.find(':');\n\n\t\t// If no colon is found just return the whole USERNAME attribute anyway.\n\t\tif (colonPos == std::string::npos)\n\t\t{\n\t\t\treturn username;\n\t\t}\n\n\t\treturn username.substr(0, colonPos);\n\t}\n\n\t/* Instance methods. */\n\n\tWebRtcServer::WebRtcServer(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  const flatbuffers::Vector<flatbuffers::Offset<FBS::Transport::ListenInfo>>* listenInfos)\n\t  : id(id), shared(shared)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (listenInfos->size() == 0)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"wrong listenInfos (empty array)\");\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\tfor (const auto* listenInfo : *listenInfos)\n\t\t\t{\n\t\t\t\tauto ip = listenInfo->ip()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tUtils::IP::NormalizeIp(ip);\n\n\t\t\t\tstd::string announcedAddress;\n\n\t\t\t\tif (flatbuffers::IsFieldPresent(listenInfo, FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t\t\t{\n\t\t\t\t\tannouncedAddress = listenInfo->announcedAddress()->str();\n\t\t\t\t}\n\n\t\t\t\tconst bool exposeInternalIp = listenInfo->exposeInternalIp();\n\n\t\t\t\tRTC::Transport::SocketFlags flags;\n\n\t\t\t\tflags.ipv6Only     = listenInfo->flags()->ipv6Only();\n\t\t\t\tflags.udpReusePort = listenInfo->flags()->udpReusePort();\n\n\t\t\t\tif (listenInfo->protocol() == FBS::Transport::Protocol::UDP)\n\t\t\t\t{\n\t\t\t\t\t// This may throw.\n\t\t\t\t\tRTC::UdpSocket* udpSocket;\n\n\t\t\t\t\tif (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  listenInfo->portRange()->min(),\n\t\t\t\t\t\t  listenInfo->portRange()->max(),\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\t\t\t\t\telse if (listenInfo->port() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags);\n\t\t\t\t\t}\n\t\t\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t\t\t// required.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->udpSocketOrTcpServers.emplace_back(\n\t\t\t\t\t  udpSocket, nullptr, announcedAddress, exposeInternalIp);\n\n\t\t\t\t\tif (listenInfo->sendBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tudpSocket->SetSendBufferSize(listenInfo->sendBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->recvBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tudpSocket->SetRecvBufferSize(listenInfo->recvBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  info,\n\t\t\t\t\t  \"UDP socket send buffer size: %d, recv buffer size: %d\",\n\t\t\t\t\t  udpSocket->GetSendBufferSize(),\n\t\t\t\t\t  udpSocket->GetRecvBufferSize());\n\t\t\t\t}\n\t\t\t\telse if (listenInfo->protocol() == FBS::Transport::Protocol::TCP)\n\t\t\t\t{\n\t\t\t\t\t// This may throw.\n\t\t\t\t\tRTC::TcpServer* tcpServer;\n\n\t\t\t\t\tif (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  listenInfo->portRange()->min(),\n\t\t\t\t\t\t  listenInfo->portRange()->max(),\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\t\t\t\t\telse if (listenInfo->port() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags);\n\t\t\t\t\t}\n\t\t\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t\t\t// required.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->udpSocketOrTcpServers.emplace_back(\n\t\t\t\t\t  nullptr, tcpServer, announcedAddress, exposeInternalIp);\n\n\t\t\t\t\tif (listenInfo->sendBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\ttcpServer->SetSendBufferSize(listenInfo->sendBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->recvBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\ttcpServer->SetRecvBufferSize(listenInfo->recvBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  info,\n\t\t\t\t\t  \"TCP server send buffer size: %d, recv buffer size: %d\",\n\t\t\t\t\t  tcpServer->GetSendBufferSize(),\n\t\t\t\t\t  tcpServer->GetRecvBufferSize());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// NOTE: This may throw.\n\t\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t\t  this->id,\n\t\t\t  /*channelRequestHandler*/ this,\n\t\t\t  /*channelNotificationHandler*/ nullptr);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\t// Must delete everything since the destructor won't be called.\n\n\t\t\tfor (auto& item : this->udpSocketOrTcpServers)\n\t\t\t{\n\t\t\t\tdelete item.udpSocket;\n\t\t\t\titem.udpSocket = nullptr;\n\n\t\t\t\tdelete item.tcpServer;\n\t\t\t\titem.tcpServer = nullptr;\n\t\t\t}\n\t\t\tthis->udpSocketOrTcpServers.clear();\n\n\t\t\tthrow;\n\t\t}\n\t}\n\n\tWebRtcServer::~WebRtcServer()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->closing = true;\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\t// NOTE: We need to close WebRtcTransports first since they may need to\n\t\t// send DTLS Close Alert so UDP sockets and TCP connections must remain\n\t\t// open.\n\t\tfor (auto* webRtcTransport : this->webRtcTransports)\n\t\t{\n\t\t\twebRtcTransport->ListenServerClosed();\n\t\t}\n\t\tthis->webRtcTransports.clear();\n\n\t\tfor (auto& item : this->udpSocketOrTcpServers)\n\t\t{\n\t\t\tdelete item.udpSocket;\n\t\t\titem.udpSocket = nullptr;\n\n\t\t\tdelete item.tcpServer;\n\t\t\titem.tcpServer = nullptr;\n\t\t}\n\t\tthis->udpSocketOrTcpServers.clear();\n\t}\n\n\tflatbuffers::Offset<FBS::WebRtcServer::DumpResponse> WebRtcServer::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add udpSockets and tcpServers.\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcServer::IpPort>> udpSockets;\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcServer::IpPort>> tcpServers;\n\n\t\tfor (const auto& item : this->udpSocketOrTcpServers)\n\t\t{\n\t\t\tif (item.udpSocket)\n\t\t\t{\n\t\t\t\tudpSockets.emplace_back(\n\t\t\t\t  FBS::WebRtcServer::CreateIpPortDirect(\n\t\t\t\t    builder, item.udpSocket->GetLocalIp().c_str(), item.udpSocket->GetLocalPort()));\n\t\t\t}\n\t\t\telse if (item.tcpServer)\n\t\t\t{\n\t\t\t\ttcpServers.emplace_back(\n\t\t\t\t  FBS::WebRtcServer::CreateIpPortDirect(\n\t\t\t\t    builder, item.tcpServer->GetLocalIp().c_str(), item.tcpServer->GetLocalPort()));\n\t\t\t}\n\t\t}\n\n\t\t// Add webRtcTransportIds.\n\t\tstd::vector<flatbuffers::Offset<flatbuffers::String>> webRtcTransportIds;\n\n\t\tfor (auto* webRtcTransport : this->webRtcTransports)\n\t\t{\n\t\t\twebRtcTransportIds.emplace_back(builder.CreateString(webRtcTransport->id));\n\t\t}\n\n\t\t// Add localIceUsernameFragments.\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcServer::IceUserNameFragment>> localIceUsernameFragments;\n\n\t\tfor (const auto& kv : this->mapLocalIceUsernameFragmentWebRtcTransport)\n\t\t{\n\t\t\tconst auto& localIceUsernameFragment = kv.first;\n\t\t\tconst auto* webRtcTransport          = kv.second;\n\n\t\t\tlocalIceUsernameFragments.emplace_back(\n\t\t\t  FBS::WebRtcServer::CreateIceUserNameFragmentDirect(\n\t\t\t    builder, localIceUsernameFragment.c_str(), webRtcTransport->id.c_str()));\n\t\t}\n\n\t\t// Add tupleHashes.\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcServer::TupleHash>> tupleHashes;\n\n\t\tfor (const auto& kv : this->mapTupleWebRtcTransport)\n\t\t{\n\t\t\tconst auto& tupleHash       = kv.first;\n\t\t\tconst auto* webRtcTransport = kv.second;\n\n\t\t\ttupleHashes.emplace_back(\n\t\t\t  FBS::WebRtcServer::CreateTupleHashDirect(builder, tupleHash, webRtcTransport->id.c_str()));\n\t\t}\n\n\t\treturn FBS::WebRtcServer::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  this->id.c_str(),\n\t\t  &udpSockets,\n\t\t  &tcpServers,\n\t\t  &webRtcTransportIds,\n\t\t  &localIceUsernameFragments,\n\t\t  &tupleHashes);\n\t}\n\n\tvoid WebRtcServer::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::WEBRTCSERVER_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcServer_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tstd::vector<RTC::ICE::IceCandidate> WebRtcServer::GetIceCandidates(\n\t  bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp) const\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::vector<RTC::ICE::IceCandidate> iceCandidates;\n\t\tuint16_t iceLocalPreferenceDecrement{ 0 };\n\n\t\t// Optimistic preallocation which takes into account worst case (each\n\t\t// listening item has |exposeInternalIp| set to true).\n\t\ticeCandidates.reserve(this->udpSocketOrTcpServers.size() * 2);\n\n\t\tfor (const auto& item : this->udpSocketOrTcpServers)\n\t\t{\n\t\t\tif (item.udpSocket && enableUdp)\n\t\t\t{\n\t\t\t\tuint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;\n\n\t\t\t\tif (preferUdp)\n\t\t\t\t{\n\t\t\t\t\ticeLocalPreference += 1000;\n\t\t\t\t}\n\n\t\t\t\tconst uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);\n\n\t\t\t\tif (item.announcedAddress.empty())\n\t\t\t\t{\n\t\t\t\t\ticeCandidates.emplace_back(item.udpSocket, icePriority);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ticeCandidates.emplace_back(\n\t\t\t\t\t  item.udpSocket, icePriority, const_cast<std::string&>(item.announcedAddress));\n\n\t\t\t\t\tif (item.exposeInternalIp)\n\t\t\t\t\t{\n\t\t\t\t\t\ticeCandidates.emplace_back(item.udpSocket, icePriority - 1000);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (item.tcpServer && enableTcp)\n\t\t\t{\n\t\t\t\tuint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;\n\n\t\t\t\tif (preferTcp)\n\t\t\t\t{\n\t\t\t\t\ticeLocalPreference += 1000;\n\t\t\t\t}\n\n\t\t\t\tconst uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);\n\n\t\t\t\tif (item.announcedAddress.empty())\n\t\t\t\t{\n\t\t\t\t\ticeCandidates.emplace_back(item.tcpServer, icePriority);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\ticeCandidates.emplace_back(\n\t\t\t\t\t  item.tcpServer, icePriority, const_cast<std::string&>(item.announcedAddress));\n\n\t\t\t\t\tif (item.exposeInternalIp)\n\t\t\t\t\t{\n\t\t\t\t\t\ticeCandidates.emplace_back(item.tcpServer, icePriority - 1000);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Decrement initial ICE local preference for next IP.\n\t\t\ticeLocalPreferenceDecrement += 100;\n\t\t}\n\n\t\treturn iceCandidates;\n\t}\n\n\tinline void WebRtcServer::OnPacketReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (RTC::ICE::StunPacket::IsStun(data, len))\n\t\t{\n\t\t\tOnStunDataReceived(tuple, data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tOnNonStunDataReceived(tuple, data, len, bufferLen);\n\t\t}\n\t}\n\n\tinline void WebRtcServer::OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* packet = RTC::ICE::StunPacket::Parse(data, len);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(ice, \"ignoring wrong STUN packet received\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// First try doing lookup in the tuples table.\n\t\tauto it1 = this->mapTupleWebRtcTransport.find(tuple->hash);\n\n\t\tif (it1 != this->mapTupleWebRtcTransport.end())\n\t\t{\n\t\t\tauto* webRtcTransport = it1->second;\n\n\t\t\twebRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet);\n\n\t\t\tdelete packet;\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise try to match the local ICE username fragment.\n\t\tauto key = WebRtcServer::GetLocalIceUsernameFragmentFromReceivedStunPacket(packet);\n\t\tauto it2 = this->mapLocalIceUsernameFragmentWebRtcTransport.find(key);\n\n\t\tif (it2 == this->mapLocalIceUsernameFragmentWebRtcTransport.end())\n\t\t{\n\t\t\tMS_WARN_TAG(ice, \"ignoring received STUN packet with unknown remote ICE usernameFragment\");\n\n\t\t\tdelete packet;\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* webRtcTransport = it2->second;\n\n\t\twebRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet);\n\n\t\tdelete packet;\n\t}\n\n\tinline void WebRtcServer::OnNonStunDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tauto it = this->mapTupleWebRtcTransport.find(tuple->hash);\n\n\t\tif (it == this->mapTupleWebRtcTransport.end())\n\t\t{\n\t\t\tMS_WARN_TAG(ice, \"ignoring received non STUN data from unknown tuple\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* webRtcTransport = it->second;\n\n\t\twebRtcTransport->ProcessNonStunPacketFromWebRtcServer(tuple, data, len, bufferLen);\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->webRtcTransports.find(webRtcTransport) == this->webRtcTransports.end(),\n\t\t  \"WebRtcTransport already handled\");\n\n\t\tthis->webRtcTransports.insert(webRtcTransport);\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->webRtcTransports.find(webRtcTransport) != this->webRtcTransports.end(),\n\t\t  \"WebRtcTransport not handled\");\n\n\t\t// NOTE: If WebRtcServer is closing then do not remove the transport from\n\t\t// the set since it would modify the set while the WebRtcServer destructor\n\t\t// is iterating it.\n\t\t// See: https://github.com/versatica/mediasoup/pull/1369#issuecomment-2044672247\n\t\tif (!this->closing)\n\t\t{\n\t\t\tthis->webRtcTransports.erase(webRtcTransport);\n\t\t}\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentAdded(\n\t  RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) ==\n\t\t    this->mapLocalIceUsernameFragmentWebRtcTransport.end(),\n\t\t  \"local ICE username fragment already exists in the table\");\n\n\t\tthis->mapLocalIceUsernameFragmentWebRtcTransport[usernameFragment] = webRtcTransport;\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentRemoved(\n\t  RTC::WebRtcTransport* /*webRtcTransport*/, const std::string& usernameFragment)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) !=\n\t\t    this->mapLocalIceUsernameFragmentWebRtcTransport.end(),\n\t\t  \"local ICE username fragment not found in the table\");\n\n\t\tthis->mapLocalIceUsernameFragmentWebRtcTransport.erase(usernameFragment);\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportTransportTupleAdded(\n\t  RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->mapTupleWebRtcTransport.find(tuple->hash) != this->mapTupleWebRtcTransport.end())\n\t\t{\n\t\t\tMS_WARN_TAG(ice, \"tuple hash already exists in the table\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->mapTupleWebRtcTransport[tuple->hash] = webRtcTransport;\n\t}\n\n\tinline void WebRtcServer::OnWebRtcTransportTransportTupleRemoved(\n\t  RTC::WebRtcTransport* /*webRtcTransport*/, RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->mapTupleWebRtcTransport.find(tuple->hash) == this->mapTupleWebRtcTransport.end())\n\t\t{\n\t\t\tMS_DEBUG_TAG(ice, \"tuple hash not found in the table\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->mapTupleWebRtcTransport.erase(tuple->hash);\n\t}\n\n\tinline void WebRtcServer::OnUdpSocketPacketReceived(\n\t  RTC::UdpSocket* socket,\n\t  const uint8_t* data,\n\t  size_t len,\n\t  size_t bufferLen,\n\t  const struct sockaddr* remoteAddr)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(socket, remoteAddr);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n\n\tinline void WebRtcServer::OnRtcTcpConnectionClosed(\n\t  RTC::TcpServer* /*tcpServer*/, RTC::TcpConnection* connection)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(connection);\n\n\t\t// NOTE: We cannot assert whether this tuple is still in our\n\t\t// mapTupleWebRtcTransport because this event may be called after the tuple\n\t\t// was removed from it.\n\n\t\tauto it = this->mapTupleWebRtcTransport.find(tuple.hash);\n\n\t\tif (it == this->mapTupleWebRtcTransport.end())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* webRtcTransport = it->second;\n\n\t\twebRtcTransport->RemoveTuple(&tuple);\n\t}\n\n\tinline void WebRtcServer::OnTcpConnectionPacketReceived(\n\t  RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(connection);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/RTC/WebRtcTransport.cpp",
    "content": "#define MS_CLASS \"RTC::WebRtcTransport\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"RTC/WebRtcTransport.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include \"FBS/webRtcTransport.h\"\n#include <cmath> // std::pow()\n\nnamespace RTC\n{\n\t/* Static. */\n\n\tstatic constexpr uint16_t IceCandidateDefaultLocalPriority{ 10000 };\n\t// We just provide \"host\" candidates so type preference is fixed.\n\tstatic constexpr uint16_t IceTypePreference{ 64 };\n\t// We do not support non rtcp-mux so component is always 1.\n\tstatic constexpr uint16_t IceComponent{ 1 };\n\n\tstatic inline uint32_t generateIceCandidatePriority(uint16_t localPreference)\n\t{\n\t\tMS_TRACE();\n\n\t\treturn (std::pow(2, 24) * IceTypePreference) + (std::pow(2, 8) * localPreference) +\n\t\t       (std::pow(2, 0) * (256 - IceComponent));\n\t}\n\n\t/* Instance methods. */\n\n\t/**\n\t * This constructor is used when the WebRtcTransport doesn't use a WebRtcServer.\n\t */\n\tWebRtcTransport::WebRtcTransport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  const FBS::WebRtcTransport::WebRtcTransportOptions* options)\n\t  : RTC::Transport::Transport(shared, id, listener, options->base())\n\t{\n\t\tMS_TRACE();\n\n\t\ttry\n\t\t{\n\t\t\tconst auto* listenIndividual = options->listen_as<FBS::WebRtcTransport::ListenIndividual>();\n\t\t\tconst auto* listenInfos      = listenIndividual->listenInfos();\n\t\t\tuint16_t iceLocalPreferenceDecrement{ 0u };\n\n\t\t\t// Multiply by 2 to preallocate space in case |exposeInternalIp| is set.\n\t\t\tthis->iceCandidates.reserve(listenInfos->size() * 2);\n\n\t\t\tfor (const auto* listenInfo : *listenInfos)\n\t\t\t{\n\t\t\t\tauto ip = listenInfo->ip()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tUtils::IP::NormalizeIp(ip);\n\n\t\t\t\tstd::string announcedAddress;\n\n\t\t\t\tif (flatbuffers::IsFieldPresent(listenInfo, FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS))\n\t\t\t\t{\n\t\t\t\t\tannouncedAddress = listenInfo->announcedAddress()->str();\n\t\t\t\t}\n\n\t\t\t\tconst bool exposeInternalIp = listenInfo->exposeInternalIp();\n\n\t\t\t\tRTC::Transport::SocketFlags flags;\n\n\t\t\t\tflags.ipv6Only     = listenInfo->flags()->ipv6Only();\n\t\t\t\tflags.udpReusePort = listenInfo->flags()->udpReusePort();\n\n\t\t\t\tconst uint16_t iceLocalPreference =\n\t\t\t\t  IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement;\n\t\t\t\tconst uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference);\n\n\t\t\t\tif (listenInfo->protocol() == FBS::Transport::Protocol::UDP)\n\t\t\t\t{\n\t\t\t\t\tRTC::UdpSocket* udpSocket;\n\n\t\t\t\t\tif (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  listenInfo->portRange()->min(),\n\t\t\t\t\t\t  listenInfo->portRange()->max(),\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\t\t\t\t\telse if (listenInfo->port() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags);\n\t\t\t\t\t}\n\t\t\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t\t\t// required.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\tudpSocket = new RTC::UdpSocket(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->udpSockets[udpSocket] = announcedAddress;\n\n\t\t\t\t\tif (announcedAddress.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->iceCandidates.emplace_back(udpSocket, icePriority);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->iceCandidates.emplace_back(udpSocket, icePriority, announcedAddress);\n\n\t\t\t\t\t\tif (exposeInternalIp)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->iceCandidates.emplace_back(udpSocket, icePriority - 1000);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->sendBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\t\tudpSocket->SetSendBufferSize(listenInfo->sendBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->recvBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\t\tudpSocket->SetRecvBufferSize(listenInfo->recvBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  info,\n\t\t\t\t\t  \"UDP socket buffer sizes [send:%\" PRIu32 \", recv:%\" PRIu32 \"]\",\n\t\t\t\t\t  udpSocket->GetSendBufferSize(),\n\t\t\t\t\t  udpSocket->GetRecvBufferSize());\n\t\t\t\t}\n\t\t\t\telse if (listenInfo->protocol() == FBS::Transport::Protocol::TCP)\n\t\t\t\t{\n\t\t\t\t\tRTC::TcpServer* tcpServer;\n\n\t\t\t\t\tif (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  listenInfo->portRange()->min(),\n\t\t\t\t\t\t  listenInfo->portRange()->max(),\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\t\t\t\t\telse if (listenInfo->port() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags);\n\t\t\t\t\t}\n\t\t\t\t\t// NOTE: This is temporal to allow deprecated usage of worker port range.\n\t\t\t\t\t// In the future this should throw since |port| or |portRange| will be\n\t\t\t\t\t// required.\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tuint64_t portRangeHash{ 0u };\n\n\t\t\t\t\t\ttcpServer = new RTC::TcpServer(\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  this,\n\t\t\t\t\t\t  ip,\n\t\t\t\t\t\t  Settings::configuration.rtcMinPort,\n\t\t\t\t\t\t  Settings::configuration.rtcMaxPort,\n\t\t\t\t\t\t  flags,\n\t\t\t\t\t\t  portRangeHash);\n\t\t\t\t\t}\n\n\t\t\t\t\tthis->tcpServers[tcpServer] = announcedAddress;\n\n\t\t\t\t\tif (announcedAddress.empty())\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->iceCandidates.emplace_back(tcpServer, icePriority);\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->iceCandidates.emplace_back(tcpServer, icePriority, announcedAddress);\n\n\t\t\t\t\t\tif (exposeInternalIp)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tthis->iceCandidates.emplace_back(tcpServer, icePriority - 1000);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->sendBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\t\ttcpServer->SetSendBufferSize(listenInfo->sendBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tif (listenInfo->recvBufferSize() != 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t// NOTE: This may throw.\n\t\t\t\t\t\ttcpServer->SetRecvBufferSize(listenInfo->recvBufferSize());\n\t\t\t\t\t}\n\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  info,\n\t\t\t\t\t  \"TCP sockets buffer sizes [send:%\" PRIu32 \", recv:%\" PRIu32 \"]\",\n\t\t\t\t\t  tcpServer->GetSendBufferSize(),\n\t\t\t\t\t  tcpServer->GetRecvBufferSize());\n\t\t\t\t}\n\n\t\t\t\t// Decrement initial ICE local preference for next IP.\n\t\t\t\ticeLocalPreferenceDecrement += 100;\n\t\t\t}\n\n\t\t\tauto iceConsentTimeout = options->iceConsentTimeout();\n\n\t\t\t// Create a ICE server.\n\t\t\tthis->iceServer = new RTC::ICE::IceServer(\n\t\t\t  this,\n\t\t\t  this->shared,\n\t\t\t  Utils::Crypto::GetRandomString(32),\n\t\t\t  Utils::Crypto::GetRandomString(32),\n\t\t\t  iceConsentTimeout);\n\n\t\t\t// Create a DTLS transport.\n\t\t\tthis->dtlsTransport = new RTC::DtlsTransport(this, this->shared);\n\n\t\t\t// NOTE: This may throw.\n\t\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t\t  this->id,\n\t\t\t  /*channelRequestHandler*/ this,\n\t\t\t  /*channelNotificationHandler*/ this);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\t// Must delete everything since the destructor won't be called.\n\n\t\t\tdelete this->dtlsTransport;\n\t\t\tthis->dtlsTransport = nullptr;\n\n\t\t\tdelete this->iceServer;\n\t\t\tthis->iceServer = nullptr;\n\n\t\t\tfor (auto& kv : this->udpSockets)\n\t\t\t{\n\t\t\t\tauto* udpSocket = kv.first;\n\n\t\t\t\tdelete udpSocket;\n\t\t\t}\n\t\t\tthis->udpSockets.clear();\n\n\t\t\tfor (auto& kv : this->tcpServers)\n\t\t\t{\n\t\t\t\tauto* tcpServer = kv.first;\n\n\t\t\t\tdelete tcpServer;\n\t\t\t}\n\t\t\tthis->tcpServers.clear();\n\n\t\t\tthis->iceCandidates.clear();\n\n\t\t\tthrow;\n\t\t}\n\t}\n\n\t/**\n\t * This constructor is used when the WebRtcTransport uses a WebRtcServer.\n\t */\n\tWebRtcTransport::WebRtcTransport(\n\t  SharedInterface* shared,\n\t  const std::string& id,\n\t  RTC::Transport::Listener* listener,\n\t  WebRtcTransportListener* webRtcTransportListener,\n\t  const std::vector<RTC::ICE::IceCandidate>& iceCandidates,\n\t  const FBS::WebRtcTransport::WebRtcTransportOptions* options)\n\t  : RTC::Transport::Transport(shared, id, listener, options->base()),\n\t    webRtcTransportListener(webRtcTransportListener),\n\t    iceCandidates(iceCandidates)\n\t{\n\t\tMS_TRACE();\n\n\t\ttry\n\t\t{\n\t\t\tif (iceCandidates.empty())\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"empty iceCandidates\");\n\t\t\t}\n\n\t\t\tauto iceConsentTimeout = options->iceConsentTimeout();\n\n\t\t\t// Create a ICE server.\n\t\t\tthis->iceServer = new RTC::ICE::IceServer(\n\t\t\t  this,\n\t\t\t  this->shared,\n\t\t\t  Utils::Crypto::GetRandomString(32),\n\t\t\t  Utils::Crypto::GetRandomString(32),\n\t\t\t  iceConsentTimeout);\n\n\t\t\t// Create a DTLS transport.\n\t\t\tthis->dtlsTransport = new RTC::DtlsTransport(this, this->shared);\n\n\t\t\t// Notify the webRtcTransportListener.\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportCreated(this);\n\n\t\t\t// NOTE: This may throw.\n\t\t\tthis->shared->GetChannelMessageRegistrator()->RegisterHandler(\n\t\t\t  this->id,\n\t\t\t  /*channelRequestHandler*/ this,\n\t\t\t  /*channelNotificationHandler*/ this);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\t// Must delete everything since the destructor won't be called.\n\n\t\t\tdelete this->dtlsTransport;\n\t\t\tthis->dtlsTransport = nullptr;\n\n\t\t\tdelete this->iceServer;\n\t\t\tthis->iceServer = nullptr;\n\n\t\t\tthrow;\n\t\t}\n\t}\n\n\tWebRtcTransport::~WebRtcTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// We need to tell the Transport parent class that we are about to destroy\n\t\t// the class instance. This is because child's destructor runs before\n\t\t// parent's destructor. See comment in Transport::OnSctpAssociationSendData().\n\t\tSetDestroying();\n\n\t\tthis->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id);\n\n\t\t// Must delete the DTLS transport first since it will generate a DTLS alert\n\t\t// to be sent.\n\t\tdelete this->dtlsTransport;\n\t\tthis->dtlsTransport = nullptr;\n\n\t\tdelete this->iceServer;\n\t\tthis->iceServer = nullptr;\n\n\t\tfor (auto& kv : this->udpSockets)\n\t\t{\n\t\t\tauto* udpSocket = kv.first;\n\n\t\t\tdelete udpSocket;\n\t\t}\n\t\tthis->udpSockets.clear();\n\n\t\tfor (auto& kv : this->tcpServers)\n\t\t{\n\t\t\tauto* tcpServer = kv.first;\n\n\t\t\tdelete tcpServer;\n\t\t}\n\t\tthis->tcpServers.clear();\n\n\t\tthis->iceCandidates.clear();\n\n\t\tdelete this->srtpSendSession;\n\t\tthis->srtpSendSession = nullptr;\n\n\t\tdelete this->srtpRecvSession;\n\t\tthis->srtpRecvSession = nullptr;\n\n\t\t// Notify the webRtcTransportListener.\n\t\tif (this->webRtcTransportListener)\n\t\t{\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportClosed(this);\n\t\t}\n\t}\n\n\tflatbuffers::Offset<FBS::WebRtcTransport::DumpResponse> WebRtcTransport::FillBuffer(\n\t  flatbuffers::FlatBufferBuilder& builder) const\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add iceParameters.\n\t\tauto iceParameters = FBS::WebRtcTransport::CreateIceParametersDirect(\n\t\t  builder,\n\t\t  this->iceServer->GetUsernameFragment().c_str(),\n\t\t  this->iceServer->GetPassword().c_str(),\n\t\t  true);\n\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcTransport::IceCandidate>> iceCandidates;\n\t\ticeCandidates.reserve(this->iceCandidates.size());\n\n\t\tfor (const auto& iceCandidate : this->iceCandidates)\n\t\t{\n\t\t\ticeCandidates.emplace_back(iceCandidate.FillBuffer(builder));\n\t\t}\n\n\t\t// Add iceState.\n\t\tauto iceState = RTC::ICE::IceServer::IceStateToFbs(this->iceServer->GetState());\n\n\t\t// Add iceSelectedTuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> iceSelectedTuple;\n\n\t\tif (this->iceServer->GetSelectedTuple())\n\t\t{\n\t\t\ticeSelectedTuple = this->iceServer->GetSelectedTuple()->FillBuffer(builder);\n\t\t}\n\n\t\t// Add dtlsParameters.fingerprints.\n\t\tstd::vector<flatbuffers::Offset<FBS::WebRtcTransport::Fingerprint>> fingerprints;\n\n\t\tfor (const auto& fingerprint : RTC::DtlsTransport::GetLocalFingerprints())\n\t\t{\n\t\t\tauto algorithm    = DtlsTransport::AlgorithmToFbs(fingerprint.algorithm);\n\t\t\tconst auto& value = fingerprint.value;\n\n\t\t\tfingerprints.emplace_back(\n\t\t\t  FBS::WebRtcTransport::CreateFingerprintDirect(builder, algorithm, value.c_str()));\n\t\t}\n\n\t\t// Add dtlsParameters.role.\n\t\tauto dtlsRole  = DtlsTransport::RoleToFbs(this->dtlsRole);\n\t\tauto dtlsState = DtlsTransport::StateToFbs(this->dtlsTransport->GetState());\n\n\t\t// Add base transport dump.\n\t\tauto base = Transport::FillBuffer(builder);\n\t\t// Add dtlsParameters.\n\t\tauto dtlsParameters =\n\t\t  FBS::WebRtcTransport::CreateDtlsParametersDirect(builder, &fingerprints, dtlsRole);\n\n\t\treturn FBS::WebRtcTransport::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  base,\n\t\t  FBS::WebRtcTransport::IceRole::CONTROLLED,\n\t\t  iceParameters,\n\t\t  &iceCandidates,\n\t\t  iceState,\n\t\t  iceSelectedTuple,\n\t\t  dtlsParameters,\n\t\t  dtlsState);\n\t}\n\n\tflatbuffers::Offset<FBS::WebRtcTransport::GetStatsResponse> WebRtcTransport::FillBufferStats(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Add iceState.\n\t\tauto iceState = RTC::ICE::IceServer::IceStateToFbs(this->iceServer->GetState());\n\n\t\t// Add iceSelectedTuple.\n\t\tflatbuffers::Offset<FBS::Transport::Tuple> iceSelectedTuple;\n\n\t\tif (this->iceServer->GetSelectedTuple())\n\t\t{\n\t\t\ticeSelectedTuple = this->iceServer->GetSelectedTuple()->FillBuffer(builder);\n\t\t}\n\n\t\tauto dtlsState = DtlsTransport::StateToFbs(this->dtlsTransport->GetState());\n\n\t\t// Base Transport stats.\n\t\tauto base = Transport::FillBufferStats(builder);\n\n\t\treturn FBS::WebRtcTransport::CreateGetStatsResponse(\n\t\t  builder,\n\t\t  base,\n\t\t  // iceRole (we are always \"controlled\").\n\t\t  FBS::WebRtcTransport::IceRole::CONTROLLED,\n\t\t  iceState,\n\t\t  iceSelectedTuple,\n\t\t  dtlsState);\n\t}\n\n\tvoid WebRtcTransport::HandleRequest(Channel::ChannelRequest* request)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (request->method)\n\t\t{\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_GET_STATS:\n\t\t\t{\n\t\t\t\tauto responseOffset = FillBufferStats(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcTransport_GetStatsResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_DUMP:\n\t\t\t{\n\t\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::WEBRTCTRANSPORT_CONNECT:\n\t\t\t{\n\t\t\t\t// Ensure this method is not called twice.\n\t\t\t\tif (this->connectCalled)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"connect() already called\");\n\t\t\t\t}\n\n\t\t\t\tconst auto* body = request->data->body_as<FBS::WebRtcTransport::ConnectRequest>();\n\n\t\t\t\tconst auto* dtlsParameters = body->dtlsParameters();\n\n\t\t\t\tRTC::DtlsTransport::Fingerprint dtlsRemoteFingerprint;\n\t\t\t\tRTC::DtlsTransport::Role dtlsRemoteRole;\n\n\t\t\t\tif (dtlsParameters->fingerprints()->size() == 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"empty dtlsParameters.fingerprints array\");\n\t\t\t\t}\n\n\t\t\t\t// NOTE: Just take the first fingerprint.\n\t\t\t\tfor (const auto& fingerprint : *dtlsParameters->fingerprints())\n\t\t\t\t{\n\t\t\t\t\tdtlsRemoteFingerprint.algorithm = DtlsTransport::AlgorithmFromFbs(fingerprint->algorithm());\n\n\t\t\t\t\tdtlsRemoteFingerprint.value = fingerprint->value()->str();\n\n\t\t\t\t\t// Just use the first fingerprint.\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdtlsRemoteRole = RTC::DtlsTransport::RoleFromFbs(dtlsParameters->role());\n\n\t\t\t\t// Set local DTLS role.\n\t\t\t\tswitch (dtlsRemoteRole)\n\t\t\t\t{\n\t\t\t\t\tcase RTC::DtlsTransport::Role::CLIENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->dtlsRole = RTC::DtlsTransport::Role::SERVER;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t// If the peer has role \"auto\" we become \"client\" since we are ICE controlled.\n\t\t\t\t\tcase RTC::DtlsTransport::Role::SERVER:\n\t\t\t\t\tcase RTC::DtlsTransport::Role::AUTO:\n\t\t\t\t\t{\n\t\t\t\t\t\tthis->dtlsRole = RTC::DtlsTransport::Role::CLIENT;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis->connectCalled = true;\n\n\t\t\t\t// Pass the remote fingerprint to the DTLS transport.\n\t\t\t\tif (this->dtlsTransport->SetRemoteFingerprint(dtlsRemoteFingerprint))\n\t\t\t\t{\n\t\t\t\t\t// If everything is fine, we may run the DTLS transport if ready.\n\t\t\t\t\tMayRunDtlsTransport();\n\t\t\t\t}\n\n\t\t\t\t// Tell the caller about the selected local DTLS role.\n\t\t\t\tauto dtlsLocalRole = DtlsTransport::RoleToFbs(this->dtlsRole);\n\n\t\t\t\tauto responseOffset =\n\t\t\t\t  FBS::WebRtcTransport::CreateConnectResponse(request->GetBufferBuilder(), dtlsLocalRole);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::WebRtcTransport_ConnectResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase Channel::ChannelRequest::Method::TRANSPORT_RESTART_ICE:\n\t\t\t{\n\t\t\t\tconst std::string usernameFragment = Utils::Crypto::GetRandomString(32);\n\t\t\t\tconst std::string password         = Utils::Crypto::GetRandomString(32);\n\n\t\t\t\tthis->iceServer->RestartIce(usernameFragment, password);\n\n\t\t\t\tMS_DEBUG_DEV(\n\t\t\t\t  \"WebRtcTransport ICE usernameFragment and password changed [id:%s]\", this->id.c_str());\n\n\t\t\t\t// Reply with the updated ICE local parameters.\n\t\t\t\tauto responseOffset = FBS::Transport::CreateRestartIceResponseDirect(\n\t\t\t\t  request->GetBufferBuilder(),\n\t\t\t\t  this->iceServer->GetUsernameFragment().c_str(),\n\t\t\t\t  this->iceServer->GetPassword().c_str(),\n\t\t\t\t  true /* iceLite */\n\t\t\t\t);\n\n\t\t\t\trequest->Accept(FBS::Response::Body::Transport_RestartIceResponse, responseOffset);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\t// Pass it to the parent class.\n\t\t\t\tRTC::Transport::HandleRequest(request);\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid WebRtcTransport::HandleNotification(Channel::ChannelNotification* notification)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Pass it to the parent class.\n\t\tRTC::Transport::HandleNotification(notification);\n\t}\n\n\tvoid WebRtcTransport::ProcessStunPacketFromWebRtcServer(\n\t  RTC::TransportTuple* tuple, const RTC::ICE::StunPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Pass it to the IceServer.\n\t\tthis->iceServer->ProcessStunPacket(packet, tuple);\n\t}\n\n\tvoid WebRtcTransport::ProcessNonStunPacketFromWebRtcServer(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Increase receive transmission.\n\t\tRTC::Transport::DataReceived(len);\n\n\t\t// Check if it's RTCP.\n\t\tif (RTC::RTCP::Packet::IsRtcp(data, len))\n\t\t{\n\t\t\tOnRtcpDataReceived(tuple, data, len);\n\t\t}\n\t\t// Check if it's RTP.\n\t\telse if (RTC::RTP::Packet::IsRtp(data, len))\n\t\t{\n\t\t\tOnRtpDataReceived(tuple, data, len, bufferLen);\n\t\t}\n\t\t// Check if it's DTLS.\n\t\telse if (RTC::DtlsTransport::IsDtls(data, len))\n\t\t{\n\t\t\tOnDtlsDataReceived(tuple, data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring received packet of unknown type\");\n\t\t}\n\t}\n\n\tvoid WebRtcTransport::RemoveTuple(RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->iceServer->RemoveTuple(tuple);\n\t}\n\n\tinline bool WebRtcTransport::IsConnected() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn (\n\t\t  (this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED ||\n\t\t   this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED) &&\n\t\t  this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED);\n\t}\n\n\tvoid WebRtcTransport::MayRunDtlsTransport()\n\t{\n\t\tMS_TRACE();\n\n\t\t// Dont' start DTLS handshake if ICE is not connected/completed.\n\t\tif (!this->iceServer->GetSelectedTuple())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Do nothing if we have the same local DTLS role as the DTLS transport.\n\t\t// NOTE: local role in DTLS transport can be NONE, but not ours.\n\t\tif (this->dtlsTransport->GetLocalRole() == this->dtlsRole)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Check our local DTLS role.\n\t\tswitch (this->dtlsRole)\n\t\t{\n\t\t\t// If still 'auto' then transition to 'server' if ICE is 'connected' or\n\t\t\t// 'completed'.\n\t\t\tcase RTC::DtlsTransport::Role::AUTO:\n\t\t\t{\n\t\t\t\tif (\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED ||\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(\n\t\t\t\t\t  dtls, \"transition from DTLS local role 'auto' to 'server' and running DTLS transport\");\n\n\t\t\t\t\tthis->dtlsRole = RTC::DtlsTransport::Role::SERVER;\n\t\t\t\t\tthis->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// 'client' is only set if a 'connect' request was previously called with\n\t\t\t// remote DTLS role 'server'.\n\t\t\t//\n\t\t\t// If 'client' then wait for ICE to be 'completed' (got USE-CANDIDATE).\n\t\t\t//\n\t\t\t// NOTE: This is the theory, however let's be more flexible as told here:\n\t\t\t//   https://bugs.chromium.org/p/webrtc/issues/detail?id=3661\n\t\t\tcase RTC::DtlsTransport::Role::CLIENT:\n\t\t\t{\n\t\t\t\tif (\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED ||\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(dtls, \"running DTLS transport in local role 'client'\");\n\n\t\t\t\t\tthis->dtlsTransport->Run(RTC::DtlsTransport::Role::CLIENT);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If 'server' then run the DTLS transport if ICE is 'connected' (not yet\n\t\t\t// USE-CANDIDATE) or 'completed'.\n\t\t\tcase RTC::DtlsTransport::Role::SERVER:\n\t\t\t{\n\t\t\t\tif (\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED ||\n\t\t\t\t  this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED)\n\t\t\t\t{\n\t\t\t\t\tMS_DEBUG_TAG(dtls, \"running DTLS transport in local role 'server'\");\n\n\t\t\t\t\tthis->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tvoid WebRtcTransport::SendRtpPacket(\n\t  RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure there is sending SRTP session.\n\t\tif (!this->srtpSendSession)\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring RTP packet due to non sending SRTP session\");\n\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetBuffer();\n\t\tauto len            = packet->GetLength();\n\n\t\tif (!this->srtpSendSession->EncryptRtp(&data, &len))\n\t\t{\n\t\t\tif (cb)\n\t\t\t{\n\t\t\t\t(*cb)(false);\n\t\t\t\tdelete cb;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->iceServer->GetSelectedTuple()->Send(data, len, cb);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid WebRtcTransport::SendRtcpPacket(RTC::RTCP::Packet* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\t// Ensure there is sending SRTP session.\n\t\tif (!this->srtpSendSession)\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring RTCP packet due to non sending SRTP session\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->iceServer->GetSelectedTuple()->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tvoid WebRtcTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tpacket->Serialize(RTC::RTCP::SerializationBuffer);\n\n\t\tconst uint8_t* data = packet->GetData();\n\t\tauto len            = packet->GetSize();\n\n\t\t// Ensure there is sending SRTP session.\n\t\tif (!this->srtpSendSession)\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"ignoring RTCP compound packet due to non sending SRTP session\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this->srtpSendSession->EncryptRtcp(&data, &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tthis->iceServer->GetSelectedTuple()->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\t// TODO: SCTP: Remove once we only use built-in SCTP stack.\n\tvoid WebRtcTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, msg, len, ppid, cb);\n\t}\n\n\tvoid WebRtcTransport::SendMessage(\n\t  RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb)\n\t{\n\t\tMS_TRACE();\n\n\t\tSendSctpMessage(dataConsumer, std::move(message), cb);\n\t}\n\n\tbool WebRtcTransport::SendData(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!IsConnected())\n\t\t{\n\t\t\tMS_WARN_TAG(sctp, \"DTLS not connected, cannot send SCTP data\");\n\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this->dtlsTransport->SendApplicationData(data, len);\n\t}\n\n\tvoid WebRtcTransport::RecvStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpRecvSession)\n\t\t{\n\t\t\tthis->srtpRecvSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tvoid WebRtcTransport::SendStreamClosed(uint32_t ssrc)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->srtpSendSession)\n\t\t{\n\t\t\tthis->srtpSendSession->RemoveStream(ssrc);\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnPacketReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Increase receive transmission.\n\t\tRTC::Transport::DataReceived(len);\n\n\t\t// Check if it's STUN.\n\t\tif (RTC::ICE::StunPacket::IsStun(data, len))\n\t\t{\n\t\t\tOnStunDataReceived(tuple, data, len);\n\t\t}\n\t\t// Check if it's RTCP.\n\t\telse if (RTC::RTCP::Packet::IsRtcp(data, len))\n\t\t{\n\t\t\tOnRtcpDataReceived(tuple, data, len);\n\t\t}\n\t\t// Check if it's RTP.\n\t\telse if (RTC::RTP::Packet::IsRtp(data, len))\n\t\t{\n\t\t\tOnRtpDataReceived(tuple, data, len, bufferLen);\n\t\t}\n\t\t// Check if it's DTLS.\n\t\telse if (RTC::DtlsTransport::IsDtls(data, len))\n\t\t{\n\t\t\tOnDtlsDataReceived(tuple, data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring received packet of unknown type\");\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnStunDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* packet = RTC::ICE::StunPacket::Parse(data, len);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring wrong STUN packet received\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass it to the IceServer.\n\t\tthis->iceServer->ProcessStunPacket(packet, tuple);\n\n\t\tdelete packet;\n\t}\n\n\tinline void WebRtcTransport::OnDtlsDataReceived(\n\t  const RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure it comes from a valid tuple.\n\t\tif (!this->iceServer->IsValidTuple(tuple))\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"ignoring DTLS data coming from an invalid tuple\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Trick for clients performing aggressive ICE regardless we are ICE-Lite.\n\t\tthis->iceServer->MayForceSelectedTuple(tuple);\n\n\t\t// Check that DTLS status is 'connecting' or 'connected'.\n\t\tif (\n\t\t  this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTING ||\n\t\t  this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"DTLS data received, passing it to the DTLS transport\");\n\n\t\t\tthis->dtlsTransport->ProcessDtlsData(data, len);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"Transport is not 'connecting' or 'connected', ignoring received DTLS data\");\n\n\t\t\treturn;\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnRtpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure DTLS is connected.\n\t\tif (this->dtlsTransport->GetState() != RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tMS_DEBUG_2TAGS(dtls, rtp, \"ignoring RTP packet while DTLS not connected\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure there is receiving SRTP session.\n\t\tif (!this->srtpRecvSession)\n\t\t{\n\t\t\tMS_DEBUG_TAG(srtp, \"ignoring RTP packet due to non receiving SRTP session\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure it comes from a valid tuple.\n\t\tif (!this->iceServer->IsValidTuple(tuple))\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"ignoring RTP packet coming from an invalid tuple\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTP packet.\n\t\tif (!this->srtpRecvSession->DecryptSrtp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\tconst auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\t\tif (!packet)\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(srtp, \"DecryptSrtp() failed due to an invalid RTP packet\");\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tMS_WARN_TAG(\n\t\t\t\t  srtp,\n\t\t\t\t  \"DecryptSrtp() failed [ssrc:%\" PRIu32 \", payloadType:%\" PRIu8 \", seq:%\" PRIu16 \"]\",\n\t\t\t\t  packet->GetSsrc(),\n\t\t\t\t  packet->GetPayloadType(),\n\t\t\t\t  packet->GetSequenceNumber());\n\n\t\t\t\tdelete packet;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtp, \"received data is not a valid RTP packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Trick for clients performing aggressive ICE regardless we are ICE-Lite.\n\t\tthis->iceServer->MayForceSelectedTuple(tuple);\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtpPacket(packet);\n\t}\n\n\tinline void WebRtcTransport::OnRtcpDataReceived(\n\t  RTC::TransportTuple* tuple, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Ensure DTLS is connected.\n\t\tif (this->dtlsTransport->GetState() != RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tMS_DEBUG_2TAGS(dtls, rtcp, \"ignoring RTCP packet while DTLS not connected\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure there is receiving SRTP session.\n\t\tif (!this->srtpRecvSession)\n\t\t{\n\t\t\tMS_DEBUG_TAG(srtp, \"ignoring RTCP packet due to non receiving SRTP session\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Ensure it comes from a valid tuple.\n\t\tif (!this->iceServer->IsValidTuple(tuple))\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"ignoring RTCP packet coming from an invalid tuple\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Decrypt the SRTCP packet.\n\t\tif (!this->srtpRecvSession->DecryptSrtcp(const_cast<uint8_t*>(data), &len))\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tauto* packet = RTC::RTCP::Packet::Parse(data, len);\n\n\t\tif (!packet)\n\t\t{\n\t\t\tMS_WARN_TAG(rtcp, \"received data is not a valid RTCP compound or single packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass the packet to the parent transport.\n\t\tRTC::Transport::ReceiveRtcpPacket(packet);\n\t}\n\n\tinline void WebRtcTransport::OnUdpSocketPacketReceived(\n\t  RTC::UdpSocket* socket,\n\t  const uint8_t* data,\n\t  size_t len,\n\t  size_t bufferLen,\n\t  const struct sockaddr* remoteAddr)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(socket, remoteAddr);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n\n\tinline void WebRtcTransport::OnRtcTcpConnectionClosed(\n\t  RTC::TcpServer* /*tcpServer*/, RTC::TcpConnection* connection)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(connection);\n\n\t\tthis->iceServer->RemoveTuple(&tuple);\n\t}\n\n\tinline void WebRtcTransport::OnTcpConnectionPacketReceived(\n\t  RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tRTC::TransportTuple tuple(connection);\n\n\t\tOnPacketReceived(&tuple, data, len, bufferLen);\n\t}\n\n\tinline void WebRtcTransport::OnIceServerSendStunPacket(\n\t  const RTC::ICE::IceServer* /*iceServer*/,\n\t  const RTC::ICE::StunPacket* packet,\n\t  RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Send the STUN response over the same transport tuple.\n\t\ttuple->Send(packet->GetBuffer(), packet->GetLength());\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(packet->GetLength());\n\t}\n\n\tinline void WebRtcTransport::OnIceServerLocalUsernameFragmentAdded(\n\t  const RTC::ICE::IceServer* /*iceServer*/, const std::string& usernameFragment)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->webRtcTransportListener)\n\t\t{\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentAdded(\n\t\t\t  this, usernameFragment);\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerLocalUsernameFragmentRemoved(\n\t  const RTC::ICE::IceServer* /*iceServer*/, const std::string& usernameFragment)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->webRtcTransportListener)\n\t\t{\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentRemoved(\n\t\t\t  this, usernameFragment);\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerTupleAdded(\n\t  const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->webRtcTransportListener)\n\t\t{\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportTransportTupleAdded(this, tuple);\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerTupleRemoved(\n\t  const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* tuple)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->webRtcTransportListener)\n\t\t{\n\t\t\tthis->webRtcTransportListener->OnWebRtcTransportTransportTupleRemoved(this, tuple);\n\t\t}\n\n\t\t// If this is a TCP tuple, close its underlaying TCP connection.\n\t\tif (tuple->GetProtocol() == RTC::TransportTuple::Protocol::TCP)\n\t\t{\n\t\t\ttuple->CloseTcpConnection();\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerSelectedTuple(\n\t  const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* /*tuple*/)\n\t{\n\t\tMS_TRACE();\n\n\t\t/*\n\t\t * RFC 5245 section 11.2 \"Receiving Media\":\n\t\t *\n\t\t * ICE implementations MUST be prepared to receive media on each component\n\t\t * on any candidates provided for that component.\n\t\t */\n\n\t\tMS_DEBUG_TAG(ice, \"ICE selected tuple\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto tuple = this->iceServer->GetSelectedTuple()->FillBuffer(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder());\n\n\t\tauto notification = FBS::WebRtcTransport::CreateIceSelectedTupleChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(), tuple);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_IceSelectedTupleChangeNotification,\n\t\t  notification);\n\t}\n\n\tinline void WebRtcTransport::OnIceServerConnected(const RTC::ICE::IceServer* /*iceServer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(ice, \"ICE connected\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::IceState::CONNECTED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification,\n\t\t  iceStateChangeOffset);\n\n\t\t// If ready, run the DTLS handler.\n\t\tMayRunDtlsTransport();\n\n\t\t// If DTLS was already connected, notify the parent class.\n\t\tif (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tRTC::Transport::Connected();\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerCompleted(const RTC::ICE::IceServer* /*iceServer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(ice, \"ICE completed\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::IceState::COMPLETED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification,\n\t\t  iceStateChangeOffset);\n\n\t\t// If ready, run the DTLS handler.\n\t\tMayRunDtlsTransport();\n\n\t\t// If DTLS was already connected, notify the parent class.\n\t\tif (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tRTC::Transport::Connected();\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnIceServerDisconnected(const RTC::ICE::IceServer* /*iceServer*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(ice, \"ICE disconnected\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::IceState::DISCONNECTED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification,\n\t\t  iceStateChangeOffset);\n\n\t\t// If DTLS was already connected, notify the parent class.\n\t\tif (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED)\n\t\t{\n\t\t\tRTC::Transport::Disconnected();\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportConnecting(const RTC::DtlsTransport* /*dtlsTransport*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(dtls, \"DTLS connecting\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::DtlsState::CONNECTING);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification,\n\t\t  dtlsStateChangeOffset);\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportConnected(\n\t  const RTC::DtlsTransport* /*dtlsTransport*/,\n\t  RTC::SrtpSession::CryptoSuite srtpCryptoSuite,\n\t  uint8_t* srtpLocalKey,\n\t  size_t srtpLocalKeyLen,\n\t  uint8_t* srtpRemoteKey,\n\t  size_t srtpRemoteKeyLen,\n\t  std::string& remoteCert)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_DEBUG_TAG(dtls, \"DTLS connected\");\n\n\t\t// Close it if it was already set and update it.\n\t\tdelete this->srtpSendSession;\n\t\tthis->srtpSendSession = nullptr;\n\n\t\tdelete this->srtpRecvSession;\n\t\tthis->srtpRecvSession = nullptr;\n\n\t\ttry\n\t\t{\n\t\t\tthis->srtpSendSession = new RTC::SrtpSession(\n\t\t\t  RTC::SrtpSession::Type::OUTBOUND, srtpCryptoSuite, srtpLocalKey, srtpLocalKeyLen);\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\tMS_ERROR(\"error creating SRTP sending session: %s\", error.what());\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\tthis->srtpRecvSession = new RTC::SrtpSession(\n\t\t\t  RTC::SrtpSession::Type::INBOUND, srtpCryptoSuite, srtpRemoteKey, srtpRemoteKeyLen);\n\n\t\t\t// Notify the Node WebRtcTransport.\n\t\t\tauto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotificationDirect(\n\t\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t\t  FBS::WebRtcTransport::DtlsState::CONNECTED,\n\t\t\t  remoteCert.c_str());\n\n\t\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t\t  this->id,\n\t\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\t\t  FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification,\n\t\t\t  dtlsStateChangeOffset);\n\n\t\t\t// Tell the parent class.\n\t\t\tRTC::Transport::Connected();\n\t\t}\n\t\tcatch (const MediaSoupError& error)\n\t\t{\n\t\t\tMS_ERROR(\"error creating SRTP receiving session: %s\", error.what());\n\n\t\t\tdelete this->srtpSendSession;\n\t\t\tthis->srtpSendSession = nullptr;\n\t\t}\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportFailed(const RTC::DtlsTransport* /*dtlsTransport*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_WARN_TAG(dtls, \"DTLS failed\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::DtlsState::FAILED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification,\n\t\t  dtlsStateChangeOffset);\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportClosed(const RTC::DtlsTransport* /*dtlsTransport*/)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_WARN_TAG(dtls, \"DTLS remotely closed\");\n\n\t\t// Notify the Node WebRtcTransport.\n\t\tauto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification(\n\t\t  this->shared->GetChannelNotifier()->GetBufferBuilder(),\n\t\t  FBS::WebRtcTransport::DtlsState::CLOSED);\n\n\t\tthis->shared->GetChannelNotifier()->Emit(\n\t\t  this->id,\n\t\t  FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE,\n\t\t  FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification,\n\t\t  dtlsStateChangeOffset);\n\n\t\t// Tell the parent class.\n\t\tRTC::Transport::Disconnected();\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportSendData(\n\t  const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (!this->iceServer->GetSelectedTuple())\n\t\t{\n\t\t\tMS_WARN_TAG(dtls, \"no selected tuple set, cannot send DTLS packet\");\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis->iceServer->GetSelectedTuple()->Send(data, len);\n\n\t\t// Increase send transmission.\n\t\tRTC::Transport::DataSent(len);\n\t}\n\n\tinline void WebRtcTransport::OnDtlsTransportApplicationDataReceived(\n\t  const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Pass it to the parent transport.\n\t\tRTC::Transport::ReceiveSctpData(data, len);\n\t}\n} // namespace RTC\n"
  },
  {
    "path": "worker/src/Settings.cpp",
    "content": "#define MS_CLASS \"Settings\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Settings.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <flatbuffers/flatbuffers.h>\n#include <cctype>   // isprint()\n#include <iterator> // std::ostream_iterator\n#include <mutex>\n#include <sstream> // std::ostringstream\nextern \"C\"\n{\n#include <getopt.h>\n}\n\n/* Static. */\n\nstatic std::mutex GlobalSyncMutex;\n\n/* Class variables. */\n\nthread_local struct Settings::Configuration Settings::configuration;\n// clang-format off\nconst absl::flat_hash_map<std::string, LogLevel> Settings::String2LogLevel =\n{\n\t{ \"debug\", LogLevel::LOG_DEBUG },\n\t{ \"warn\",  LogLevel::LOG_WARN  },\n\t{ \"error\", LogLevel::LOG_ERROR },\n\t{ \"none\",  LogLevel::LOG_NONE  }\n};\nconst absl::flat_hash_map<LogLevel, std::string> Settings::LogLevel2String =\n{\n\t{ LogLevel::LOG_DEBUG, \"debug\" },\n\t{ LogLevel::LOG_WARN,  \"warn\"  },\n\t{ LogLevel::LOG_ERROR, \"error\" },\n\t{ LogLevel::LOG_NONE,  \"none\"  }\n};\n// clang-format on\n\n/* Class methods. */\n\nvoid Settings::SetConfiguration(int argc, char* argv[])\n{\n\tMS_TRACE();\n\n\t/* Variables for getopt. */\n\n\tint c;\n\tint optionIdx{ 0 };\n\t// clang-format off\n\tstruct option options[] =\n\t{\n\t\t{ .name=\"logLevel\",             .has_arg=optional_argument, .flag=nullptr, .val='l' },\n\t\t{ .name=\"logTags\",              .has_arg=optional_argument, .flag=nullptr, .val='t' },\n\t\t{ .name=\"rtcMinPort\",           .has_arg=optional_argument, .flag=nullptr, .val='m' },\n\t\t{ .name=\"rtcMaxPort\",           .has_arg=optional_argument, .flag=nullptr, .val='M' },\n\t\t{ .name=\"dtlsCertificateFile\",  .has_arg=optional_argument, .flag=nullptr, .val='c' },\n\t\t{ .name=\"dtlsPrivateKeyFile\",   .has_arg=optional_argument, .flag=nullptr, .val='p' },\n\t\t{ .name=\"libwebrtcFieldTrials\", .has_arg=optional_argument, .flag=nullptr, .val='W' },\n\t\t{ .name=\"disableLiburing\",      .has_arg=optional_argument, .flag=nullptr, .val='d' },\n\t\t{ .name=\"useBuiltInSctpStack\",  .has_arg=optional_argument, .flag=nullptr, .val='s' },\n\t\t{ .name=nullptr,                .has_arg=0,                 .flag=nullptr,  .val=0  }\n\t};\n\t// clang-format on\n\tstd::string stringValue;\n\tstd::vector<std::string> logTags;\n\n\t/* Parse command line options. */\n\n\t// getopt_long_only() is not thread-safe\n\tconst std::scoped_lock lock(GlobalSyncMutex);\n\n\toptind = 1; // Set explicitly, otherwise subsequent runs will fail.\n\topterr = 0; // Don't allow getopt to print error messages.\n\n\twhile ((c = getopt_long_only(argc, argv, \"\", options, std::addressof(optionIdx))) != -1)\n\t{\n\t\tif (!optarg)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"missing value in command line argument in option '%c'\", c);\n\t\t}\n\n\t\tswitch (c)\n\t\t{\n\t\t\tcase 'l':\n\t\t\t{\n\t\t\t\tstringValue = std::string(optarg);\n\t\t\t\tSetLogLevel(stringValue);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 't':\n\t\t\t{\n\t\t\t\tstringValue = std::string(optarg);\n\t\t\t\tlogTags.push_back(stringValue);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'm':\n\t\t\t{\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tSettings::configuration.rtcMinPort = static_cast<uint16_t>(std::stoi(optarg));\n\t\t\t\t}\n\t\t\t\tcatch (const std::exception& error)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"%s\", error.what());\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'M':\n\t\t\t{\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tSettings::configuration.rtcMaxPort = static_cast<uint16_t>(std::stoi(optarg));\n\t\t\t\t}\n\t\t\t\tcatch (const std::exception& error)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"%s\", error.what());\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'c':\n\t\t\t{\n\t\t\t\tstringValue                                 = std::string(optarg);\n\t\t\t\tSettings::configuration.dtlsCertificateFile = stringValue;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'p':\n\t\t\t{\n\t\t\t\tstringValue                                = std::string(optarg);\n\t\t\t\tSettings::configuration.dtlsPrivateKeyFile = stringValue;\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'W':\n\t\t\t{\n\t\t\t\tstringValue = std::string(optarg);\n\n\t\t\t\tif (stringValue != Settings::configuration.libwebrtcFieldTrials)\n\t\t\t\t{\n\t\t\t\t\tMS_WARN_TAG(\n\t\t\t\t\t  info,\n\t\t\t\t\t  \"overriding default value of libwebrtcFieldTrials may generate crashes in mediasoup-worker\");\n\n\t\t\t\t\tSettings::configuration.libwebrtcFieldTrials = stringValue;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 'd':\n\t\t\t{\n\t\t\t\tstringValue = std::string(optarg);\n\n\t\t\t\tif (stringValue == \"true\")\n\t\t\t\t{\n\t\t\t\t\tSettings::configuration.disableLiburing = true;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase 's':\n\t\t\t{\n\t\t\t\tstringValue = std::string(optarg);\n\n\t\t\t\tif (stringValue == \"true\")\n\t\t\t\t{\n\t\t\t\t\tSettings::configuration.useBuiltInSctpStack = true;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tSettings::configuration.useBuiltInSctpStack = false;\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Invalid option.\n\t\t\tcase '?':\n\t\t\t{\n\t\t\t\tif (isprint(optopt) != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid option '-%c'\", (char)optopt);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"unknown long option given as argument\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Valid option, but it requires and argument that is not given.\n\t\t\tcase ':':\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"option '%c' requires an argument\", (char)optopt);\n\t\t\t}\n\n\t\t\t// This should never happen.\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"'default' should never happen\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Post configuration. */\n\n\t// Set logTags.\n\tif (!logTags.empty())\n\t{\n\t\tSettings::SetLogTags(logTags);\n\t}\n\n\t// Validate RTC ports.\n\tif (Settings::configuration.rtcMaxPort < Settings::configuration.rtcMinPort)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"rtcMaxPort cannot be less than rtcMinPort\");\n\t}\n\n\t// Set DTLS certificate files (if provided),\n\tSettings::SetDtlsCertificateAndPrivateKeyFiles();\n}\n\nvoid Settings::SetLogLevel(std::string& level)\n{\n\tMS_TRACE();\n\n\t// Lowcase given level.\n\tUtils::String::ToLowerCase(level);\n\n\tif (Settings::String2LogLevel.find(level) == Settings::String2LogLevel.end())\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"invalid value '%s' for logLevel\", level.c_str());\n\t}\n\n\tSettings::configuration.logLevel = Settings::String2LogLevel.at(level);\n}\n\nvoid Settings::SetLogTags(const std::vector<std::string>& tags)\n{\n\tMS_TRACE();\n\n\t// Reset logTags.\n\tstruct LogTags logTags;\n\n\tfor (const auto& tag : tags)\n\t{\n\t\tif (tag == \"info\")\n\t\t{\n\t\t\tlogTags.info = true;\n\t\t}\n\t\telse if (tag == \"ice\")\n\t\t{\n\t\t\tlogTags.ice = true;\n\t\t}\n\t\telse if (tag == \"dtls\")\n\t\t{\n\t\t\tlogTags.dtls = true;\n\t\t}\n\t\telse if (tag == \"rtp\")\n\t\t{\n\t\t\tlogTags.rtp = true;\n\t\t}\n\t\telse if (tag == \"srtp\")\n\t\t{\n\t\t\tlogTags.srtp = true;\n\t\t}\n\t\telse if (tag == \"rtcp\")\n\t\t{\n\t\t\tlogTags.rtcp = true;\n\t\t}\n\t\telse if (tag == \"rtx\")\n\t\t{\n\t\t\tlogTags.rtx = true;\n\t\t}\n\t\telse if (tag == \"bwe\")\n\t\t{\n\t\t\tlogTags.bwe = true;\n\t\t}\n\t\telse if (tag == \"score\")\n\t\t{\n\t\t\tlogTags.score = true;\n\t\t}\n\t\telse if (tag == \"simulcast\")\n\t\t{\n\t\t\tlogTags.simulcast = true;\n\t\t}\n\t\telse if (tag == \"svc\")\n\t\t{\n\t\t\tlogTags.svc = true;\n\t\t}\n\t\telse if (tag == \"sctp\")\n\t\t{\n\t\t\tlogTags.sctp = true;\n\t\t}\n\t\telse if (tag == \"message\")\n\t\t{\n\t\t\tlogTags.message = true;\n\t\t}\n\t}\n\n\tSettings::configuration.logTags = logTags;\n}\n\nvoid Settings::PrintConfiguration()\n{\n\tMS_TRACE();\n\n\tstd::vector<std::string> logTags;\n\tstd::ostringstream logTagsStream;\n\n\tif (Settings::configuration.logTags.info)\n\t{\n\t\tlogTags.emplace_back(\"info\");\n\t}\n\tif (Settings::configuration.logTags.ice)\n\t{\n\t\tlogTags.emplace_back(\"ice\");\n\t}\n\tif (Settings::configuration.logTags.dtls)\n\t{\n\t\tlogTags.emplace_back(\"dtls\");\n\t}\n\tif (Settings::configuration.logTags.rtp)\n\t{\n\t\tlogTags.emplace_back(\"rtp\");\n\t}\n\tif (Settings::configuration.logTags.srtp)\n\t{\n\t\tlogTags.emplace_back(\"srtp\");\n\t}\n\tif (Settings::configuration.logTags.rtcp)\n\t{\n\t\tlogTags.emplace_back(\"rtcp\");\n\t}\n\tif (Settings::configuration.logTags.rtx)\n\t{\n\t\tlogTags.emplace_back(\"rtx\");\n\t}\n\tif (Settings::configuration.logTags.bwe)\n\t{\n\t\tlogTags.emplace_back(\"bwe\");\n\t}\n\tif (Settings::configuration.logTags.score)\n\t{\n\t\tlogTags.emplace_back(\"score\");\n\t}\n\tif (Settings::configuration.logTags.simulcast)\n\t{\n\t\tlogTags.emplace_back(\"simulcast\");\n\t}\n\tif (Settings::configuration.logTags.svc)\n\t{\n\t\tlogTags.emplace_back(\"svc\");\n\t}\n\tif (Settings::configuration.logTags.sctp)\n\t{\n\t\tlogTags.emplace_back(\"sctp\");\n\t}\n\tif (Settings::configuration.logTags.message)\n\t{\n\t\tlogTags.emplace_back(\"message\");\n\t}\n\n\tif (!logTags.empty())\n\t{\n\t\tstd::copy(\n\t\t  logTags.begin(), logTags.end() - 1, std::ostream_iterator<std::string>(logTagsStream, \",\"));\n\t\tlogTagsStream << logTags.back();\n\t}\n\n\tMS_DEBUG_TAG(info, \"<configuration>\");\n\n\tMS_DEBUG_TAG(\n\t  info, \"  logLevel: %s\", Settings::LogLevel2String.at(Settings::configuration.logLevel).c_str());\n\tMS_DEBUG_TAG(info, \"  logTags: %s\", logTagsStream.str().c_str());\n\tMS_DEBUG_TAG(info, \"  rtcMinPort: %\" PRIu16, Settings::configuration.rtcMinPort);\n\tMS_DEBUG_TAG(info, \"  rtcMaxPort: %\" PRIu16, Settings::configuration.rtcMaxPort);\n\tif (!Settings::configuration.dtlsCertificateFile.empty())\n\t{\n\t\tMS_DEBUG_TAG(\n\t\t  info, \"  dtlsCertificateFile: %s\", Settings::configuration.dtlsCertificateFile.c_str());\n\t\tMS_DEBUG_TAG(info, \"  dtlsPrivateKeyFile: %s\", Settings::configuration.dtlsPrivateKeyFile.c_str());\n\t}\n\tif (!Settings::configuration.libwebrtcFieldTrials.empty())\n\t{\n\t\tMS_DEBUG_TAG(\n\t\t  info, \"  libwebrtcFieldTrials: %s\", Settings::configuration.libwebrtcFieldTrials.c_str());\n\t}\n\tMS_DEBUG_TAG(info, \"  disableLiburing: %s\", Settings::configuration.disableLiburing ? \"yes\" : \"no\");\n\tMS_DEBUG_TAG(\n\t  info, \"  useBuiltInSctpStack: %s\", Settings::configuration.useBuiltInSctpStack ? \"yes\" : \"no\");\n\n\tMS_DEBUG_TAG(info, \"</configuration>\");\n}\n\nvoid Settings::HandleRequest(Channel::ChannelRequest* request)\n{\n\tMS_TRACE();\n\n\tswitch (request->method)\n\t{\n\t\tcase Channel::ChannelRequest::Method::WORKER_UPDATE_SETTINGS:\n\t\t{\n\t\t\tconst auto* body = request->data->body_as<FBS::Worker::UpdateSettingsRequest>();\n\n\t\t\tif (flatbuffers::IsFieldPresent(body, FBS::Worker::UpdateSettingsRequest::VT_LOGLEVEL))\n\t\t\t{\n\t\t\t\tauto logLevel = body->logLevel()->str();\n\n\t\t\t\t// This may throw.\n\t\t\t\tSettings::SetLogLevel(logLevel);\n\t\t\t}\n\n\t\t\t// Update logTags if requested.\n\t\t\tif (flatbuffers::IsFieldPresent(body, FBS::Worker::UpdateSettingsRequest::VT_LOGTAGS))\n\t\t\t{\n\t\t\t\tstd::vector<std::string> logTags;\n\n\t\t\t\tfor (const auto& logTag : *body->logTags())\n\t\t\t\t{\n\t\t\t\t\tlogTags.push_back(logTag->str());\n\t\t\t\t}\n\n\t\t\t\tSettings::SetLogTags(logTags);\n\t\t\t}\n\n\t\t\t// Print the new effective configuration.\n\t\t\tSettings::PrintConfiguration();\n\n\t\t\trequest->Accept();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t{\n\t\t\tMS_THROW_ERROR(\"unknown method '%s'\", request->methodCStr);\n\t\t}\n\t}\n}\n\nvoid Settings::SetDtlsCertificateAndPrivateKeyFiles()\n{\n\tMS_TRACE();\n\n\tif (\n\t  !Settings::configuration.dtlsCertificateFile.empty() &&\n\t  Settings::configuration.dtlsPrivateKeyFile.empty())\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"missing dtlsPrivateKeyFile\");\n\t}\n\telse if (\n\t  Settings::configuration.dtlsCertificateFile.empty() &&\n\t  !Settings::configuration.dtlsPrivateKeyFile.empty())\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"missing dtlsCertificateFile\");\n\t}\n\telse if (\n\t  Settings::configuration.dtlsCertificateFile.empty() &&\n\t  Settings::configuration.dtlsPrivateKeyFile.empty())\n\t{\n\t\treturn;\n\t}\n\n\tconst std::string& dtlsCertificateFile = Settings::configuration.dtlsCertificateFile;\n\tconst std::string& dtlsPrivateKeyFile  = Settings::configuration.dtlsPrivateKeyFile;\n\n\ttry\n\t{\n\t\tUtils::File::CheckFile(dtlsCertificateFile.c_str());\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"dtlsCertificateFile: %s\", error.what());\n\t}\n\n\ttry\n\t{\n\t\tUtils::File::CheckFile(dtlsPrivateKeyFile.c_str());\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"dtlsPrivateKeyFile: %s\", error.what());\n\t}\n}\n"
  },
  {
    "path": "worker/src/Shared.cpp",
    "content": "#define MS_CLASS \"Shared\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Shared.hpp\"\n#include \"Logger.hpp\"\n#include \"handles/BackoffTimerHandle.hpp\"\n#include \"handles/TimerHandle.hpp\"\n\nShared::Shared(\n  Channel::ChannelMessageRegistrator* channelMessageRegistrator,\n  Channel::ChannelNotifier* channelNotifier)\n  : channelMessageRegistrator(channelMessageRegistrator), channelNotifier(channelNotifier)\n{\n\tMS_TRACE();\n}\n\nShared::~Shared()\n{\n\tMS_TRACE();\n}\n\nTimerHandleInterface* Shared::CreateTimer(TimerHandleInterface::Listener* listener)\n{\n\tMS_TRACE();\n\n\treturn new TimerHandle(listener);\n}\n\nBackoffTimerHandleInterface* Shared::CreateBackoffTimer(\n  const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options)\n{\n\tMS_TRACE();\n\n\treturn new BackoffTimerHandle(options);\n}\n"
  },
  {
    "path": "worker/src/Utils/BitStream.cpp",
    "content": "#define MS_CLASS \"Utils::BitStream\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n\nnamespace Utils\n{\n\t// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, hicpp-member-init)\n\tBitStream::BitStream(uint8_t* data, size_t len) : len(len)\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::memcpy(this->data, data, len);\n\t}\n\n\tconst uint8_t* BitStream::GetData() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->data;\n\t}\n\n\tsize_t BitStream::GetLength() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->len;\n\t}\n\n\tuint32_t BitStream::GetOffset() const\n\t{\n\t\tMS_TRACE();\n\n\t\treturn this->offset;\n\t}\n\n\tvoid BitStream::Reset()\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->offset = 0;\n\t\tthis->len    = sizeof(this->data);\n\n\t\tstd::memset(this->data, 0, this->len);\n\t}\n\n\tuint8_t BitStream::GetBit()\n\t{\n\t\tMS_TRACE();\n\n\t\tauto bit = ((*(data + (this->offset >> 0x3))) >> (0x7 - (this->offset & 0x7))) & 0x1;\n\n\t\tthis->offset++;\n\n\t\treturn bit;\n\t}\n\n\tuint32_t BitStream::GetBits(size_t count)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint32_t bits = 0;\n\n\t\tfor (unsigned i = 0; i < count; ++i)\n\t\t{\n\t\t\tbits = (2 * bits) + GetBit();\n\t\t}\n\n\t\treturn bits;\n\t}\n\n\tuint32_t BitStream::GetLeftBits() const\n\t{\n\t\tMS_TRACE();\n\n\t\tif (this->offset >= this->len * 8)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\n\t\tauto leftBits = (this->len * 8) - this->offset;\n\n\t\treturn leftBits;\n\t}\n\n\tvoid BitStream::SkipBits(size_t count)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->offset += count;\n\t}\n\n\t/*\n\t * non-symmetric unsigned encoded integer with maximum\n\t * number of values n (i.e., output in range 0..n-1).\n\t * Returns std::nullopt if there are not enough bits left.\n\t */\n\tstd::optional<uint32_t> BitStream::ReadNs(uint32_t n)\n\t{\n\t\tunsigned w = 0;\n\t\tunsigned x = n;\n\n\t\twhile (x != 0)\n\t\t{\n\t\t\tx = x >> 1;\n\t\t\t++w;\n\t\t}\n\n\t\tif (this->GetLeftBits() < w - 1)\n\t\t{\n\t\t\treturn std::nullopt;\n\t\t}\n\n\t\tconst unsigned v = this->GetBits(w - 1);\n\t\tconst unsigned m = (1u << w) - n;\n\n\t\tif (v < m)\n\t\t{\n\t\t\treturn v;\n\t\t}\n\n\t\tif (this->GetLeftBits() < 1)\n\t\t{\n\t\t\treturn std::nullopt;\n\t\t}\n\n\t\tconst unsigned extraBit = this->GetBit();\n\n\t\treturn (v << 1) - m + extraBit;\n\t}\n\n\tvoid BitStream::Write(uint32_t offset, uint32_t n, uint32_t v)\n\t{\n\t\tMS_TRACE();\n\n\t\tunsigned w = 0;\n\t\tunsigned x = n;\n\n\t\twhile (x != 0)\n\t\t{\n\t\t\tx = x >> 1;\n\t\t\t++w;\n\t\t}\n\n\t\tconst unsigned m = (1 << w) - n;\n\n\t\tif (v < m)\n\t\t{\n\t\t\tthis->PutBits(offset, w - 1, v);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->PutBits(offset, w, v + m);\n\t\t}\n\t}\n\n\tvoid BitStream::PutBit(uint8_t bit)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->PutBit(this->offset, bit);\n\t}\n\n\tvoid BitStream::PutBits(uint32_t count, uint32_t bits)\n\t{\n\t\tMS_TRACE();\n\n\t\tthis->PutBits(this->offset, count, bits);\n\t}\n\n\tvoid BitStream::PutBit(uint32_t offset, uint8_t bit)\n\t{\n\t\tMS_TRACE();\n\n\t\t// Retrieve the current byte position.\n\t\tconst size_t byteOffset = offset >> 0x3;\n\n\t\t// Calculate the bitmask for the target bit within the current byte.\n\t\tconst auto bitmask = (1u << (0x7 - (offset & 0x7)));\n\n\t\tif (bit)\n\t\t{\n\t\t\tthis->data[byteOffset] |= bitmask;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthis->data[byteOffset] &= ~bitmask;\n\t\t}\n\n\t\t++this->offset;\n\t}\n\n\tvoid BitStream::PutBits(uint32_t offset, uint32_t count, uint32_t bits)\n\t{\n\t\tMS_TRACE();\n\n\t\tMS_ASSERT(\n\t\t  count <= 32, \"count cannot be bigger than 32 [count:%\" PRIu32 \", bits:%\" PRIu32 \"]\", count, bits);\n\n\t\tfor (unsigned i = 0; i < count; ++i)\n\t\t{\n\t\t\tconst uint32_t shift = count - i - 1;\n\t\t\tconst uint8_t bit    = (bits >> shift) & 0x1;\n\n\t\t\tthis->PutBit(offset++, bit);\n\t\t}\n\t}\n} // namespace Utils\n"
  },
  {
    "path": "worker/src/Utils/Crypto.cpp",
    "content": "#define MS_CLASS \"Utils::Crypto\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"Utils.hpp\"\n#include <openssl/rand.h>\n#include <openssl/sha.h>\n\nnamespace Utils\n{\n\t/* Class variables. */\n\n\tthread_local std::mt19937_64 Crypto::rng;\n\tthread_local EVP_MAC* Crypto::mac{ nullptr };\n\tthread_local EVP_MAC_CTX* Crypto::hmacSha1Ctx{ nullptr };\n\tthread_local uint8_t Crypto::hmacSha1Buffer[SHA_DIGEST_LENGTH];\n\t// clang-format off\n\tconst uint32_t Crypto::Crc32Table[] =\n\t{\n\t\t0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,\n\t\t0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,\n\t\t0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,\n\t\t0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,\n\t\t0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\n\t\t0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,\n\t\t0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,\n\t\t0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,\n\t\t0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,\n\t\t0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,\n\t\t0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,\n\t\t0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,\n\t\t0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,\n\t\t0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,\n\t\t0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n\t\t0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,\n\t\t0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,\n\t\t0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,\n\t\t0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,\n\t\t0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,\n\t\t0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,\n\t\t0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,\n\t\t0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,\n\t\t0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,\n\t\t0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,\n\t\t0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,\n\t\t0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,\n\t\t0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,\n\t\t0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,\n\t\t0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n\t\t0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,\n\t\t0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d\n\t};\n\t// clang-format on\n\t// clang-format off\n\tconst uint32_t Crypto::Crc32cTable[] =\n\t{\n\t\t0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,\n\t\t0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,\n\t\t0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,\n\t\t0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,\n\t\t0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,\n\t\t0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,\n\t\t0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,\n\t\t0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,\n\t\t0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,\n\t\t0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,\n\t\t0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,\n\t\t0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,\n\t\t0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,\n\t\t0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,\n\t\t0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,\n\t\t0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,\n\t\t0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,\n\t\t0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,\n\t\t0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,\n\t\t0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,\n\t\t0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,\n\t\t0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,\n\t\t0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,\n\t\t0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,\n\t\t0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,\n\t\t0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,\n\t\t0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,\n\t\t0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,\n\t\t0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,\n\t\t0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,\n\t\t0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,\n\t\t0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351\n\t};\n\t// clang-format on\n\n\t/* Static methods. */\n\n\tvoid Crypto::ClassInit()\n\t{\n\t\tMS_TRACE();\n\n\t\tstd::random_device rd;\n\t\tconst uint64_t seed = (uint64_t(rd()) << 32) | uint64_t(rd());\n\n\t\tCrypto::rng.seed(seed);\n\n\t\t// Create an OpenSSL HMAC_CTX context for HMAC SHA1 calculation.\n\t\tCrypto::mac         = EVP_MAC_fetch(nullptr, \"HMAC\", nullptr);\n\t\tCrypto::hmacSha1Ctx = EVP_MAC_CTX_new(mac);\n\t}\n\n\tvoid Crypto::ClassDestroy()\n\t{\n\t\tMS_TRACE();\n\n\t\tif (Crypto::hmacSha1Ctx != nullptr)\n\t\t{\n\t\t\tEVP_MAC_CTX_free(Crypto::hmacSha1Ctx);\n\t\t}\n\n\t\tif (Crypto::mac != nullptr)\n\t\t{\n\t\t\tEVP_MAC_free(Crypto::mac);\n\t\t}\n\t}\n\n\tstd::string Crypto::GetRandomString(size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tchar buffer[64];\n\t\tstatic const char Chars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',\n\t\t\t                            'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',\n\t\t\t                            'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };\n\n\t\tlen = std::min<size_t>(len, 64);\n\n\t\tfor (size_t i{ 0 }; i < len; ++i)\n\t\t{\n\t\t\tbuffer[i] = Chars[GetRandomUInt<size_t>(0, sizeof(Chars) - 1)];\n\t\t}\n\n\t\treturn { buffer, len };\n\t}\n\n\tuint32_t Crypto::GetCRC32(const uint8_t* data, size_t size)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint32_t crc{ 0xFFFFFFFF };\n\t\tconst uint8_t* p = data;\n\n\t\twhile (size--)\n\t\t{\n\t\t\tcrc = Crypto::Crc32Table[(crc ^ *p++) & 0xFF] ^ (crc >> 8);\n\t\t}\n\n\t\treturn crc ^ ~0U;\n\t}\n\n\t/**\n\t * Obtained from https://datatracker.ietf.org/doc/html/rfc9260#appendix-A\n\t */\n\tuint32_t Crypto::GetCRC32c(const uint8_t* data, size_t size)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint32_t crc32 = 0xFFFFFFFF;\n\n\t\tfor (size_t i{ 0 }; i < size; ++i)\n\t\t{\n\t\t\tcrc32 = (crc32 >> 8) ^ Crypto::Crc32cTable[(crc32 ^ data[i]) & 0xFF];\n\t\t}\n\n\t\tconst uint32_t result = ~crc32;\n\t\tconst uint32_t byte0  = result & 0xff;\n\t\tconst uint32_t byte1  = (result >> 8) & 0xff;\n\t\tconst uint32_t byte2  = (result >> 16) & 0xff;\n\t\tconst uint32_t byte3  = (result >> 24) & 0xff;\n\n\t\tcrc32 = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);\n\n\t\treturn crc32;\n\t}\n\n\tconst uint8_t* Crypto::GetHmacSha1(const char* key, size_t keyLen, const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tint ret;\n\n\t\tOSSL_PARAM sha1[] = {\n\t\t\t{ \"digest\", OSSL_PARAM_UTF8_STRING, (void*)\"sha1\", 4, 0 },\n      OSSL_PARAM_END\n\t\t};\n\n\t\tret =\n\t\t  EVP_MAC_init(Crypto::hmacSha1Ctx, reinterpret_cast<const unsigned char*>(key), keyLen, sha1);\n\n\t\tMS_ASSERT(ret == 1, \"OpenSSL EVP_MAC_init() failed with key '%s'\", key);\n\n\t\tret = EVP_MAC_update(Crypto::hmacSha1Ctx, data, len);\n\n\t\tMS_ASSERT(\n\t\t  ret == 1, \"OpenSSL EVP_MAC_update() failed with key '%s' and data length %zu bytes\", key, len);\n\n\t\tsize_t resultLen;\n\n\t\tret = EVP_MAC_final(Crypto::hmacSha1Ctx, Crypto::hmacSha1Buffer, &resultLen, SHA_DIGEST_LENGTH);\n\n\t\tMS_ASSERT(\n\t\t  ret == 1, \"OpenSSL HMAC_Final() failed with key '%s' and data length %zu bytes\", key, len);\n\t\tMS_ASSERT(\n\t\t  resultLen == SHA_DIGEST_LENGTH, \"OpenSSL HMAC_Final() resultLen is %zu instead of 20\", resultLen);\n\n\t\treturn Crypto::hmacSha1Buffer;\n\t}\n\n\tvoid Crypto::WriteRandomBytes(uint8_t* buffer, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (RAND_bytes(buffer, len) != 1)\n\t\t{\n\t\t\tMS_ABORT(\"OpenSSL RAND_bytes() failed\");\n\t\t}\n\t}\n} // namespace Utils\n"
  },
  {
    "path": "worker/src/Utils/File.cpp",
    "content": "#define MS_CLASS \"Utils::File\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <cerrno>\n#include <sys/stat.h> // stat()\n#ifdef _WIN32\n#include <io.h>\n#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))\n#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)\n#else\n#include <unistd.h> // access(), R_OK\n#endif\n\nnamespace Utils\n{\n\tvoid Utils::File::CheckFile(const char* file)\n\t{\n\t\tMS_TRACE();\n\n\t\tstruct stat fileStat{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\t\tint err;\n\n\t\t// Ensure the given file exists.\n\t\terr = stat(file, &fileStat);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"cannot read file '%s': %s\", file, std::strerror(errno));\n\t\t}\n\n\t\t// Ensure it is a regular file.\n\t\tif (!S_ISREG(fileStat.st_mode))\n\t\t{\n\t\t\tMS_THROW_ERROR(\"'%s' is not a regular file\", file);\n\t\t}\n\n\t\t// Ensure it is readable.\n\t\terr = access(file, R_OK);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"cannot read file '%s': %s\", file, std::strerror(errno));\n\t\t}\n\t}\n} // namespace Utils\n"
  },
  {
    "path": "worker/src/Utils/IP.cpp",
    "content": "#define MS_CLASS \"Utils::IP\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <uv.h>\n\nnamespace Utils\n{\n\tint IP::GetFamily(const std::string& ip)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (ip.size() >= INET6_ADDRSTRLEN)\n\t\t{\n\t\t\treturn AF_UNSPEC;\n\t\t}\n\n\t\tconst auto* ipPtr               = ip.c_str();\n\t\tchar ipBuffer[INET6_ADDRSTRLEN] = { 0 };\n\n\t\tif (uv_inet_pton(AF_INET, ipPtr, ipBuffer) == 0)\n\t\t{\n\t\t\treturn AF_INET;\n\t\t}\n\t\telse if (uv_inet_pton(AF_INET6, ipPtr, ipBuffer) == 0)\n\t\t{\n\t\t\treturn AF_INET6;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn AF_UNSPEC;\n\t\t}\n\t}\n\n\tvoid IP::GetAddressInfo(const struct sockaddr* addr, int& family, std::string& ip, uint16_t& port)\n\t{\n\t\tMS_TRACE();\n\n\t\tchar ipBuffer[INET6_ADDRSTRLEN] = { 0 };\n\t\tint err;\n\n\t\tswitch (addr->sa_family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\terr = uv_inet_ntop(\n\t\t\t\t  AF_INET,\n\t\t\t\t  std::addressof(reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr),\n\t\t\t\t  ipBuffer,\n\t\t\t\t  sizeof(ipBuffer));\n\n\t\t\t\tif (err)\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"uv_inet_ntop() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tport = ntohs(reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\terr = uv_inet_ntop(\n\t\t\t\t  AF_INET6,\n\t\t\t\t  std::addressof(reinterpret_cast<const struct sockaddr_in6*>(addr)->sin6_addr),\n\t\t\t\t  ipBuffer,\n\t\t\t\t  sizeof(ipBuffer));\n\n\t\t\t\tif (err)\n\t\t\t\t{\n\t\t\t\t\tMS_ABORT(\"uv_inet_ntop() failed: %s\", uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tport = ntohs(reinterpret_cast<const struct sockaddr_in6*>(addr)->sin6_port);\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ABORT(\"unknown network family: %d\", static_cast<int>(addr->sa_family));\n\t\t\t}\n\t\t}\n\n\t\tfamily = addr->sa_family;\n\t\tip.assign(ipBuffer);\n\t}\n\n\tsize_t IP::GetAddressLen(const struct sockaddr* addr)\n\t{\n\t\tMS_TRACE();\n\n\t\tswitch (addr->sa_family)\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\treturn sizeof(struct sockaddr_in);\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\treturn sizeof(struct sockaddr_in6);\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_ABORT(\"unknown network family: %d\", static_cast<int>(addr->sa_family));\n\t\t\t}\n\t\t}\n\t}\n\n\tstd::string IP::NormalizeIp(std::string& ip)\n\t{\n\t\tMS_TRACE();\n\n\t\tsockaddr_storage addrStorage{};\n\t\tchar ipBuffer[INET6_ADDRSTRLEN] = { 0 };\n\t\tint err;\n\n\t\tswitch (IP::GetFamily(ip))\n\t\t{\n\t\t\tcase AF_INET:\n\t\t\t{\n\t\t\t\terr = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in*>(&addrStorage));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"uv_ip4_addr() failed [ip:'%s']: %s\", ip.c_str(), uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\terr = uv_ip4_name(\n\t\t\t\t  reinterpret_cast<const struct sockaddr_in*>(std::addressof(addrStorage)),\n\t\t\t\t  ipBuffer,\n\t\t\t\t  sizeof(ipBuffer));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"uv_ipv4_name() failed [ip:'%s']: %s\", ip.c_str(), uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tip.assign(ipBuffer);\n\n\t\t\t\treturn ip;\n\t\t\t}\n\n\t\t\tcase AF_INET6:\n\t\t\t{\n\t\t\t\terr = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast<struct sockaddr_in6*>(&addrStorage));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"uv_ip6_addr() failed [ip:'%s']: %s\", ip.c_str(), uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\terr = uv_ip6_name(\n\t\t\t\t  reinterpret_cast<const struct sockaddr_in6*>(std::addressof(addrStorage)),\n\t\t\t\t  ipBuffer,\n\t\t\t\t  sizeof(ipBuffer));\n\n\t\t\t\tif (err != 0)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_TYPE_ERROR(\"uv_ipv6_name() failed [ip:'%s']: %s\", ip.c_str(), uv_strerror(err));\n\t\t\t\t}\n\n\t\t\t\tip.assign(ipBuffer);\n\n\t\t\t\treturn ip;\n\t\t\t}\n\n\t\t\tdefault:\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"invalid IP '%s'\", ip.c_str());\n\t\t\t}\n\t\t}\n\t}\n} // namespace Utils\n"
  },
  {
    "path": "worker/src/Utils/README_BASE64_UTILS",
    "content": "wpa_supplicant and hostapd\n--------------------------\n\nCopyright (c) 2002-2012, Jouni Malinen <j@w1.fi> and contributors\nAll Rights Reserved.\n\nThese programs are licensed under the BSD license (the one with\nadvertisement clause removed).\n\nIf you are submitting changes to the project, please see CONTRIBUTIONS\nfile for more instructions.\n\n\nThis package may include either wpa_supplicant, hostapd, or both. See\nREADME file respective subdirectories (wpa_supplicant/README or\nhostapd/README) for more details.\n\nSource code files were moved around in v0.6.x releases and compared to\nearlier releases, the programs are now built by first going to a\nsubdirectory (wpa_supplicant or hostapd) and creating build\nconfiguration (.config) and running 'make' there (for Linux/BSD/cygwin\nbuilds).\n\n\nLicense\n-------\n\nThis software may be distributed, used, and modified under the terms of\nBSD license:\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\n3. Neither the name(s) of the above-listed copyright holder(s) nor the\n   names of its contributors may be used to endorse or promote products\n   derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "worker/src/Utils/String.cpp",
    "content": "/*\n * Code from http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c\n *\n * Base64 encoding/decoding (RFC1341)\n * Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>\n *\n * This software may be distributed under the terms of the BSD license.\n * See README_BASE64_UTILS for more details.\n */\n\n#define MS_CLASS \"Utils::String\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memset()\n\n/* Static. */\n\nstatic constexpr size_t BufferOutSize{ 65536 };\nstatic thread_local uint8_t BufferOut[BufferOutSize];\nstatic const uint8_t Base64Table[65] =\n  \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nnamespace Utils\n{\n\tstd::string Utils::String::Base64Encode(const uint8_t* data, size_t len)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst uint8_t* out = BufferOut;\n\t\tuint8_t* pos;\n\t\tconst uint8_t* end;\n\t\tconst uint8_t* in;\n\t\tsize_t olen;\n\n\t\tolen = (len * 4 / 3) + 4; // 3-byte blocks to 4-byte.\n\n\t\tif (olen < len)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"integer overflow\");\n\t\t}\n\t\telse if (olen > BufferOutSize - 1)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"data too big\");\n\t\t}\n\n\t\tend = data + len;\n\t\tin  = data;\n\t\tpos = const_cast<uint8_t*>(out);\n\n\t\twhile (end - in >= 3)\n\t\t{\n\t\t\t*pos++ = Base64Table[in[0] >> 2];\n\t\t\t*pos++ = Base64Table[((in[0] & 0x03) << 4) | (in[1] >> 4)];\n\t\t\t*pos++ = Base64Table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];\n\t\t\t*pos++ = Base64Table[in[2] & 0x3f];\n\t\t\tin += 3;\n\t\t}\n\n\t\tif (end - in)\n\t\t{\n\t\t\t*pos++ = Base64Table[in[0] >> 2];\n\n\t\t\tif (end - in == 1)\n\t\t\t{\n\t\t\t\t*pos++ = Base64Table[(in[0] & 0x03) << 4];\n\t\t\t\t*pos++ = '=';\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t*pos++ = Base64Table[((in[0] & 0x03) << 4) | (in[1] >> 4)];\n\t\t\t\t*pos++ = Base64Table[(in[1] & 0x0f) << 2];\n\t\t\t}\n\n\t\t\t*pos++ = '=';\n\t\t}\n\n\t\treturn { reinterpret_cast<const char*>(out), static_cast<size_t>(pos - out) };\n\t}\n\n\tstd::string Utils::String::Base64Encode(const std::string& str)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* data = reinterpret_cast<const uint8_t*>(str.c_str());\n\n\t\treturn Base64Encode(data, str.size());\n\t}\n\n\tuint8_t* Utils::String::Base64Decode(const uint8_t* data, size_t len, size_t& outLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tuint8_t dtable[256];\n\t\tuint8_t* out = BufferOut;\n\t\tuint8_t* pos;\n\t\tuint8_t block[4];\n\t\tuint8_t tmp;\n\t\tsize_t i;\n\t\tsize_t count;\n\t\tint pad{ 0 };\n\n\t\t// NOTE: This is not really accurate but anyway.\n\t\tif (len > BufferOutSize - 1)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"data too big\");\n\t\t}\n\n\t\tstd::memset(dtable, 0x80, 256);\n\n\t\tfor (i = 0; i < sizeof(Base64Table) - 1; ++i)\n\t\t{\n\t\t\tdtable[Base64Table[i]] = static_cast<uint8_t>(i);\n\t\t}\n\t\tdtable[uint8_t{ '=' }] = 0;\n\n\t\tcount = 0;\n\t\tfor (i = 0; i < len; ++i)\n\t\t{\n\t\t\tif (dtable[data[i]] != 0x80)\n\t\t\t{\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\n\t\tif (count == 0 || count % 4)\n\t\t{\n\t\t\tMS_THROW_TYPE_ERROR(\"invalid data\");\n\t\t}\n\n\t\tpos   = out;\n\t\tcount = 0;\n\n\t\tfor (i = 0; i < len; ++i)\n\t\t{\n\t\t\ttmp = dtable[data[i]];\n\n\t\t\tif (tmp == 0x80)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (data[i] == '=')\n\t\t\t{\n\t\t\t\tpad++;\n\t\t\t}\n\n\t\t\tblock[count] = tmp;\n\t\t\tcount++;\n\n\t\t\tif (count == 4)\n\t\t\t{\n\t\t\t\t*pos++ = (block[0] << 2) | (block[1] >> 4);\n\t\t\t\t*pos++ = (block[1] << 4) | (block[2] >> 2);\n\t\t\t\t*pos++ = (block[2] << 6) | block[3];\n\t\t\t\tcount  = 0;\n\n\t\t\t\tif (pad)\n\t\t\t\t{\n\t\t\t\t\tif (pad == 1)\n\t\t\t\t\t{\n\t\t\t\t\t\tpos--;\n\t\t\t\t\t}\n\t\t\t\t\telse if (pad == 2)\n\t\t\t\t\t{\n\t\t\t\t\t\tpos -= 2;\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tMS_THROW_TYPE_ERROR(\"integer padding\");\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\toutLen = pos - out;\n\n\t\treturn out;\n\t}\n\n\tuint8_t* Utils::String::Base64Decode(const std::string& str, size_t& outLen)\n\t{\n\t\tMS_TRACE();\n\n\t\tconst auto* data = reinterpret_cast<const uint8_t*>(str.c_str());\n\n\t\treturn Base64Decode(data, str.size(), outLen);\n\t}\n} // namespace Utils\n"
  },
  {
    "path": "worker/src/Worker.cpp",
    "content": "#define MS_CLASS \"Worker\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"Worker.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"DepLibUV.hpp\"\n// TODO: Remove once we only use built-in SCTP stack.\n#include \"DepUsrSCTP.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"FBS/response.h\"\n#include \"FBS/worker.h\"\n\n/* Instance methods. */\n\nWorker::Worker(::Channel::ChannelSocket* channel, SharedInterface* shared)\n  : channel(channel), shared(shared)\n{\n\tMS_TRACE();\n\n\t// Set us as Channel's listener.\n\tthis->channel->SetListener(this);\n\n\t// Set the SignalHandle.\n\tthis->signalHandle = new SignalHandle(this);\n\n#ifdef MS_EXECUTABLE\n\t{\n\t\t// Add signals to handle.\n\t\tthis->signalHandle->AddSignal(SIGINT, \"INT\");\n\t\tthis->signalHandle->AddSignal(SIGTERM, \"TERM\");\n\t}\n#endif\n\n\t// TODO: Remove once we only use built-in SCTP stack.\n\tif (!Settings::configuration.useBuiltInSctpStack)\n\t{\n\t\t// Create the Checker instance in DepUsrSCTP.\n\t\tDepUsrSCTP::CreateChecker(this->shared);\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\t// Start polling CQEs, which will create a uv_pool_t handle.\n\t\tDepLibUring::StartPollingCQEs();\n\t}\n#endif\n\n\t// Tell the Node process that we are running.\n\tthis->shared->GetChannelNotifier()->Emit(\n\t  std::to_string(Logger::Pid), FBS::Notification::Event::WORKER_RUNNING);\n\n\tMS_DEBUG_DEV(\"starting libuv loop\");\n\tDepLibUV::RunLoop();\n\tMS_DEBUG_DEV(\"libuv loop ended\");\n}\n\nWorker::~Worker()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\tClose();\n\t}\n}\n\nvoid Worker::Close()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tthis->closed = true;\n\n\t// Delete the SignalHandle.\n\tdelete this->signalHandle;\n\n\t// Delete all Routers.\n\tfor (auto& kv : this->mapRouters)\n\t{\n\t\tauto* router = kv.second;\n\n\t\tdelete router;\n\t}\n\tthis->mapRouters.clear();\n\n\t// Delete all WebRtcServers.\n\tfor (auto& kv : this->mapWebRtcServers)\n\t{\n\t\tauto* webRtcServer = kv.second;\n\n\t\tdelete webRtcServer;\n\t}\n\tthis->mapWebRtcServers.clear();\n\n\t// TODO: Remove once we only use built-in SCTP stack.\n\tif (!Settings::configuration.useBuiltInSctpStack)\n\t{\n\t\t// Close the Checker instance in DepUsrSCTP.\n\t\tDepUsrSCTP::CloseChecker();\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\t// Stop polling CQEs, which will close the uv_pool_t handle.\n\t\tDepLibUring::StopPollingCQEs();\n\t}\n#endif\n\n\t// Close the Channel.\n\tthis->channel->Close();\n}\n\nflatbuffers::Offset<FBS::Worker::DumpResponse> Worker::FillBuffer(\n  flatbuffers::FlatBufferBuilder& builder) const\n{\n\t// Add webRtcServerIds.\n\tstd::vector<flatbuffers::Offset<flatbuffers::String>> webRtcServerIds;\n\twebRtcServerIds.reserve(this->mapWebRtcServers.size());\n\n\tfor (const auto& kv : this->mapWebRtcServers)\n\t{\n\t\tconst auto& webRtcServerId = kv.first;\n\n\t\twebRtcServerIds.push_back(builder.CreateString(webRtcServerId));\n\t}\n\n\t// Add routerIds.\n\tstd::vector<flatbuffers::Offset<flatbuffers::String>> routerIds;\n\trouterIds.reserve(this->mapRouters.size());\n\n\tfor (const auto& kv : this->mapRouters)\n\t{\n\t\tconst auto& routerId = kv.first;\n\n\t\trouterIds.push_back(builder.CreateString(routerId));\n\t}\n\n\t// Add channelMessageHandlers.\n\tauto channelMessageHandlers = this->shared->GetChannelMessageRegistrator()->FillBuffer(builder);\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\treturn FBS::Worker::CreateDumpResponseDirect(\n\t\t  builder,\n\t\t  Logger::Pid,\n\t\t  &webRtcServerIds,\n\t\t  &routerIds,\n\t\t  channelMessageHandlers,\n\t\t  DepLibUring::FillBuffer(builder));\n\t}\n\telse\n\t{\n\t\treturn FBS::Worker::CreateDumpResponseDirect(\n\t\t  builder, Logger::Pid, &webRtcServerIds, &routerIds, channelMessageHandlers);\n\t}\n#else\n\treturn FBS::Worker::CreateDumpResponseDirect(\n\t  builder, Logger::Pid, &webRtcServerIds, &routerIds, channelMessageHandlers);\n#endif\n}\n\nflatbuffers::Offset<FBS::Worker::ResourceUsageResponse> Worker::FillBufferResourceUsage(\n  flatbuffers::FlatBufferBuilder& builder) const\n{\n\tMS_TRACE();\n\n\tint err;\n\tuv_rusage_t uvRusage{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\terr = uv_getrusage(std::addressof(uvRusage));\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_getrusage() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn FBS::Worker::CreateResourceUsageResponse(\n\t  builder,\n\t  // Add ru_utime (uv_timeval_t, user CPU time used, converted to ms).\n\t  (uvRusage.ru_utime.tv_sec * static_cast<uint64_t>(1000)) + (uvRusage.ru_utime.tv_usec / 1000),\n\t  // Add ru_stime (uv_timeval_t, system CPU time used, converted to ms).\n\t  (uvRusage.ru_stime.tv_sec * static_cast<uint64_t>(1000)) + (uvRusage.ru_stime.tv_usec / 1000),\n\t  // Add ru_maxrss (uint64_t, maximum resident set size).\n\t  uvRusage.ru_maxrss,\n\n\t  // Add ru_ixrss (uint64_t, integral shared memory size).\n\t  uvRusage.ru_ixrss,\n\n\t  // Add ru_idrss (uint64_t, integral unshared data size).\n\t  uvRusage.ru_idrss,\n\n\t  // Add ru_isrss (uint64_t, integral unshared stack size).\n\t  uvRusage.ru_isrss,\n\n\t  // Add ru_minflt (uint64_t, page reclaims, soft page faults).\n\t  uvRusage.ru_minflt,\n\n\t  // Add ru_majflt (uint64_t, page faults, hard page faults).\n\t  uvRusage.ru_majflt,\n\n\t  // Add ru_nswap (uint64_t, swaps).\n\t  uvRusage.ru_nswap,\n\n\t  // Add ru_inblock (uint64_t, block input operations).\n\t  uvRusage.ru_inblock,\n\n\t  // Add ru_oublock (uint64_t, block output operations).\n\t  uvRusage.ru_oublock,\n\n\t  // Add ru_msgsnd (uint64_t, IPC messages sent).\n\t  uvRusage.ru_msgsnd,\n\n\t  // Add ru_msgrcv (uint64_t, IPC messages received).\n\t  uvRusage.ru_msgrcv,\n\n\t  // Add ru_nsignals (uint64_t, signals received).\n\t  uvRusage.ru_nsignals,\n\t  // Add ru_nvcsw (uint64_t, voluntary context switches).\n\t  uvRusage.ru_nvcsw,\n\t  // Add ru_nivcsw (uint64_t, involuntary context switches).\n\t  uvRusage.ru_nivcsw);\n}\n\nRTC::WebRtcServer* Worker::GetWebRtcServer(const std::string& webRtcServerId) const\n{\n\tauto it = this->mapWebRtcServers.find(webRtcServerId);\n\n\tif (it == this->mapWebRtcServers.end())\n\t{\n\t\tMS_THROW_ERROR(\"WebRtcServer not found\");\n\t}\n\n\treturn it->second;\n}\n\nRTC::Router* Worker::GetRouter(const std::string& routerId) const\n{\n\tMS_TRACE();\n\n\tauto it = this->mapRouters.find(routerId);\n\n\tif (it == this->mapRouters.end())\n\t{\n\t\tMS_THROW_ERROR(\"Router not found\");\n\t}\n\n\treturn it->second;\n}\n\nvoid Worker::CheckNoWebRtcServer(const std::string& webRtcServerId) const\n{\n\tif (this->mapWebRtcServers.find(webRtcServerId) != this->mapWebRtcServers.end())\n\t{\n\t\tMS_THROW_ERROR(\"a WebRtcServer with same webRtcServerId already exists\");\n\t}\n}\n\nvoid Worker::CheckNoRouter(const std::string& routerId) const\n{\n\tif (this->mapRouters.find(routerId) != this->mapRouters.end())\n\t{\n\t\tMS_THROW_ERROR(\"a Router with same routerId already exists\");\n\t}\n}\n\nvoid Worker::HandleRequest(Channel::ChannelRequest* request)\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_DEV(\n\t  \"Channel request received [method:%s, id:%\" PRIu32 \"]\", request->methodCStr, request->id);\n\n\tswitch (request->method)\n\t{\n\t\tcase Channel::ChannelRequest::Method::WORKER_DUMP:\n\t\t{\n\t\t\tauto dumpOffset = FillBuffer(request->GetBufferBuilder());\n\n\t\t\trequest->Accept(FBS::Response::Body::Worker_DumpResponse, dumpOffset);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_GET_RESOURCE_USAGE:\n\t\t{\n\t\t\tauto resourceUsageOffset = FillBufferResourceUsage(request->GetBufferBuilder());\n\n\t\t\trequest->Accept(FBS::Response::Body::Worker_ResourceUsageResponse, resourceUsageOffset);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_UPDATE_SETTINGS:\n\t\t{\n\t\t\tSettings::HandleRequest(request);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_CREATE_WEBRTCSERVER:\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tconst auto* const body = request->data->body_as<FBS::Worker::CreateWebRtcServerRequest>();\n\n\t\t\t\tconst std::string webRtcServerId = body->webRtcServerId()->str();\n\n\t\t\t\tCheckNoWebRtcServer(webRtcServerId);\n\n\t\t\t\tauto* webRtcServer = new RTC::WebRtcServer(this->shared, webRtcServerId, body->listenInfos());\n\n\t\t\t\tthis->mapWebRtcServers[webRtcServerId] = webRtcServer;\n\n\t\t\t\tMS_DEBUG_DEV(\"WebRtcServer created [webRtcServerId:%s]\", webRtcServerId.c_str());\n\n\t\t\t\trequest->Accept();\n\t\t\t}\n\t\t\tcatch (const MediaSoupTypeError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_WEBRTCSERVER_CLOSE:\n\t\t{\n\t\t\tconst RTC::WebRtcServer* webRtcServer{ nullptr };\n\n\t\t\tconst auto* body = request->data->body_as<FBS::Worker::CloseWebRtcServerRequest>();\n\n\t\t\tauto webRtcServerId = body->webRtcServerId()->str();\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\twebRtcServer = GetWebRtcServer(webRtcServerId);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\n\t\t\t// Remove it from the map and delete it.\n\t\t\tthis->mapWebRtcServers.erase(webRtcServer->GetId());\n\n\t\t\tdelete webRtcServer;\n\n\t\t\tMS_DEBUG_DEV(\"WebRtcServer closed [id:%s]\", webRtcServer->id.c_str());\n\n\t\t\trequest->Accept();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_CREATE_ROUTER:\n\t\t{\n\t\t\tconst auto* body = request->data->body_as<FBS::Worker::CreateRouterRequest>();\n\n\t\t\tauto routerId = body->routerId()->str();\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tCheckNoRouter(routerId);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\n\t\t\tauto* router = new RTC::Router(this->shared, routerId, this);\n\n\t\t\tthis->mapRouters[routerId] = router;\n\n\t\t\tMS_DEBUG_DEV(\"Router created [routerId:%s]\", routerId.c_str());\n\n\t\t\trequest->Accept();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Channel::ChannelRequest::Method::WORKER_CLOSE_ROUTER:\n\t\t{\n\t\t\tconst RTC::Router* router{ nullptr };\n\n\t\t\tconst auto* body = request->data->body_as<FBS::Worker::CloseRouterRequest>();\n\n\t\t\tauto routerId = body->routerId()->str();\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\trouter = GetRouter(routerId);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\n\t\t\t// Remove it from the map and delete it.\n\t\t\tthis->mapRouters.erase(router->id);\n\n\t\t\tdelete router;\n\n\t\t\tMS_DEBUG_DEV(\"Router closed [id:%s]\", router->id.c_str());\n\n\t\t\trequest->Accept();\n\n\t\t\tbreak;\n\t\t}\n\n\t\t// Any other request must be delivered to the corresponding Router.\n\t\tdefault:\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tauto* handler =\n\t\t\t\t  this->shared->GetChannelMessageRegistrator()->GetChannelRequestHandler(request->handlerId);\n\n\t\t\t\tif (handler == nullptr)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\"Channel request handler with ID %s not found\", request->handlerId.c_str());\n\t\t\t\t}\n\n\t\t\t\thandler->HandleRequest(request);\n\t\t\t}\n\t\t\tcatch (const MediaSoupTypeError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [method:%s]\", error.what(), request->methodCStr);\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid Worker::HandleNotification(Channel::ChannelNotification* notification)\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_DEV(\"Channel notification received [event:%s]\", notification->eventCStr);\n\n\tswitch (notification->event)\n\t{\n\t\tcase Channel::ChannelNotification::Event::WORKER_CLOSE:\n\t\t{\n\t\t\tif (this->closed)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tMS_DEBUG_DEV(\"closing Worker\");\n\n\t\t\tClose();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tauto* handler = this->shared->GetChannelMessageRegistrator()->GetChannelNotificationHandler(\n\t\t\t\t  notification->handlerId);\n\n\t\t\t\tif (handler == nullptr)\n\t\t\t\t{\n\t\t\t\t\tMS_THROW_ERROR(\n\t\t\t\t\t  \"Channel notification handler with ID %s not found\", notification->handlerId.c_str());\n\t\t\t\t}\n\n\t\t\t\thandler->HandleNotification(notification);\n\t\t\t}\n\t\t\tcatch (const MediaSoupTypeError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_TYPE_ERROR(\"%s [event:%s]\", error.what(), notification->eventCStr);\n\t\t\t}\n\t\t\tcatch (const MediaSoupError& error)\n\t\t\t{\n\t\t\t\tMS_THROW_ERROR(\"%s [event:%s]\", error.what(), notification->eventCStr);\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid Worker::OnChannelClosed(Channel::ChannelSocket* /*socket*/)\n{\n\tMS_TRACE_STD();\n\n\t// Only needed for executable, library user can close channel earlier and it\n\t// is fine.\n#ifdef MS_EXECUTABLE\n\t// If the pipe is remotely closed it may mean that mediasoup Node process\n\t// abruptly died (SIGKILL?) so we must die.\n\tMS_ERROR_STD(\"channel remotely closed, closing myself\");\n#endif\n\n\tClose();\n}\n\nvoid Worker::OnSignal(SignalHandle* /*signalHandle*/, int signum)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tswitch (signum)\n\t{\n\t\tcase SIGINT:\n\t\tcase SIGTERM:\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"%s signal received, closing myself\", signum == SIGINT ? \"INT\" : \"TERM\");\n\n\t\t\tClose();\n\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t{\n\t\t\tMS_WARN_DEV(\"ignoring received non handled signal [signum:%d]\", signum);\n\t\t}\n\t}\n}\n\nRTC::WebRtcServer* Worker::OnRouterNeedWebRtcServer(RTC::Router* /*router*/, std::string& webRtcServerId)\n{\n\tMS_TRACE();\n\n\tRTC::WebRtcServer* webRtcServer{ nullptr }; // NOLINT(misc-const-correctness)\n\n\tconst auto it = this->mapWebRtcServers.find(webRtcServerId);\n\n\tif (it != this->mapWebRtcServers.end())\n\t{\n\t\twebRtcServer = it->second;\n\t}\n\n\treturn webRtcServer;\n}\n"
  },
  {
    "path": "worker/src/handles/BackoffTimerHandle.cpp",
    "content": "#define MS_CLASS \"BackoffTimerHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/BackoffTimerHandle.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <algorithm> // std::min()\n\n/* Instance methods. */\n\nBackoffTimerHandle::BackoffTimerHandle(BackoffTimerHandleOptions options)\n  : listener(options.listener),\n    label(std::move(options.label)),\n    baseTimeoutMs(options.baseTimeoutMs),\n    backoffAlgorithm(options.backoffAlgorithm),\n    maxBackoffTimeoutMs(options.maxBackoffTimeoutMs),\n    maxRestarts(options.maxRestarts)\n{\n\tMS_TRACE();\n\n\tif (!this->listener)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"options.listener must be given\");\n\t}\n\n\tif (this->label.empty())\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"options.label must be given\");\n\t}\n\n\tif (this->baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs)\n\t{\n\t\tMS_THROW_ERROR(\n\t\t  \"[%s] base timeout (%\" PRIu64 \" ms) cannot be greater than %\" PRIu64 \" ms\",\n\t\t  this->label.c_str(),\n\t\t  this->baseTimeoutMs,\n\t\t  BackoffTimerHandleInterface::MaxTimeoutMs);\n\t}\n\n\tthis->timer = new TimerHandle(this);\n}\n\nBackoffTimerHandle::~BackoffTimerHandle()\n{\n\tMS_TRACE();\n\n\tdelete this->timer;\n\tthis->timer = nullptr;\n}\n\nvoid BackoffTimerHandle::Start()\n{\n\tMS_TRACE();\n\n\tthis->timer->Start(this->baseTimeoutMs);\n\n\tthis->running         = true;\n\tthis->expirationCount = 0;\n}\n\nvoid BackoffTimerHandle::Stop()\n{\n\tMS_TRACE();\n\n\tthis->timer->Stop();\n\n\tthis->running         = false;\n\tthis->expirationCount = 0;\n}\n\nvoid BackoffTimerHandle::SetBaseTimeoutMs(uint64_t baseTimeoutMs)\n{\n\tMS_TRACE();\n\n\tif (baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs)\n\t{\n\t\tMS_THROW_ERROR(\n\t\t  \"[%s] base timeout (%\" PRIu64 \" ms) cannot be greater than %\" PRIu64 \" ms\",\n\t\t  this->label.c_str(),\n\t\t  baseTimeoutMs,\n\t\t  BackoffTimerHandleInterface::MaxTimeoutMs);\n\t}\n\n\tthis->baseTimeoutMs = baseTimeoutMs;\n}\n\nuint64_t BackoffTimerHandle::ComputeNextTimeoutMs() const\n{\n\tMS_TRACE();\n\n\tauto expirationCount = this->expirationCount;\n\n\tswitch (this->backoffAlgorithm)\n\t{\n\t\tcase BackoffAlgorithm::FIXED:\n\t\t{\n\t\t\treturn this->baseTimeoutMs;\n\t\t}\n\n\t\tcase BackoffAlgorithm::EXPONENTIAL:\n\t\t{\n\t\t\tauto timeoutMs = this->baseTimeoutMs;\n\n\t\t\twhile (expirationCount > 0 && timeoutMs < BackoffTimerHandleInterface::MaxTimeoutMs)\n\t\t\t{\n\t\t\t\ttimeoutMs *= 2;\n\t\t\t\t--expirationCount;\n\n\t\t\t\tif (this->maxBackoffTimeoutMs.has_value() && timeoutMs > this->maxBackoffTimeoutMs.value())\n\t\t\t\t{\n\t\t\t\t\treturn this->maxBackoffTimeoutMs.value();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn std::min<uint64_t>(timeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs);\n\t\t}\n\n\t\t\tNO_DEFAULT_GCC();\n\t}\n}\n\nvoid BackoffTimerHandle::OnTimer(TimerHandleInterface* /*timer*/)\n{\n\tMS_TRACE();\n\n\tthis->expirationCount++;\n\n\t// Compute whether the BackoffTimer should still be running after this timeout\n\t// expiration so the parent can check IsRunning() within the `OnBackoffTimer()`\n\t// callback.\n\tthis->running =\n\t  !this->maxRestarts.has_value() || this->expirationCount <= this->maxRestarts.value();\n\n\tuint64_t baseTimeoutMs{ this->baseTimeoutMs };\n\tbool stop{ false };\n\n\t// Call the listener by passing base timeout as reference so the parent has\n\t// a chance to change it and affect the next timeout.\n\tthis->listener->OnBackoffTimer(this, baseTimeoutMs, stop);\n\n\t// If the parent has set `stop` to true it means that it has deleted the\n\t// instance, so stop here.\n\tif (stop)\n\t{\n\t\treturn;\n\t}\n\n\t// NOTE: This may throw.\n\tSetBaseTimeoutMs(baseTimeoutMs);\n\n\t// The caller may have called Stop() within the callback so we must check\n\t// the `running` flag.\n\tif (this->running)\n\t{\n\t\tauto nextTimeoutMs = ComputeNextTimeoutMs();\n\n\t\tthis->timer->Start(nextTimeoutMs);\n\t}\n}\n"
  },
  {
    "path": "worker/src/handles/SignalHandle.cpp",
    "content": "#define MS_CLASS \"SignalHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/SignalHandle.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\n/* Static methods for UV callbacks. */\n\ninline static void onSignal(uv_signal_t* handle, int signum)\n{\n\tstatic_cast<SignalHandle*>(handle->data)->OnUvSignal(signum);\n}\n\ninline static void onCloseSignal(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_signal_t*>(handle);\n}\n\n/* Instance methods. */\n\nSignalHandle::SignalHandle(Listener* listener) : listener(listener)\n{\n\tMS_TRACE();\n}\n\nSignalHandle::~SignalHandle()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\tInternalClose();\n\t}\n}\n\nvoid SignalHandle::AddSignal(int signum, const std::string& name)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tMS_THROW_ERROR(\"closed\");\n\t}\n\n\tint err;\n\tauto* uvHandle = new uv_signal_t;\n\n\tuvHandle->data = static_cast<void*>(this);\n\n\terr = uv_signal_init(DepLibUV::GetLoop(), uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tdelete uvHandle;\n\n\t\tMS_THROW_ERROR(\"uv_signal_init() failed for signal %s: %s\", name.c_str(), uv_strerror(err));\n\t}\n\n\terr = uv_signal_start(uvHandle, static_cast<uv_signal_cb>(onSignal), signum);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_signal_start() failed for signal %s: %s\", name.c_str(), uv_strerror(err));\n\t}\n\n\t// Enter the UV handle into the vector.\n\tthis->uvHandles.push_back(uvHandle);\n}\n\nvoid SignalHandle::InternalClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tthis->closed = true;\n\n\tfor (auto* uvHandle : this->uvHandles)\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(uvHandle), static_cast<uv_close_cb>(onCloseSignal));\n\t}\n}\n\nvoid SignalHandle::OnUvSignal(int signum)\n{\n\tMS_TRACE();\n\n\t// Notify the listener.\n\tthis->listener->OnSignal(this, signum);\n}\n"
  },
  {
    "path": "worker/src/handles/TcpConnectionHandle.cpp",
    "content": "#define MS_CLASS \"TcpConnectionHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/TcpConnectionHandle.hpp\"\n#include \"DepLibUV.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memcpy()\n\n/* Static methods for UV callbacks. */\n\ninline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)\n{\n\tauto* connection = static_cast<TcpConnectionHandle*>(handle->data);\n\n\tif (connection)\n\t{\n\t\tconnection->OnUvReadAlloc(suggestedSize, buf);\n\t}\n}\n\ninline static void onRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)\n{\n\tauto* connection = static_cast<TcpConnectionHandle*>(handle->data);\n\n\tif (connection)\n\t{\n\t\tconnection->OnUvRead(nread, buf);\n\t}\n}\n\ninline static void onWrite(uv_write_t* req, int status)\n{\n\tauto* writeData  = static_cast<TcpConnectionHandle::UvWriteData*>(req->data);\n\tauto* handle     = req->handle;\n\tauto* connection = static_cast<TcpConnectionHandle*>(handle->data);\n\tconst auto* cb   = writeData->cb;\n\n\tif (connection)\n\t{\n\t\tconnection->OnUvWrite(status, cb);\n\t}\n\n\t// Delete the UvWriteData struct and the cb.\n\tdelete writeData;\n}\n\n// NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by\n// ensuring that we call `delete xxx` with same type as `new xxx` before.\ninline static void onCloseTcp(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_tcp_t*>(handle);\n}\n\ninline static void onShutdown(uv_shutdown_t* req, int /*status*/)\n{\n\tauto* handle = req->handle;\n\n\tdelete req;\n\n\t// Now do close the handle.\n\tuv_close(reinterpret_cast<uv_handle_t*>(handle), static_cast<uv_close_cb>(onCloseTcp));\n}\n\n/* Instance methods. */\n\n// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\nTcpConnectionHandle::TcpConnectionHandle(size_t bufferSize)\n  : bufferSize(bufferSize), uvHandle(new uv_tcp_t)\n{\n\tMS_TRACE();\n\n\tthis->uvHandle->data = static_cast<void*>(this);\n\n\t// NOTE: Don't allocate the buffer here. Instead wait for the first uv_alloc_cb().\n}\n\nTcpConnectionHandle::~TcpConnectionHandle()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\tInternalClose();\n\t}\n\n\tdelete[] this->buffer;\n}\n\nvoid TcpConnectionHandle::TriggerClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tInternalClose();\n\n\tthis->listener->OnTcpConnectionClosed(this);\n}\n\nvoid TcpConnectionHandle::Dump(int indentation) const\n{\n\tMS_DUMP_CLEAN(indentation, \"<TcpConnectionHandle>\");\n\tMS_DUMP_CLEAN(indentation, \"  local IP: %s\", this->localIp.c_str());\n\tMS_DUMP_CLEAN(indentation, \"  local port: %\" PRIu16, static_cast<uint16_t>(this->localPort));\n\tMS_DUMP_CLEAN(indentation, \"  remote IP: %s\", this->peerIp.c_str());\n\tMS_DUMP_CLEAN(indentation, \"  remote port: %\" PRIu16, static_cast<uint16_t>(this->peerPort));\n\tMS_DUMP_CLEAN(indentation, \"  closed: %s\", this->closed ? \"yes\" : \"no\");\n\tMS_DUMP_CLEAN(indentation, \"</TcpConnectionHandle>\");\n}\n\nvoid TcpConnectionHandle::Setup(\n  Listener* listener, struct sockaddr_storage* localAddr, const std::string& localIp, uint16_t localPort)\n{\n\tMS_TRACE();\n\n\t// Set the UV handle.\n\tconst int err = uv_tcp_init(DepLibUV::GetLoop(), this->uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tdelete this->uvHandle;\n\t\tthis->uvHandle = nullptr;\n\n\t\tMS_THROW_ERROR(\"uv_tcp_init() failed: %s\", uv_strerror(err));\n\t}\n\n\t// Set the listener.\n\tthis->listener = listener;\n\n\t// Set the local address.\n\tthis->localAddr = localAddr;\n\tthis->localIp   = localIp;\n\tthis->localPort = localPort;\n}\n\nvoid TcpConnectionHandle::Start()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\t// NOLINTNEXTLINE(misc-const-correctness)\n\tint err = uv_read_start(\n\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t  static_cast<uv_alloc_cb>(onAlloc),\n\t  static_cast<uv_read_cb>(onRead));\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_read_start() failed: %s\", uv_strerror(err));\n\t}\n\n\t// Get the peer address.\n\tif (!SetPeerAddress())\n\t{\n\t\tMS_THROW_ERROR(\"error setting peer IP and port\");\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\terr = uv_fileno(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(this->fd));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"uv_fileno() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n#endif\n}\n\nvoid TcpConnectionHandle::Write(\n  const uint8_t* data1,\n  size_t len1,\n  const uint8_t* data2,\n  size_t len2,\n  TcpConnectionHandle::onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif (len1 == 0 && len2 == 0)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\tif (!DepLibUring::IsActive())\n\t\t{\n\t\t\tgoto write_libuv;\n\t\t}\n\n\t\t// Prepare the data to be sent.\n\t\t// NOTE: If all SQEs are currently in use or no UserData entry is available we'll\n\t\t// fall back to libuv.\n\t\tauto prepared = DepLibUring::PrepareWrite(this->fd, data1, len1, data2, len2, cb);\n\n\t\tif (!prepared)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"cannot write via liburing, fallback to libuv\");\n\n\t\t\tgoto write_libuv;\n\t\t}\n\n\t\treturn;\n\t}\n\nwrite_libuv:\n#endif\n\n\t// First try uv_try_write(). In case it can not directly write all the given\n\t// data then build a uv_req_t and use uv_write().\n\n\tconst size_t totalLen = len1 + len2;\n\tuv_buf_t buffers[2];\n\tint written{ 0 };\n\tint err;\n\n\tbuffers[0] = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data1)), len1);\n\tbuffers[1] = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data2)), len2);\n\twritten    = uv_try_write(reinterpret_cast<uv_stream_t*>(this->uvHandle), buffers, 2);\n\n\t// All the data was written. Done.\n\tif (written == static_cast<int>(totalLen))\n\t{\n\t\t// Update sent bytes.\n\t\tthis->sentBytes += written;\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\t// Cannot write any data at first time. Use uv_write().\n\telse if (written == UV_EAGAIN || written == UV_ENOSYS)\n\t{\n\t\t// Set written to 0 so pendingLen can be properly calculated.\n\t\twritten = 0;\n\t}\n\t// Any other error.\n\telse if (written < 0)\n\t{\n\t\tMS_WARN_DEV(\"uv_try_write() failed, trying uv_write(): %s\", uv_strerror(written));\n\n\t\t// Set written to 0 so pendingLen can be properly calculated.\n\t\twritten = 0;\n\t}\n\n\tconst size_t pendingLen = totalLen - written;\n\tauto* writeData         = new UvWriteData(pendingLen);\n\n\twriteData->req.data = static_cast<void*>(writeData);\n\n\t// If the first buffer was not entirely written then splice it.\n\tif (static_cast<size_t>(written) < len1)\n\t{\n\t\tstd::memcpy(\n\t\t  writeData->store, data1 + static_cast<size_t>(written), len1 - static_cast<size_t>(written));\n\t\tstd::memcpy(writeData->store + (len1 - static_cast<size_t>(written)), data2, len2);\n\t}\n\t// Otherwise just take the pending data in the second buffer.\n\telse\n\t{\n\t\tstd::memcpy(\n\t\t  writeData->store,\n\t\t  data2 + (static_cast<size_t>(written) - len1),\n\t\t  len2 - (static_cast<size_t>(written) - len1));\n\t}\n\n\twriteData->cb = cb;\n\n\tconst uv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(writeData->store), pendingLen);\n\n\terr = uv_write(\n\t  &writeData->req,\n\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t  &buffer,\n\t  1,\n\t  static_cast<uv_write_cb>(onWrite));\n\n\tif (err != 0)\n\t{\n\t\tMS_WARN_DEV(\"uv_write() failed: %s\", uv_strerror(err));\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t}\n\n\t\t// Delete the UvWriteData struct (it will delete the store and cb too).\n\t\tdelete writeData;\n\t}\n\telse\n\t{\n\t\t// Update sent bytes.\n\t\tthis->sentBytes += pendingLen;\n\t}\n}\n\nvoid TcpConnectionHandle::ErrorReceiving()\n{\n\tMS_TRACE();\n\n\tInternalClose();\n\n\tthis->listener->OnTcpConnectionClosed(this);\n}\n\nvoid TcpConnectionHandle::InternalClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tint err;\n\n\tthis->closed = true;\n\n\t// Tell the UV handle that the TcpConnectionHandle has been closed.\n\tthis->uvHandle->data = nullptr;\n\n\t// Don't read more.\n\terr = uv_read_stop(reinterpret_cast<uv_stream_t*>(this->uvHandle));\n\n\tif (err != 0)\n\t{\n\t\ttry\n\t\t{\n\t\t\tMS_ABORT(\"uv_read_stop() failed: %s\", uv_strerror(err));\n\t\t}\n\t\tcatch (const std::exception& e)\n\t\t{\n\t\t\tMS_ERROR(\"%s\", e.what());\n\t\t}\n\t}\n\n\t// If there is no error and the peer didn't close its connection side then close gracefully.\n\tif (!this->hasError && !this->isClosedByPeer)\n\t{\n\t\t// Use uv_shutdown() so pending data to be written will be sent to the peer\n\t\t// before closing.\n\t\tauto* req = new uv_shutdown_t;\n\t\treq->data = static_cast<void*>(this);\n\t\terr       = uv_shutdown(\n\t\t  req, reinterpret_cast<uv_stream_t*>(this->uvHandle), static_cast<uv_shutdown_cb>(onShutdown));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tMS_ABORT(\"uv_shutdown() failed: %s\", uv_strerror(err));\n\t\t\t}\n\t\t\tcatch (const std::exception& e)\n\t\t\t{\n\t\t\t\tMS_ERROR(\"%s\", e.what());\n\t\t\t}\n\t\t}\n\t}\n\t// Otherwise directly close the socket.\n\telse\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseTcp));\n\t}\n}\n\nbool TcpConnectionHandle::SetPeerAddress()\n{\n\tMS_TRACE();\n\n\tint err;\n\tint len = sizeof(this->peerAddr);\n\n\terr = uv_tcp_getpeername(this->uvHandle, reinterpret_cast<struct sockaddr*>(&this->peerAddr), &len);\n\n\tif (err != 0)\n\t{\n\t\tMS_ERROR(\"uv_tcp_getpeername() failed: %s\", uv_strerror(err));\n\n\t\treturn false;\n\t}\n\n\tint family;\n\n\tUtils::IP::GetAddressInfo(\n\t  reinterpret_cast<const struct sockaddr*>(&this->peerAddr), family, this->peerIp, this->peerPort);\n\n\treturn true;\n}\n\ninline void TcpConnectionHandle::OnUvReadAlloc(size_t /*suggestedSize*/, uv_buf_t* buf)\n{\n\tMS_TRACE();\n\n\t// If this is the first call to onUvReadAlloc() then allocate the receiving\n\t// buffer now.\n\tif (!this->buffer)\n\t{\n\t\tthis->buffer = new uint8_t[this->bufferSize];\n\t}\n\n\t// Tell UV to write after the last data byte in the buffer.\n\tbuf->base = reinterpret_cast<char*>(this->buffer + this->bufferDataLen);\n\n\t// Give UV all the remaining space in the buffer.\n\tif (this->bufferSize > this->bufferDataLen)\n\t{\n\t\tbuf->len = this->bufferSize - this->bufferDataLen;\n\t}\n\telse\n\t{\n\t\tbuf->len = 0;\n\n\t\tMS_WARN_DEV(\"no available space in the buffer\");\n\t}\n}\n\ninline void TcpConnectionHandle::OnUvRead(ssize_t nread, const uv_buf_t* /*buf*/)\n{\n\tMS_TRACE();\n\n\tif (nread == 0)\n\t{\n\t\treturn;\n\t}\n\n\t// Data received.\n\tif (nread > 0)\n\t{\n\t\t// Update received bytes.\n\t\tthis->recvBytes += nread;\n\n\t\t// Update the buffer data length.\n\t\tthis->bufferDataLen += static_cast<size_t>(nread);\n\n\t\t// Notify the subclass.\n\t\tUserOnTcpConnectionRead();\n\t}\n\t// Client disconnected.\n\telse if (nread == UV_EOF || nread == UV_ECONNRESET)\n\t{\n\t\tMS_DEBUG_DEV(\"connection closed by peer, closing server side\");\n\n\t\tthis->isClosedByPeer = true;\n\n\t\t// Close server side of the connection.\n\t\tInternalClose();\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTcpConnectionClosed(this);\n\t}\n\t// Some error.\n\telse\n\t{\n\t\tMS_WARN_DEV(\"read error, closing the connection: %s\", uv_strerror(nread));\n\n\t\tthis->hasError = true;\n\n\t\t// Close server side of the connection.\n\t\tInternalClose();\n\n\t\t// Notify the listener.\n\t\tthis->listener->OnTcpConnectionClosed(this);\n\t}\n}\n\ninline void TcpConnectionHandle::OnUvWrite(int status, TcpConnectionHandle::onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\t// NOTE: Do not delete cb here since it will be delete in onWrite() above.\n\n\tif (status == 0)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true);\n\t\t}\n\t}\n\telse\n\t{\n\t\tif (status != UV_EPIPE && status != UV_ENOTCONN)\n\t\t{\n\t\t\tthis->hasError = true;\n\t\t}\n\n\t\tMS_WARN_DEV(\"write error, closing the connection: %s\", uv_strerror(status));\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t}\n\n\t\tInternalClose();\n\n\t\tthis->listener->OnTcpConnectionClosed(this);\n\t}\n}\n"
  },
  {
    "path": "worker/src/handles/TcpServerHandle.cpp",
    "content": "#define MS_CLASS \"TcpServerHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/TcpServerHandle.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n\n/* Static. */\n\nstatic constexpr int ListenBacklog{ 512 };\n\n/* Static methods for UV callbacks. */\n\ninline static void onConnection(uv_stream_t* handle, int status)\n{\n\tauto* server = static_cast<TcpServerHandle*>(handle->data);\n\n\tif (server)\n\t{\n\t\tserver->OnUvConnection(status);\n\t}\n}\n\ninline static void onCloseTcp(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_tcp_t*>(handle);\n}\n\n/* Instance methods. */\n\n// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\nTcpServerHandle::TcpServerHandle(uv_tcp_t* uvHandle) : uvHandle(uvHandle)\n{\n\tMS_TRACE();\n\n\tint err;\n\n\tthis->uvHandle->data = static_cast<void*>(this);\n\n\terr = uv_listen(\n\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t  ListenBacklog,\n\t  static_cast<uv_connection_cb>(onConnection));\n\n\tif (err != 0)\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseTcp));\n\n\t\tMS_THROW_ERROR(\"uv_listen() failed: %s\", uv_strerror(err));\n\t}\n\n\t// Set local address.\n\tif (!SetLocalAddress())\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseTcp));\n\n\t\tMS_THROW_ERROR(\"error setting local IP and port\");\n\t}\n}\n\nTcpServerHandle::~TcpServerHandle()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\tInternalClose();\n\t}\n}\n\nvoid TcpServerHandle::Dump(int indentation) const\n{\n\tMS_DUMP_CLEAN(indentation, \"<TcpServerHandle>\");\n\tMS_DUMP_CLEAN(indentation, \"  local IP: %s\", this->localIp.c_str());\n\tMS_DUMP_CLEAN(indentation, \"  local port: %\" PRIu16, static_cast<uint16_t>(this->localPort));\n\tMS_DUMP_CLEAN(indentation, \"  num connections: %zu\", this->connections.size());\n\tMS_DUMP_CLEAN(indentation, \"  closed: %s\", this->closed ? \"yes\" : \"no\");\n\tMS_DUMP_CLEAN(indentation, \"</TcpServerHandle>\");\n}\n\nuint32_t TcpServerHandle::GetSendBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid TcpServerHandle::SetSendBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\nuint32_t TcpServerHandle::GetRecvBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid TcpServerHandle::SetRecvBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid TcpServerHandle::AcceptTcpConnection(TcpConnectionHandle* connection)\n{\n\tMS_TRACE();\n\n\tMS_ASSERT(connection != nullptr, \"TcpConnectionHandle pointer was not allocated by the user\");\n\n\ttry\n\t{\n\t\tconnection->Setup(this, &(this->localAddr), this->localIp, this->localPort);\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tdelete connection;\n\n\t\treturn;\n\t}\n\n\t// Accept the connection.\n\tconst int err = uv_accept(\n\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t  reinterpret_cast<uv_stream_t*>(connection->GetUvHandle()));\n\n\tif (err != 0)\n\t{\n\t\tMS_ABORT(\"uv_accept() failed: %s\", uv_strerror(err));\n\t}\n\n\t// Start receiving data.\n\ttry\n\t{\n\t\t// NOTE: This may throw.\n\t\tconnection->Start();\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tdelete connection;\n\n\t\treturn;\n\t}\n\n\t// Store it.\n\tthis->connections.insert(connection);\n}\n\nvoid TcpServerHandle::InternalClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tthis->closed = true;\n\n\t// Tell the UV handle that the TcpServerHandle has been closed.\n\tthis->uvHandle->data = nullptr;\n\n\tMS_DEBUG_DEV(\"closing %zu active connections\", this->connections.size());\n\n\tfor (auto* connection : this->connections)\n\t{\n\t\tdelete connection;\n\t}\n\n\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseTcp));\n}\n\nbool TcpServerHandle::SetLocalAddress()\n{\n\tMS_TRACE();\n\n\tint err;\n\tint len = sizeof(this->localAddr);\n\n\terr =\n\t  uv_tcp_getsockname(this->uvHandle, reinterpret_cast<struct sockaddr*>(&this->localAddr), &len);\n\n\tif (err != 0)\n\t{\n\t\tMS_ERROR(\"uv_tcp_getsockname() failed: %s\", uv_strerror(err));\n\n\t\treturn false;\n\t}\n\n\tint family;\n\n\tUtils::IP::GetAddressInfo(\n\t  reinterpret_cast<const struct sockaddr*>(&this->localAddr), family, this->localIp, this->localPort);\n\n\treturn true;\n}\n\ninline void TcpServerHandle::OnUvConnection(int status)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tif (status != 0)\n\t{\n\t\tMS_ERROR(\"error while receiving a new TCP connection: %s\", uv_strerror(status));\n\n\t\treturn;\n\t}\n\n\t// Notify the subclass about a new TCP connection attempt.\n\tUserOnTcpConnectionAlloc();\n}\n\ninline void TcpServerHandle::OnTcpConnectionClosed(TcpConnectionHandle* connection)\n{\n\tMS_TRACE();\n\n\tMS_DEBUG_DEV(\"TCP connection closed\");\n\n\t// Remove the TcpConnectionHandle from the set.\n\tthis->connections.erase(connection);\n\n\t// Notify the subclass.\n\tUserOnTcpConnectionClosed(connection);\n\n\t// Delete it.\n\tdelete connection;\n}\n"
  },
  {
    "path": "worker/src/handles/TimerHandle.cpp",
    "content": "#define MS_CLASS \"TimerHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/TimerHandle.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n\n/* Static methods for UV callbacks. */\n\nstatic void onTimer(uv_timer_t* handle)\n{\n\tstatic_cast<TimerHandle*>(handle->data)->OnUvTimer();\n}\n\nstatic void onCloseTimer(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_timer_t*>(handle);\n}\n\n/* Instance methods. */\n\nTimerHandle::TimerHandle(TimerHandleInterface::Listener* listener)\n  : listener(listener), uvHandle(new uv_timer_t)\n{\n\tMS_TRACE();\n\n\tthis->uvHandle->data = static_cast<void*>(this);\n\n\tconst int err = uv_timer_init(DepLibUV::GetLoop(), this->uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tdelete this->uvHandle;\n\t\tthis->uvHandle = nullptr;\n\n\t\tMS_THROW_ERROR(\"uv_timer_init() failed: %s\", uv_strerror(err));\n\t}\n}\n\nTimerHandle::~TimerHandle()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\tInternalClose();\n\t}\n}\n\nvoid TimerHandle::Start(uint64_t timeout, uint64_t repeat)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tMS_THROW_ERROR(\"closed\");\n\t}\n\n\tthis->timeout = timeout;\n\tthis->repeat  = repeat;\n\n\tint err;\n\n\tif (uv_is_active(reinterpret_cast<uv_handle_t*>(this->uvHandle)) != 0)\n\t{\n\t\terr = uv_timer_stop(this->uvHandle);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"uv_timer_stop() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n\n\terr =\n\t  uv_timer_start(this->uvHandle, static_cast<uv_timer_cb>(onTimer), this->timeout, this->repeat);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_timer_start() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid TimerHandle::Stop()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tMS_THROW_ERROR(\"closed\");\n\t}\n\n\tconst int err = uv_timer_stop(this->uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_timer_stop() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid TimerHandle::Restart()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tMS_THROW_ERROR(\"closed\");\n\t}\n\n\tint err;\n\n\tif (uv_is_active(reinterpret_cast<uv_handle_t*>(this->uvHandle)) != 0)\n\t{\n\t\terr = uv_timer_stop(this->uvHandle);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"uv_timer_stop() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n\n\terr =\n\t  uv_timer_start(this->uvHandle, static_cast<uv_timer_cb>(onTimer), this->timeout, this->repeat);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_timer_start() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid TimerHandle::Restart(uint64_t timeout, uint64_t repeat)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tMS_THROW_ERROR(\"closed\");\n\t}\n\n\tthis->timeout = timeout;\n\tthis->repeat  = repeat;\n\n\tint err;\n\n\tif (uv_is_active(reinterpret_cast<uv_handle_t*>(this->uvHandle)) != 0)\n\t{\n\t\terr = uv_timer_stop(this->uvHandle);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"uv_timer_stop() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n\n\terr =\n\t  uv_timer_start(this->uvHandle, static_cast<uv_timer_cb>(onTimer), this->timeout, this->repeat);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"uv_timer_start() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid TimerHandle::InternalClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tthis->closed = true;\n\n\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseTimer));\n}\n\nvoid TimerHandle::OnUvTimer()\n{\n\tMS_TRACE();\n\n\t// Notify the listener.\n\tthis->listener->OnTimer(this);\n}\n"
  },
  {
    "path": "worker/src/handles/UdpSocketHandle.cpp",
    "content": "#define MS_CLASS \"UdpSocketHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/UdpSocketHandle.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <cstring> // std::memcpy()\n\n/* Static. */\n\nstatic constexpr size_t ReadBufferSize{ 65536 };\n// NOTE: Buffer must be 4-byte aligned since RTP/RTCP/STUN packet parsing casts\n// it to structs (e.g. RTP::Packet::FixedHeader) that require 4-byte alignment.\n// Without this, accessing multi-byte fields would be undefined behavior on\n// strict-alignment architectures.\nalignas(4) static thread_local uint8_t ReadBuffer[ReadBufferSize];\n\n/* Static methods for UV callbacks. */\n\ninline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)\n{\n\tauto* socket = static_cast<UdpSocketHandle*>(handle->data);\n\n\tif (socket)\n\t{\n\t\tsocket->OnUvRecvAlloc(suggestedSize, buf);\n\t}\n}\n\ninline static void onRecv(\n  uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags)\n{\n\tauto* socket = static_cast<UdpSocketHandle*>(handle->data);\n\n\tif (socket)\n\t{\n\t\tsocket->OnUvRecv(nread, buf, addr, flags);\n\t}\n}\n\ninline static void onSend(uv_udp_send_t* req, int status)\n{\n\tauto* sendData = static_cast<UdpSocketHandle::UvSendData*>(req->data);\n\tauto* handle   = req->handle;\n\tauto* socket   = static_cast<UdpSocketHandle*>(handle->data);\n\tconst auto* cb = sendData->cb;\n\n\tif (socket)\n\t{\n\t\tsocket->OnUvSend(status, cb);\n\t}\n\n\t// Delete the UvSendData struct (it will delete the store and cb too).\n\tdelete sendData;\n}\n\ninline static void onCloseUdp(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_udp_t*>(handle);\n}\n\n/* Instance methods. */\n\n// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)\nUdpSocketHandle::UdpSocketHandle(uv_udp_t* uvHandle) : uvHandle(uvHandle)\n{\n\tMS_TRACE();\n\n\tthis->uvHandle->data = static_cast<void*>(this);\n\n\t// NOLINTNEXTLINE(misc-const-correctness)\n\tint err = uv_udp_recv_start(\n\t  this->uvHandle, static_cast<uv_alloc_cb>(onAlloc), static_cast<uv_udp_recv_cb>(onRecv));\n\n\tif (err != 0)\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseUdp));\n\n\t\tMS_THROW_ERROR(\"uv_udp_recv_start() failed: %s\", uv_strerror(err));\n\t}\n\n\t// Set local address.\n\tif (!SetLocalAddress())\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseUdp));\n\n\t\tMS_THROW_ERROR(\"error setting local IP and port\");\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\terr = uv_fileno(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(this->fd));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"uv_fileno() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n#endif\n}\n\nUdpSocketHandle::~UdpSocketHandle()\n{\n\tMS_TRACE();\n\n\tif (!this->closed)\n\t{\n\t\ttry\n\t\t{\n\t\t\tInternalClose();\n\t\t}\n\t\tcatch (const std::exception& e)\n\t\t{\n\t\t\tMS_ERROR(\"error closing UDP socket: %s\", e.what());\n\t\t}\n\t}\n}\n\nvoid UdpSocketHandle::Dump(int indentation) const\n{\n\tMS_DUMP_CLEAN(indentation, \"<UdpSocketHandle>\");\n\tMS_DUMP_CLEAN(indentation, \"  local IP: %s\", this->localIp.c_str());\n\tMS_DUMP_CLEAN(indentation, \"  local port: %\" PRIu16, static_cast<uint16_t>(this->localPort));\n\tMS_DUMP_CLEAN(indentation, \"  closed: %s\", this->closed ? \"yes\" : \"no\");\n\tMS_DUMP_CLEAN(indentation, \"</UdpSocketHandle>\");\n}\n\nvoid UdpSocketHandle::Send(\n  const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandle::onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\n\tif (len == 0)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\n#ifdef MS_LIBURING_SUPPORTED\n\tif (DepLibUring::IsEnabled())\n\t{\n\t\tif (!DepLibUring::IsActive())\n\t\t{\n\t\t\tgoto send_libuv;\n\t\t}\n\n\t\t// Prepare the data to be sent.\n\t\t// NOTE: If all SQEs are currently in use or no UserData entry is available we'll\n\t\t// fall back to libuv.\n\t\tauto prepared = DepLibUring::PrepareSend(this->fd, data, len, addr, cb);\n\n\t\tif (!prepared)\n\t\t{\n\t\t\tMS_DEBUG_DEV(\"cannot send via liburing, fallback to libuv\");\n\n\t\t\tgoto send_libuv;\n\t\t}\n\n\t\treturn;\n\t}\n\nsend_libuv:\n#endif\n\n\t// First try uv_udp_try_send(). In case it can not directly send the datagram\n\t// then build a uv_req_t and use uv_udp_send().\n\n\tuv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data)), len);\n\tconst int sent  = uv_udp_try_send(this->uvHandle, &buffer, 1, addr);\n\n\t// Entire datagram was sent. Done.\n\tif (sent == static_cast<int>(len))\n\t{\n\t\t// Update sent bytes.\n\t\tthis->sentBytes += sent;\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\telse if (sent >= 0)\n\t{\n\t\tMS_WARN_DEV(\"datagram truncated (just %d of %zu bytes were sent)\", sent, len);\n\n\t\t// Update sent bytes.\n\t\tthis->sentBytes += sent;\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t\tdelete cb;\n\t\t}\n\n\t\treturn;\n\t}\n\t// Any error but legit EAGAIN. Use uv_udp_send().\n\telse if (sent != UV_EAGAIN)\n\t{\n\t\tMS_WARN_DEV(\"uv_udp_try_send() failed, trying uv_udp_send(): %s\", uv_strerror(sent));\n\t}\n\n\tauto* sendData = new UvSendData(len);\n\n\tsendData->req.data = static_cast<void*>(sendData);\n\tstd::memcpy(sendData->store, data, len);\n\tsendData->cb = cb;\n\n\tbuffer = uv_buf_init(reinterpret_cast<char*>(sendData->store), len);\n\n\tconst int err = uv_udp_send(\n\t  &sendData->req, this->uvHandle, &buffer, 1, addr, static_cast<uv_udp_send_cb>(onSend));\n\n\tif (err != 0)\n\t{\n\t\t// NOTE: uv_udp_send() returns error if a wrong INET family is given\n\t\t// (IPv6 destination on a IPv4 binded socket), so be ready.\n\t\tMS_WARN_DEV(\"uv_udp_send() failed: %s\", uv_strerror(err));\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t}\n\n\t\t// Delete the UvSendData struct (it will delete the store and cb too).\n\t\tdelete sendData;\n\t}\n\telse\n\t{\n\t\t// Update sent bytes.\n\t\tthis->sentBytes += len;\n\t}\n}\n\nuint32_t UdpSocketHandle::GetSendBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid UdpSocketHandle::SetSendBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\nuint32_t UdpSocketHandle::GetRecvBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid UdpSocketHandle::SetRecvBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\nvoid UdpSocketHandle::InternalClose()\n{\n\tMS_TRACE();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tthis->closed = true;\n\n\t// Tell the UV handle that the UdpSocketHandle has been closed.\n\tthis->uvHandle->data = nullptr;\n\n\t// Don't read more.\n\tconst int err = uv_udp_recv_stop(this->uvHandle);\n\n\tif (err != 0)\n\t{\n\t\tMS_ABORT(\"uv_udp_recv_stop() failed: %s\", uv_strerror(err));\n\t}\n\n\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onCloseUdp));\n}\n\nbool UdpSocketHandle::SetLocalAddress()\n{\n\tMS_TRACE();\n\n\tint err;\n\tint len = sizeof(this->localAddr);\n\n\terr =\n\t  uv_udp_getsockname(this->uvHandle, reinterpret_cast<struct sockaddr*>(&this->localAddr), &len);\n\n\tif (err != 0)\n\t{\n\t\tMS_ERROR(\"uv_udp_getsockname() failed: %s\", uv_strerror(err));\n\n\t\treturn false;\n\t}\n\n\tint family;\n\n\tUtils::IP::GetAddressInfo(\n\t  reinterpret_cast<const struct sockaddr*>(&this->localAddr), family, this->localIp, this->localPort);\n\n\treturn true;\n}\n\ninline void UdpSocketHandle::OnUvRecvAlloc(size_t /*suggestedSize*/, uv_buf_t* buf)\n{\n\tMS_TRACE();\n\n\t// Tell UV to write into the static buffer.\n\tbuf->base = reinterpret_cast<char*>(ReadBuffer);\n\t// Give UV all the buffer space.\n\tbuf->len = ReadBufferSize;\n}\n\ninline void UdpSocketHandle::OnUvRecv(\n  ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags)\n{\n\tMS_TRACE();\n\n\t// NOTE: Ignore if there is nothing to read or if it was an empty datagram.\n\tif (nread == 0)\n\t{\n\t\treturn;\n\t}\n\n\t// Check flags.\n\tif ((flags & UV_UDP_PARTIAL) != 0u)\n\t{\n\t\tMS_ERROR(\"received datagram was truncated due to insufficient buffer, ignoring it\");\n\n\t\treturn;\n\t}\n\n\t// Data received.\n\tif (nread > 0)\n\t{\n\t\t// Update received bytes.\n\t\tthis->recvBytes += nread;\n\n\t\t// Notify the subclass.\n\t\tUserOnUdpDatagramReceived(reinterpret_cast<uint8_t*>(buf->base), nread, ReadBufferSize, addr);\n\t}\n\t// Some error.\n\telse\n\t{\n\t\tMS_DEBUG_DEV(\"read error: %s\", uv_strerror(nread));\n\t}\n}\n\ninline void UdpSocketHandle::OnUvSend(int status, UdpSocketHandle::onSendCallback* cb)\n{\n\tMS_TRACE();\n\n\t// NOTE: Do not delete cb here since it will be delete in onSend() above.\n\n\tif (status == 0)\n\t{\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(true);\n\t\t}\n\t}\n\telse\n\t{\n#if MS_LOG_DEV_LEVEL == 3\n\t\tMS_DEBUG_DEV(\"send error: %s\", uv_strerror(status));\n#endif\n\n\t\tif (cb)\n\t\t{\n\t\t\t(*cb)(false);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/src/handles/UnixStreamSocketHandle.cpp",
    "content": "/**\n * NOTE: This code cannot log to the Channel since this is the base code of the\n * Channel.\n */\n\n#define MS_CLASS \"UnixStreamSocketHandle\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"handles/UnixStreamSocketHandle.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include <cstring> // std::memcpy()\n\n/* Static methods for UV callbacks. */\n\ninline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf)\n{\n\tauto* socket = static_cast<UnixStreamSocketHandle*>(handle->data);\n\n\tif (socket)\n\t{\n\t\tsocket->OnUvReadAlloc(suggestedSize, buf);\n\t}\n}\n\ninline static void onRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf)\n{\n\tauto* socket = static_cast<UnixStreamSocketHandle*>(handle->data);\n\n\tif (socket)\n\t{\n\t\tsocket->OnUvRead(nread, buf);\n\t}\n}\n\ninline static void onWrite(uv_write_t* req, int status)\n{\n\tauto* writeData = static_cast<UnixStreamSocketHandle::UvWriteData*>(req->data);\n\tauto* handle    = req->handle;\n\tauto* socket    = static_cast<UnixStreamSocketHandle*>(handle->data);\n\n\t// Just notify the UnixStreamSocketHandle when error.\n\tif (socket && status != 0)\n\t{\n\t\tsocket->OnUvWriteError(status);\n\t}\n\n\t// Delete the UvWriteData struct.\n\tdelete writeData;\n}\n\n// NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by\n// ensuring that we call `delete xxx` with same type as `new xxx` before.\ninline static void onClosePipe(uv_handle_t* handle)\n{\n\tdelete reinterpret_cast<uv_pipe_t*>(handle);\n}\n\ninline static void onShutdown(uv_shutdown_t* req, int /*status*/)\n{\n\tauto* handle = req->handle;\n\n\tdelete req;\n\n\t// Now do close the handle.\n\tuv_close(reinterpret_cast<uv_handle_t*>(handle), static_cast<uv_close_cb>(onClosePipe));\n}\n\n/* Instance methods. */\n\nUnixStreamSocketHandle::UnixStreamSocketHandle(\n  int fd, size_t bufferSize, UnixStreamSocketHandle::Role role)\n  : uvHandle(new uv_pipe_t), bufferSize(bufferSize), role(role)\n{\n\tMS_TRACE_STD();\n\n\tint err;\n\n\tthis->uvHandle->data = static_cast<void*>(this);\n\n\terr = uv_pipe_init(DepLibUV::GetLoop(), this->uvHandle, 0);\n\n\tif (err != 0)\n\t{\n\t\tdelete this->uvHandle;\n\t\tthis->uvHandle = nullptr;\n\n\t\tMS_THROW_ERROR_STD(\"uv_pipe_init() failed: %s\", uv_strerror(err));\n\t}\n\n\terr = uv_pipe_open(this->uvHandle, fd);\n\n\tif (err != 0)\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onClosePipe));\n\n\t\tMS_THROW_ERROR_STD(\"uv_pipe_open() failed: %s\", uv_strerror(err));\n\t}\n\n\tif (this->role == UnixStreamSocketHandle::Role::CONSUMER)\n\t{\n\t\t// Start reading.\n\t\terr = uv_read_start(\n\t\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t\t  static_cast<uv_alloc_cb>(onAlloc),\n\t\t  static_cast<uv_read_cb>(onRead));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onClosePipe));\n\n\t\t\tMS_THROW_ERROR_STD(\"uv_read_start() failed: %s\", uv_strerror(err));\n\t\t}\n\t}\n\n\t// NOTE: Don't allocate the buffer here. Instead wait for the first uv_alloc_cb().\n}\n\nUnixStreamSocketHandle::~UnixStreamSocketHandle()\n{\n\tMS_TRACE_STD();\n\n\tif (!this->closed)\n\t{\n\t\tClose();\n\t}\n\n\tdelete[] this->buffer;\n}\n\n// NOTE: In UnixStreamSocketHandle we need a poublic Close() method and cannot\n// just rely on the destructor plus a private InternalClose() method.\nvoid UnixStreamSocketHandle::Close()\n{\n\tMS_TRACE_STD();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tint err;\n\n\tthis->closed = true;\n\n\t// Tell the UV handle that the UnixStreamSocketHandle has been closed.\n\tthis->uvHandle->data = nullptr;\n\n\tif (this->role == UnixStreamSocketHandle::Role::CONSUMER)\n\t{\n\t\t// Don't read more.\n\t\terr = uv_read_stop(reinterpret_cast<uv_stream_t*>(this->uvHandle));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tMS_ABORT(\"uv_read_stop() failed: %s\", uv_strerror(err));\n\t\t\t}\n\t\t\tcatch (const std::exception& e)\n\t\t\t{\n\t\t\t\tMS_ERROR(\"%s\", e.what());\n\t\t\t}\n\t\t}\n\t}\n\n\t// If there is no error and the peer didn't close its pipe side then close gracefully.\n\tif (this->role == UnixStreamSocketHandle::Role::PRODUCER && !this->hasError && !this->isClosedByPeer)\n\t{\n\t\t// Use uv_shutdown() so pending data to be written will be sent to the peer before closing.\n\t\tauto* req = new uv_shutdown_t;\n\t\treq->data = static_cast<void*>(this);\n\t\terr       = uv_shutdown(\n\t\t  req, reinterpret_cast<uv_stream_t*>(this->uvHandle), static_cast<uv_shutdown_cb>(onShutdown));\n\n\t\tif (err != 0)\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tMS_ABORT(\"uv_shutdown() failed: %s\", uv_strerror(err));\n\t\t\t}\n\t\t\tcatch (const std::exception& e)\n\t\t\t{\n\t\t\t\tMS_ERROR(\"%s\", e.what());\n\t\t\t}\n\t\t}\n\t}\n\t// Otherwise directly close the socket.\n\telse\n\t{\n\t\tuv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(onClosePipe));\n\t}\n}\n\nvoid UnixStreamSocketHandle::Write(const uint8_t* data, size_t len)\n{\n\tMS_TRACE_STD();\n\n\tif (this->closed)\n\t{\n\t\treturn;\n\t}\n\n\tif (len == 0)\n\t{\n\t\treturn;\n\t}\n\n\t// First try uv_try_write(). In case it can not directly send all the given data\n\t// then build a uv_req_t and use uv_write().\n\n\tuv_buf_t buffer = uv_buf_init(reinterpret_cast<char*>(const_cast<uint8_t*>(data)), len);\n\tint written     = uv_try_write(reinterpret_cast<uv_stream_t*>(this->uvHandle), &buffer, 1);\n\n\t// All the data was written. Done.\n\tif (written == static_cast<int>(len))\n\t{\n\t\treturn;\n\t}\n\t// Cannot write any data at first time. Use uv_write().\n\telse if (written == UV_EAGAIN || written == UV_ENOSYS)\n\t{\n\t\t// Set written to 0 so pendingLen can be properly calculated.\n\t\twritten = 0;\n\t}\n\t// Any other error.\n\telse if (written < 0)\n\t{\n\t\tMS_ERROR_STD(\"uv_try_write() failed, trying uv_write(): %s\", uv_strerror(written));\n\n\t\t// Set written to 0 so pendingLen can be properly calculated.\n\t\twritten = 0;\n\t}\n\n\tconst size_t pendingLen = len - written;\n\tauto* writeData         = new UvWriteData(pendingLen);\n\n\twriteData->req.data = static_cast<void*>(writeData);\n\tstd::memcpy(writeData->store, data + written, pendingLen);\n\n\tbuffer = uv_buf_init(reinterpret_cast<char*>(writeData->store), pendingLen);\n\n\tconst int err = uv_write(\n\t  &writeData->req,\n\t  reinterpret_cast<uv_stream_t*>(this->uvHandle),\n\t  &buffer,\n\t  1,\n\t  static_cast<uv_write_cb>(onWrite));\n\n\tif (err != 0)\n\t{\n\t\tMS_ERROR_STD(\"uv_write() failed: %s\", uv_strerror(err));\n\n\t\t// Delete the UvSendData struct.\n\t\tdelete writeData;\n\t}\n}\n\nuint32_t UnixStreamSocketHandle::GetSendBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR_STD(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid UnixStreamSocketHandle::SetSendBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR_STD(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR_STD(\"uv_send_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\nuint32_t UnixStreamSocketHandle::GetRecvBufferSize() const\n{\n\tMS_TRACE();\n\n\tint size{ 0 };\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(size));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR_STD(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n\n\treturn static_cast<uint32_t>(size);\n}\n\nvoid UnixStreamSocketHandle::SetRecvBufferSize(uint32_t size)\n{\n\tMS_TRACE();\n\n\tauto sizeInt = static_cast<int>(size);\n\n\tif (sizeInt <= 0)\n\t{\n\t\tMS_THROW_TYPE_ERROR_STD(\"invalid size: %d\", sizeInt);\n\t}\n\n\tconst int err =\n\t  uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(this->uvHandle), std::addressof(sizeInt));\n\n\tif (err)\n\t{\n\t\tMS_THROW_ERROR_STD(\"uv_recv_buffer_size() failed: %s\", uv_strerror(err));\n\t}\n}\n\ninline void UnixStreamSocketHandle::OnUvReadAlloc(size_t /*suggestedSize*/, uv_buf_t* buf)\n{\n\tMS_TRACE_STD();\n\n\t// If this is the first call to onUvReadAlloc() then allocate the receiving buffer now.\n\tif (!this->buffer)\n\t{\n\t\tthis->buffer = new uint8_t[this->bufferSize];\n\t}\n\n\t// Tell UV to write after the last data byte in the buffer.\n\tbuf->base = reinterpret_cast<char*>(this->buffer + this->bufferDataLen);\n\n\t// Give UV all the remaining space in the buffer.\n\tif (this->bufferSize > this->bufferDataLen)\n\t{\n\t\tbuf->len = this->bufferSize - this->bufferDataLen;\n\t}\n\telse\n\t{\n\t\tbuf->len = 0;\n\n\t\tMS_ERROR_STD(\"no available space in the buffer\");\n\t}\n}\n\ninline void UnixStreamSocketHandle::OnUvRead(ssize_t nread, const uv_buf_t* /*buf*/)\n{\n\tMS_TRACE_STD();\n\n\tif (nread == 0)\n\t{\n\t\treturn;\n\t}\n\n\t// Data received.\n\tif (nread > 0)\n\t{\n\t\t// Update the buffer data length.\n\t\tthis->bufferDataLen += static_cast<size_t>(nread);\n\n\t\t// Notify the subclass.\n\t\tUserOnUnixStreamRead();\n\t}\n\t// Peer disconnected.\n\telse if (nread == UV_EOF || nread == UV_ECONNRESET)\n\t{\n\t\tthis->isClosedByPeer = true;\n\n\t\t// Close local side of the pipe.\n\t\tClose();\n\n\t\t// Notify the subclass.\n\t\tUserOnUnixStreamSocketClosed();\n\t}\n\t// Some error.\n\telse\n\t{\n\t\tMS_ERROR_STD(\"read error, closing the pipe: %s\", uv_strerror(nread));\n\n\t\tthis->hasError = true;\n\n\t\t// Close the socket.\n\t\tClose();\n\n\t\t// Notify the subclass.\n\t\tUserOnUnixStreamSocketClosed();\n\t}\n}\n\ninline void UnixStreamSocketHandle::OnUvWriteError(int error)\n{\n\tMS_TRACE_STD();\n\n\tif (error != UV_EPIPE && error != UV_ENOTCONN)\n\t{\n\t\tthis->hasError = true;\n\t}\n\n\tMS_ERROR_STD(\"write error, closing the pipe: %s\", uv_strerror(error));\n\n\tClose();\n\n\t// Notify the subclass.\n\tUserOnUnixStreamSocketClosed();\n}\n"
  },
  {
    "path": "worker/src/lib.cpp",
    "content": "#define MS_CLASS \"mediasoup-worker\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"lib.hpp\"\n#include \"common.hpp\"\n#include \"DepLibSRTP.hpp\"\n#ifdef MS_LIBURING_SUPPORTED\n#include \"DepLibUring.hpp\"\n#endif\n#include \"DepLibUV.hpp\"\n#include \"DepLibWebRTC.hpp\"\n#include \"DepOpenSSL.hpp\"\n// TODO: Remove once we only use built-in SCTP stack.\n#include \"DepUsrSCTP.hpp\"\n#include \"Logger.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Settings.hpp\"\n#include \"Shared.hpp\"\n#include \"Utils.hpp\"\n#include \"Worker.hpp\"\n#include \"Channel/ChannelMessageRegistrator.hpp\"\n#include \"Channel/ChannelNotifier.hpp\"\n#include \"Channel/ChannelSocket.hpp\"\n#include \"RTC/DtlsTransport.hpp\"\n#include \"RTC/SrtpSession.hpp\"\n#include <absl/container/flat_hash_map.h>\n#include <csignal> // sigaction()\n#include <string>\n\nstatic void ignoreSignals();\n\n/**\n * Initializes everything and creates an instance of Worker class.\n *\n * @return\n * - 0 if the Worker terminated properly.\n * - 42 if given settings are wrong/invalid.\n * - 40 if an uncaught MediasoupError happens (only in non executable mode).\n * - 134 when any other uncaught C++ exception happens (only in non executable\n *   mode).\n */\n// NOLINTNEXTLINE(readability-identifier-naming)\nextern \"C\" int mediasoup_worker_run(\n  int argc,\n  char* argv[],\n  const char* version,\n  int consumerChannelFd,\n  int producerChannelFd,\n  ChannelReadFn channelReadFn,\n  ChannelReadCtx channelReadCtx,\n  ChannelWriteFn channelWriteFn,\n  ChannelWriteCtx channelWriteCtx)\n{\n\t// Initialize libuv stuff (we need it for the Channel).\n\tDepLibUV::ClassInit();\n\n\t// Channel socket. If Worker instance runs properly, this socket is closed by\n\t// it in its destructor. Otherwise it's closed here by also letting libuv\n\t// deallocate its UV handles.\n\tstd::unique_ptr<Channel::ChannelSocket> channel{ nullptr };\n\n#ifndef MS_EXECUTABLE\n\ttry\n\t{\n#endif\n\t\tif (channelReadFn)\n\t\t{\n\t\t\tchannel.reset(\n\t\t\t  new Channel::ChannelSocket(channelReadFn, channelReadCtx, channelWriteFn, channelWriteCtx));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tchannel.reset(new Channel::ChannelSocket(consumerChannelFd, producerChannelFd));\n\t\t}\n#ifndef MS_EXECUTABLE\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tMS_ERROR_STD(\"error creating the Channel: %s\", error.what());\n\n\t\tDepLibUV::RunLoop();\n\t\tDepLibUV::ClassDestroy();\n\n\t\t// 40 is a custom exit code to notify \"unknown error\" caller.\n\t\treturn 40;\n\t}\n#endif\n\n\t// Create a Shared singleton.\n\tstd::unique_ptr<Shared> shared{ new Shared(\n\t\t/*channelMessageRegistrator*/ new Channel::ChannelMessageRegistrator(),\n\t\t/*channelNotifier*/ new Channel::ChannelNotifier(channel.get())) };\n\n\t// Initialize the Logger.\n\tLogger::ClassInit(channel.get());\n\n\ttry\n\t{\n\t\tSettings::SetConfiguration(argc, argv);\n\t}\n\tcatch (const MediaSoupTypeError& error)\n\t{\n\t\tMS_ERROR_STD(\"settings error: %s\", error.what());\n\n\t\tchannel->Close();\n\t\tDepLibUV::RunLoop();\n\t\tDepLibUV::ClassDestroy();\n\n\t\t// 42 is a custom exit code to notify \"settings error\" caller.\n\t\treturn 42;\n\t}\n#ifndef MS_EXECUTABLE\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tMS_ERROR_STD(\"unexpected settings error: %s\", error.what());\n\n\t\tchannel->Close();\n\t\tDepLibUV::RunLoop();\n\t\tDepLibUV::ClassDestroy();\n\n\t\t// 40 is a custom exit code to notify \"unknown error\" caller.\n\t\treturn 40;\n\t}\n\tcatch (const std::runtime_error& error)\n\t{\n\t\t// 134 is the exit code for SIGABRT.\n\t\treturn 134;\n\t}\n#endif\n\n\tMS_DEBUG_TAG(info, \"starting mediasoup-worker process [version:%s]\", version);\n\n#ifdef MS_LITTLE_ENDIAN\n\tMS_DEBUG_TAG(info, \"little-endian CPU detected\");\n#elif defined(MS_BIG_ENDIAN)\n\tMS_DEBUG_TAG(info, \"big-endian CPU detected\");\n#else\n\tMS_WARN_TAG(info, \"cannot determine whether little-endian or big-endian\");\n#endif\n\n#if defined(INTPTR_MAX) && defined(INT32_MAX) && (INTPTR_MAX == INT32_MAX)\n\tMS_DEBUG_TAG(info, \"32 bits architecture detected\");\n#elif defined(INTPTR_MAX) && defined(INT64_MAX) && (INTPTR_MAX == INT64_MAX)\n\tMS_DEBUG_TAG(info, \"64 bits architecture detected\");\n#else\n\tMS_WARN_TAG(info, \"cannot determine 32 or 64 bits architecture\");\n#endif\n\n\tSettings::PrintConfiguration();\n\tDepLibUV::PrintVersion();\n\n#ifndef MS_EXECUTABLE\n\ttry\n\t{\n#endif\n\t\t// Initialize static stuff.\n\t\tDepOpenSSL::ClassInit();\n\t\tDepLibSRTP::ClassInit();\n\t\t// TODO: Remove once we only use built-in SCTP stack.\n\t\tif (!Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tDepUsrSCTP::ClassInit();\n\t\t}\n#ifdef MS_LIBURING_SUPPORTED\n\t\tDepLibUring::ClassInit();\n#endif\n\t\tDepLibWebRTC::ClassInit();\n\t\tUtils::Crypto::ClassInit();\n\t\tRTC::DtlsTransport::ClassInit();\n\t\tRTC::SrtpSession::ClassInit();\n\n\t\t// Ignore some signals.\n\t\tignoreSignals();\n\n\t\tMS_DEBUG_TAG(info, \"creating Worker instance\");\n\n\t\t// Run the Worker.\n\t\tconst Worker worker(channel.get(), shared.get());\n\n\t\tMS_DEBUG_TAG(info, \"Worker instance terminated\");\n\n\t\t// Free static stuff.\n\t\tDepLibSRTP::ClassDestroy();\n\t\tUtils::Crypto::ClassDestroy();\n\t\tDepLibWebRTC::ClassDestroy();\n#ifdef MS_LIBURING_SUPPORTED\n\t\tDepLibUring::ClassDestroy();\n#endif\n\t\tRTC::DtlsTransport::ClassDestroy();\n\t\t// TODO: Remove once we only use built-in SCTP stack.\n\t\tif (!Settings::configuration.useBuiltInSctpStack)\n\t\t{\n\t\t\tDepUsrSCTP::ClassDestroy();\n\t\t}\n\t\tDepLibUV::ClassDestroy();\n\n\t\treturn 0;\n#ifndef MS_EXECUTABLE\n\t}\n\tcatch (const MediaSoupError& error)\n\t{\n\t\tMS_ERROR_STD(\"failure exit: %s\", error.what());\n\n\t\t// 40 is a custom exit code to notify \"unknown error\" caller.\n\t\treturn 40;\n\t}\n\tcatch (const std::runtime_error& error)\n\t{\n\t\t// 134 is the exit code for SIGABRT.\n\t\treturn 134;\n\t}\n#endif\n}\n\nstatic void ignoreSignals()\n{\n#ifdef MS_EXECUTABLE\n#ifndef _WIN32\n\tMS_TRACE();\n\n\tint err;\n\tstruct sigaction act{}; // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\t// clang-format off\n\tabsl::flat_hash_map<std::string, int> const ignoredSignals =\n\t{\n\t\t{ \"PIPE\", SIGPIPE },\n\t\t{ \"HUP\",  SIGHUP  },\n\t\t{ \"ALRM\", SIGALRM },\n\t\t{ \"USR1\", SIGUSR1 },\n\t\t{ \"USR2\", SIGUSR2 }\n\t};\n\t// clang-format on\n\n\tact.sa_handler = SIG_IGN; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast)\n\tact.sa_flags   = 0;\n\terr            = sigfillset(&act.sa_mask);\n\n\tif (err != 0)\n\t{\n\t\tMS_THROW_ERROR(\"sigfillset() failed: %s\", std::strerror(errno));\n\t}\n\n\tfor (const auto& kv : ignoredSignals)\n\t{\n\t\tconst auto& sigName = kv.first;\n\t\tconst int sigId     = kv.second;\n\n\t\terr = sigaction(sigId, &act, nullptr);\n\n\t\tif (err != 0)\n\t\t{\n\t\t\tMS_THROW_ERROR(\"sigaction() failed for signal %s: %s\", sigName.c_str(), std::strerror(errno));\n\t\t}\n\t}\n#endif\n#endif\n}\n"
  },
  {
    "path": "worker/src/lib.rs",
    "content": "use std::os::raw::{c_char, c_int, c_void};\n\npub use planus_codegen::*;\n\nmod planus_codegen {\n    #![allow(clippy::all)]\n    include!(concat!(env!(\"OUT_DIR\"), \"/fbs.rs\"));\n}\n\n#[repr(transparent)]\n#[derive(Copy, Clone)]\npub struct UvAsyncT(pub *const c_void);\n\nunsafe impl Send for UvAsyncT {}\n\n#[repr(transparent)]\npub struct ChannelReadCtx(pub *const c_void);\npub type ChannelReadFreeFn = Option<\n    unsafe extern \"C\" fn(\n        /* message: */ *mut u8,\n        /* message_len: */ u32,\n        /* message_ctx: */ usize,\n    ),\n>;\npub type ChannelReadFn = unsafe extern \"C\" fn(\n    /* message: */ *mut *mut u8,\n    /* message_len: */ *mut u32,\n    /* message_ctx: */ *mut usize,\n    // This is `uv_async_t` handle that can be called later with `uv_async_send()` when there is\n    // more data to read\n    /* handle */\n    UvAsyncT,\n    /* ctx: */ ChannelReadCtx,\n) -> ChannelReadFreeFn;\n\nunsafe impl Send for ChannelReadCtx {}\n\n#[repr(transparent)]\npub struct ChannelWriteCtx(pub *const c_void);\npub type ChannelWriteFn = unsafe extern \"C\" fn(\n    /* message: */ *const u8,\n    /* message_len: */ u32,\n    /* ctx: */ ChannelWriteCtx,\n);\n\nunsafe impl Send for ChannelWriteCtx {}\n\n#[link(name = \"mediasoup-worker\", kind = \"static\")]\nextern \"C\" {\n    /// Returns `0` on success, or an error code `< 0` on failure\n    pub fn uv_async_send(handle: UvAsyncT) -> c_int;\n\n    pub fn mediasoup_worker_run(\n        argc: c_int,\n        argv: *const *const c_char,\n        version: *const c_char,\n        consumer_channel_fd: c_int,\n        producer_channel_fd: c_int,\n        channel_read_fn: ChannelReadFn,\n        channel_read_ctx: ChannelReadCtx,\n        channel_write_fn: ChannelWriteFn,\n        channel_write_ctx: ChannelWriteCtx,\n    ) -> c_int;\n}\n"
  },
  {
    "path": "worker/src/main.cpp",
    "content": "#define MS_CLASS \"mediasoup-worker\"\n// #define MS_LOG_DEV_LEVEL 3\n\n#include \"common.hpp\"\n#include \"Logger.hpp\"\n#include \"lib.hpp\"\n#include <cstdlib> // std::_Exit()\n#include <string>\n\nstatic constexpr int ConsumerChannelFd{ 3 };\nstatic constexpr int ProducerChannelFd{ 4 };\n\nint main(int argc, char* argv[])\n{\n\tconst char* envVersion = std::getenv(\"MEDIASOUP_VERSION\");\n\n\t// Ensure we are called by our Node library.\n\tif (!envVersion)\n\t{\n\t\tMS_ERROR_STD(\"you don't seem to be my real father!\");\n\n\t\t// 41 is a custom exit code to notify about \"missing MEDIASOUP_VERSION\" env.\n\t\tstd::_Exit(41);\n\t}\n\n\tconst std::string version{ envVersion };\n\n\tconst auto statusCode = mediasoup_worker_run(\n\t  /*argc*/ argc,\n\t  /*argv*/ argv,\n\t  /*version*/ version.c_str(),\n\t  /*consumerChannelFd*/ ConsumerChannelFd,\n\t  /*producerChannelFd*/ ProducerChannelFd,\n\t  /*channelReadFn*/ nullptr,\n\t  /*channelReadCtx*/ nullptr,\n\t  /*channelWriteFn*/ nullptr,\n\t  /*channelWriteCtx*/ nullptr);\n\n\tstd::_Exit(statusCode);\n}\n"
  },
  {
    "path": "worker/subprojects/.clang-tidy",
    "content": "# Bad workaround to disable clang-tidy checks in worker/subprojects folder.\n# Ideally we should use a modern version so worker/.clang-tidy-ignore would be\n# honored. Here we should also be able to do Checks: '-*' to disable all rules\n# but if we do it it fails with \"Error: no checks enable\".\n---\nChecks: 'modernize-*'\n...\n"
  },
  {
    "path": "worker/subprojects/abseil-cpp.wrap",
    "content": "[wrap-file]\ndirectory = abseil-cpp-20240722.0\nsource_url = https://github.com/abseil/abseil-cpp/releases/download/20240722.0/abseil-cpp-20240722.0.tar.gz\nsource_filename = abseil-cpp-20240722.0.tar.gz\nsource_hash = f50e5ac311a81382da7fa75b97310e4b9006474f9560ac46f54a9967f07d4ae3\npatch_filename = abseil-cpp_20240722.0-4_patch.zip\npatch_url = https://wrapdb.mesonbuild.com/v2/abseil-cpp_20240722.0-4/get_patch\npatch_hash = e39d535c4707f6e342e84e3e616449e1cc98cb7fadda92a09820b0ae67c6d0d6\nsource_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/abseil-cpp_20240722.0-4/abseil-cpp-20240722.0.tar.gz\nwrapdb_version = 20240722.0-4\n\n[provide]\nabsl_base = absl_base_dep\nabsl_container = absl_container_dep\nabsl_debugging = absl_debugging_dep\nabsl_log = absl_log_dep\nabsl_flags = absl_flags_dep\nabsl_hash = absl_hash_dep\nabsl_crc = absl_crc_dep\nabsl_numeric = absl_numeric_dep\nabsl_profiling = absl_profiling_dep\nabsl_random = absl_random_dep\nabsl_status = absl_status_dep\nabsl_strings = absl_strings_dep\nabsl_synchronization = absl_synchronization_dep\nabsl_time = absl_time_dep\nabsl_types = absl_types_dep\nabsl_algorithm_container = absl_base_dep\nabsl_any_invocable = absl_base_dep\nabsl_bad_any_cast_impl = absl_types_dep\nabsl_bad_optional_access = absl_types_dep\nabsl_bad_variant_access = absl_types_dep\nabsl_bind_front = absl_base_dep\nabsl_city = absl_hash_dep\nabsl_civil_time = absl_time_dep\nabsl_cleanup = absl_base_dep\nabsl_cord = absl_strings_dep\nabsl_cord_internal = absl_strings_dep\nabsl_cordz_functions = absl_strings_dep\nabsl_cordz_handle = absl_strings_dep\nabsl_cordz_info = absl_strings_dep\nabsl_cordz_sample_token = absl_strings_dep\nabsl_core_headers = absl_base_dep\nabsl_crc32c = absl_crc_dep\nabsl_debugging_internal = absl_debugging_dep\nabsl_demangle_internal = absl_debugging_dep\nabsl_die_if_null = absl_log_dep\nabsl_examine_stack = absl_debugging_dep\nabsl_exponential_biased = absl_profiling_dep\nabsl_failure_signal_handler = absl_debugging_dep\nabsl_flags_commandlineflag = absl_flags_dep\nabsl_flags_commandlineflag_internal = absl_flags_dep\nabsl_flags_config = absl_flags_dep\nabsl_flags_internal = absl_flags_dep\nabsl_flags_marshalling = absl_flags_dep\nabsl_flags_parse = absl_flags_dep\nabsl_flags_private_handle_accessor = absl_flags_dep\nabsl_flags_program_name = absl_flags_dep\nabsl_flags_reflection = absl_flags_dep\nabsl_flags_usage = absl_flags_dep\nabsl_flags_usage_internal = absl_flags_dep\nabsl_flat_hash_map = absl_container_dep\nabsl_flat_hash_set = absl_container_dep\nabsl_function_ref = absl_base_dep\nabsl_graphcycles_internal = absl_synchronization_dep\nabsl_hashtablez_sampler = absl_container_dep\nabsl_inlined_vector = absl_container_dep\nabsl_int128 = absl_numeric_dep\nabsl_leak_check = absl_debugging_dep\nabsl_log_initialize = absl_log_dep\nabsl_log_internal_check_op = absl_log_dep\nabsl_log_internal_message = absl_log_dep\nabsl_log_severity = absl_base_dep\nabsl_low_level_hash = absl_hash_dep\nabsl_memory = absl_base_dep\nabsl_optional = absl_types_dep\nabsl_periodic_sampler = absl_profiling_dep\nabsl_random_bit_gen_ref = absl_random_dep\nabsl_random_distributions = absl_random_dep\nabsl_random_internal_distribution_test_util = absl_random_dep\nabsl_random_internal_platform = absl_random_dep\nabsl_random_internal_pool_urbg = absl_random_dep\nabsl_random_internal_randen = absl_random_dep\nabsl_random_internal_randen_hwaes = absl_random_dep\nabsl_random_internal_randen_hwaes_impl = absl_random_dep\nabsl_random_internal_randen_slow = absl_random_dep\nabsl_random_internal_seed_material = absl_random_dep\nabsl_random_random = absl_random_dep\nabsl_random_seed_gen_exception = absl_random_dep\nabsl_random_seed_sequences = absl_random_dep\nabsl_raw_hash_set = absl_container_dep\nabsl_raw_logging_internal = absl_base_dep\nabsl_scoped_set_env = absl_base_dep\nabsl_span = absl_types_dep\nabsl_spinlock_wait = absl_base_dep\nabsl_stacktrace = absl_debugging_dep\nabsl_statusor = absl_status_dep\nabsl_str_format = absl_strings_dep\nabsl_str_format_internal = absl_strings_dep\nabsl_strerror = absl_base_dep\nabsl_string_view = absl_strings_dep\nabsl_strings_internal = absl_strings_dep\nabsl_symbolize = absl_debugging_dep\nabsl_throw_delegate = absl_base_dep\nabsl_time_zone = absl_time_dep\nabsl_type_traits = absl_base_dep\nabsl_utility = absl_base_dep\nabsl_variant = absl_types_dep\n"
  },
  {
    "path": "worker/subprojects/catch2.wrap",
    "content": "[wrap-file]\ndirectory = Catch2-3.14.0\nsource_url = https://github.com/catchorg/Catch2/archive/v3.14.0.tar.gz\nsource_filename = Catch2-3.14.0.tar.gz\nsource_hash = ba2a939efead3c833c499cf487e185762f419a71d30158cd1b43c6079c586490\nsource_fallback_url = https://wrapdb.mesonbuild.com/v2/catch2_3.14.0-1/get_source/Catch2-3.14.0.tar.gz\nwrapdb_version = 3.14.0-1\n\n[provide]\ncatch2 = catch2_dep\ncatch2-with-main = catch2_with_main_dep\n"
  },
  {
    "path": "worker/subprojects/flatbuffers.wrap",
    "content": "[wrap-file]\ndirectory = flatbuffers-24.3.25\nsource_url = https://github.com/google/flatbuffers/archive/v24.3.25.tar.gz\nsource_filename = flatbuffers-24.3.25.tar.gz\nsource_hash = 4157c5cacdb59737c5d627e47ac26b140e9ee28b1102f812b36068aab728c1ed\npatch_filename = flatbuffers_24.3.25-1_patch.zip\npatch_url = https://wrapdb.mesonbuild.com/v2/flatbuffers_24.3.25-1/get_patch\npatch_hash = 9be75a2053a19e5a59175f2fbbf6e9d40f4243d2786f2661a131d3502ddfa457\nsource_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/flatbuffers_24.3.25-1/flatbuffers-24.3.25.tar.gz\nwrapdb_version = 24.3.25-1\n\n[provide]\nflatbuffers = flatbuffers_dep\nprogram_names = flatc, flathash\n"
  },
  {
    "path": "worker/subprojects/libsrtp3.wrap",
    "content": "[wrap-file]\ndirectory = libsrtp-3.0.0-beta\nsource_url = https://github.com/versatica/libsrtp/archive/v3.0.0-beta.zip\nsource_filename = libsrtp-3.0.0-beta.zip\nsource_hash = b0cb21aaf27120b1a242f3d9b07c145b4a6c7a02a25da0824539fd95611f690c\n\n[provide]\nlibsrtp3 = libsrtp3_dep\n"
  },
  {
    "path": "worker/subprojects/liburing.wrap",
    "content": "[wrap-file]\ndirectory = liburing-liburing-2.14\nsource_url = https://github.com/axboe/liburing/archive/refs/tags/liburing-2.14.tar.gz\nsource_filename = liburing-2.14.tar.gz\nsource_hash = 5f80964108981c6ad979c735f0b4877d5f49914c2a062f8e88282f26bf61de0c\nsource_fallback_url = https://wrapdb.mesonbuild.com/v2/liburing_2.14-1/get_source/liburing-2.14.tar.gz\npatch_filename = liburing_2.14-1_patch.zip\npatch_url = https://wrapdb.mesonbuild.com/v2/liburing_2.14-1/get_patch\npatch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/liburing_2.14-1/liburing_2.14-1_patch.zip\npatch_hash = 4d0ba8f507c25c4fb9a31507a5c06d2a6c253822828084a0101e2bea27dba62a\nwrapdb_version = 2.14-1\n\n[provide]\ndependency_names = liburing\n"
  },
  {
    "path": "worker/subprojects/libuv.wrap",
    "content": "[wrap-file]\ndirectory = libuv-v1.51.0\nsource_url = https://dist.libuv.org/dist/v1.51.0/libuv-v1.51.0.tar.gz\nsource_filename = libuv-v1.51.0.tar.gz\nsource_hash = 5f0557b90b1106de71951a3c3931de5e0430d78da1d9a10287ebc7a3f78ef8eb\npatch_filename = libuv_1.51.0-1_patch.zip\npatch_url = https://wrapdb.mesonbuild.com/v2/libuv_1.51.0-1/get_patch\npatch_hash = 0fb123dee5e74621a767a8f2a29dde7219c65a01a5fe63e3c8ffeed675a2d820\nsource_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libuv_1.51.0-1/libuv-v1.51.0.tar.gz\nwrapdb_version = 1.51.0-1\n\n[provide]\nlibuv = libuv_dep\n"
  },
  {
    "path": "worker/subprojects/openssl.wrap",
    "content": "[wrap-file]\ndirectory = openssl-3.0.8\nsource_url = https://www.openssl.org/source/openssl-3.0.8.tar.gz\nsource_filename = openssl-3.0.8.tar.gz\nsource_hash = 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e\npatch_filename = openssl_3.0.8-3_patch.zip\npatch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.8-3/get_patch\npatch_hash = 300da189e106942347d61a4a4295aa2edbcf06184f8d13b4cee0bed9fb936963\nsource_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.8-3/openssl-3.0.8.tar.gz\nwrapdb_version = 3.0.8-3\n\n[provide]\nlibcrypto = libcrypto_dep\nlibssl = libssl_dep\nopenssl = openssl_dep\n"
  },
  {
    "path": "worker/subprojects/usrsctp.wrap",
    "content": "[wrap-file]\ndirectory = usrsctp-fd070e05a7474f38c7fecdf4d4b6005d2547ee00\nsource_url = https://github.com/sctplab/usrsctp/archive/fd070e05a7474f38c7fecdf4d4b6005d2547ee00.zip\nsource_filename = fd070e05a7474f38c7fecdf4d4b6005d2547ee00.zip\nsource_hash = a0f9255a88fef2e375b590f2823ddbc70ab38142b9931cd3f1c7c0b700bf7043\n\n[provide]\nusrsctp = usrsctp_dep\n"
  },
  {
    "path": "worker/subprojects/wingetopt.wrap",
    "content": "[wrap-file]\ndirectory = wingetopt-1.00\nsource_url = https://github.com/alex85k/wingetopt/archive/v1.00.zip\nsource_filename = wingetopt-1.00.zip\nsource_hash = 4454ca03a59702a4ca4d1488ca8fa6168b0c8d77dc739a6fe2825c3dd8609d87\n"
  },
  {
    "path": "worker/tasks.py",
    "content": "# Ignore these pylint warnings, conventions and refactoring messages:\n# - W0301: Unnecessary semicolon (unnecessary-semicolon)\n# - W0622: Redefining built-in 'format' (redefined-builtin)\n# - W0702: No exception type(s) specified (bare-except)\n# - C0114: Missing module docstring (missing-module-docstring)\n# - C0301: Line too long (line-too-long)\n#\n# pylint: disable=W0301,W0622,W0702,C0114,C0301\n\n#\n# This is a tasks.py file for the Python invoke package:\n# https://docs.pyinvoke.org/.\n#\n# It's a replacement of our Makefile with same tasks.\n#\n# Usage:\n#   pip install invoke\n#   invoke --list\n#\n\nimport sys;\nimport os;\nimport inspect;\nimport shutil;\nfrom contextlib import contextmanager;\n# We import this from a custom location and pylint doesn't know.\nfrom invoke import task, call; # pylint: disable=import-error\n\nMEDIASOUP_BUILDTYPE = os.getenv('MEDIASOUP_BUILDTYPE') or 'Release';\nWORKER_DIR = os.path.dirname(os.path.abspath(\n    inspect.getframeinfo(inspect.currentframe()).filename\n));\n# NOTE: MEDIASOUP_OUT_DIR is overrided by build.rs.\nMEDIASOUP_OUT_DIR = os.getenv('MEDIASOUP_OUT_DIR') or f'{WORKER_DIR}/out';\nMEDIASOUP_INSTALL_DIR = os.getenv('MEDIASOUP_INSTALL_DIR') or f'{MEDIASOUP_OUT_DIR}/{MEDIASOUP_BUILDTYPE}';\nBUILD_DIR = os.getenv('BUILD_DIR') or f'{MEDIASOUP_INSTALL_DIR}/build';\n# Custom pip folder for invoke package.\n# NOTE: We invoke `pip install` always with `--no-user` to make it not complain\n# about \"can not combine --user and --target\".\nPIP_INVOKE_DIR = f'{MEDIASOUP_OUT_DIR}/pip_invoke';\n# Custom pip folder for meson and ninja packages.\nPIP_MESON_NINJA_DIR = f'{MEDIASOUP_OUT_DIR}/pip_meson_ninja';\n# Custom pip folder for pylint package.\n# NOTE: We do this because using --target and --upgrade in `pip install` is not\n# supported and a latter invokation entirely replaces the bin folder.\nPIP_PYLINT_DIR = f'{MEDIASOUP_OUT_DIR}/pip_pylint';\n# If available (only on some *nix systems), os.sched_getaffinity(0) gets set of\n# CPUs the calling thread is restricted to. Instead, os.cpu_count() returns the\n# total number of CPUs in a system (it doesn't take into account how many of them\n# the calling thread can use).\nNUM_CORES = len(os.sched_getaffinity(0)) if hasattr(os, 'sched_getaffinity') else os.cpu_count();\nPYTHON = os.getenv('PYTHON') or sys.executable;\nMESON = os.getenv('MESON') or f'{PIP_MESON_NINJA_DIR}/bin/meson';\nMESON_VERSION = os.getenv('MESON_VERSION') or '1.9.1';\n# MESON_ARGS can be used to provide extra configuration parameters to meson,\n# such as adding defines or changing optimization options. For instance, use\n# `MESON_ARGS=\"-Dms_log_trace=true -Dms_log_file_line=true\" npm i` to compile\n# worker with tracing and enabled.\n# NOTE: On Windows make sure to add `--vsenv` or have MSVS environment already\n# active if you override this parameter.\nMESON_ARGS = os.getenv('MESON_ARGS') if os.getenv('MESON_ARGS') else '--vsenv' if os.name == 'nt' else '';\n# Let's use a specific version of ninja to avoid buggy version 1.11.1:\n# https://mediasoup.discourse.group/t/partly-solved-could-not-detect-ninja-v1-8-2-or-newer/\n# https://github.com/ninja-build/ninja/issues/2211\nNINJA_VERSION = os.getenv('NINJA_VERSION') or '1.10.2.4';\nPYLINT_VERSION = os.getenv('PYLINT_VERSION') or '3.0.2';\nNPM = os.getenv('NPM') or 'npm';\nDOCKER = os.getenv('DOCKER') or 'docker';\n# pty=True in ctx.run() is not available on Windows so if stdout is not a TTY\n# let's assume PTY is not supported. Related issue in invoke project:\n# https://github.com/pyinvoke/invoke/issues/561\nPTY_SUPPORTED =  os.name != 'nt' and sys.stdout.isatty();\n# Use sh (widely supported, more than bash) if not in Windows.\nSHELL = '/bin/sh' if not os.name == 'nt' else None;\n\n# Disable `*.pyc` files creation.\nos.environ['PYTHONDONTWRITEBYTECODE'] = 'true';\n\n# Instruct meson where to look for ninja binary.\nif os.name == 'nt':\n    # Windows is, of course, special.\n    os.environ['NINJA'] = f'{PIP_MESON_NINJA_DIR}/bin/ninja.exe';\nelse:\n    os.environ['NINJA'] = f'{PIP_MESON_NINJA_DIR}/bin/ninja';\n\n# Instruct Python where to look for modules it needs, such that meson actually\n# runs from installed location.\n# NOTE: On Windows we must use ; instead of : to separate paths.\nPYTHONPATH = os.getenv('PYTHONPATH') or '';\nif os.name == 'nt':\n    os.environ['PYTHONPATH'] = f'{PIP_INVOKE_DIR};{PIP_MESON_NINJA_DIR};{PIP_PYLINT_DIR};{PYTHONPATH}';\nelse:\n    os.environ['PYTHONPATH'] = f'{PIP_INVOKE_DIR}:{PIP_MESON_NINJA_DIR}:{PIP_PYLINT_DIR}:{PYTHONPATH}';\n\n\n@contextmanager\ndef cd_worker():\n    \"\"\"\n    Context manager to change to worker/ folder the safe way\n    \"\"\"\n    original_dir = os.getcwd()\n    os.chdir(WORKER_DIR)\n    try:\n        yield\n    finally:\n        os.chdir(original_dir)\n\n@task\ndef meson_ninja(ctx):\n    \"\"\"\n    Install meson and ninja (also update Python pip and setuptools packages)\n    \"\"\"\n    if os.path.isfile(MESON):\n        return;\n\n    # Updated pip and setuptools are needed for meson.\n    # `--system` is not present everywhere and is only needed as workaround for\n    # Debian-specific issue (copied from https://github.com/gluster/gstatus/pull/33),\n    # fallback to command without `--system` if the first one fails.\n    try:\n        ctx.run(\n            f'\"{PYTHON}\" -m pip install --system --upgrade --no-user --target \"{PIP_MESON_NINJA_DIR}\" pip setuptools',\n            echo=True,\n            hide=True,\n            shell=SHELL\n        );\n    except:\n        ctx.run(\n            f'\"{PYTHON}\" -m pip install --upgrade --no-user --target \"{PIP_MESON_NINJA_DIR}\" pip setuptools',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    # Workaround for NixOS and Guix that don't work with pre-built binaries, see:\n    # https://github.com/NixOS/nixpkgs/issues/142383.\n    pip_build_binaries = '--no-binary :all:' if os.path.isfile('/etc/NIXOS') or os.path.isdir('/etc/guix') else '';\n\n    # Install meson and ninja using pip into our custom location, so we don't\n    # depend on system-wide installation.\n    ctx.run(\n        f'\"{PYTHON}\" -m pip install --upgrade --no-user --target \"{PIP_MESON_NINJA_DIR}\" {pip_build_binaries} meson=={MESON_VERSION} ninja=={NINJA_VERSION}',\n        echo=True,\n        pty=PTY_SUPPORTED,\n        shell=SHELL\n    );\n\n\n@task(pre=[meson_ninja])\ndef setup(ctx, meson_args=MESON_ARGS):\n    \"\"\"\n    Run meson setup\n    \"\"\"\n    if MEDIASOUP_BUILDTYPE == 'Release':\n        with cd_worker():\n            ctx.run(\n                f'\"{MESON}\" setup --prefix \"{MEDIASOUP_INSTALL_DIR}\" --bindir \"\" --libdir \"\" --buildtype release -Db_ndebug=true {meson_args} \"{BUILD_DIR}\"',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n    elif MEDIASOUP_BUILDTYPE == 'Debug':\n        with cd_worker():\n            ctx.run(\n                f'\"{MESON}\" setup --prefix \"{MEDIASOUP_INSTALL_DIR}\" --bindir \"\" --libdir \"\" --buildtype debug {meson_args} \"{BUILD_DIR}\"',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n    else:\n        with cd_worker():\n            ctx.run(\n                f'\"{MESON}\" setup --prefix \"{MEDIASOUP_INSTALL_DIR}\" --bindir \"\" --libdir \"\" --buildtype {MEDIASOUP_BUILDTYPE} -Db_ndebug=if-release {meson_args} \"{BUILD_DIR}\"',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n\n\n@task\ndef clean(ctx): # pylint: disable=unused-argument\n    \"\"\"\n    Clean the installation directory\n    \"\"\"\n    shutil.rmtree(MEDIASOUP_INSTALL_DIR, ignore_errors=True);\n\n\n@task\ndef clean_build(ctx): # pylint: disable=unused-argument\n    \"\"\"\n    Clean the build directory\n    \"\"\"\n    shutil.rmtree(BUILD_DIR, ignore_errors=True);\n\n\n@task\ndef clean_pip(ctx): # pylint: disable=unused-argument\n    \"\"\"\n    Clean the local pip setup\n    \"\"\"\n    shutil.rmtree(PIP_MESON_NINJA_DIR, ignore_errors=True);\n    shutil.rmtree(PIP_PYLINT_DIR, ignore_errors=True);\n\n\n@task(pre=[meson_ninja])\ndef clean_subprojects(ctx):\n    \"\"\"\n    Clean meson subprojects\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" subprojects purge --include-cache --confirm',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task\ndef clean_all(ctx):\n    \"\"\"\n    Clean meson subprojects and all installed/built artificats\n    \"\"\"\n    with cd_worker():\n        try:\n            ctx.run(\n                f'\"{MESON}\" subprojects purge --include-cache --confirm',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n        except:\n            pass;\n\n        shutil.rmtree(MEDIASOUP_OUT_DIR, ignore_errors=True);\n        shutil.rmtree('include/FBS', ignore_errors=True);\n\n\n@task(pre=[meson_ninja])\ndef update_wrap_file(ctx, subproject):\n    \"\"\"\n    Update the wrap file of a subproject\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" subprojects update --reset {subproject}',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[setup])\ndef flatc(ctx):\n    \"\"\"\n    Compile FlatBuffers FBS files\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" flatbuffers-generator',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[setup, flatc], default=True)\ndef mediasoup_worker(ctx):\n    \"\"\"\n    Compile mediasoup-worker binary\n    \"\"\"\n    if os.getenv('MEDIASOUP_WORKER_BIN'):\n        print('skipping mediasoup-worker compilation due to the existence of the MEDIASOUP_WORKER_BIN environment variable');\n        return;\n\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} mediasoup-worker',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags mediasoup-worker',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[setup, flatc])\ndef libmediasoup_worker(ctx):\n    \"\"\"\n    Compile libmediasoup-worker library\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} libmediasoup-worker',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags libmediasoup-worker',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[setup, flatc])\ndef xcode(ctx):\n    \"\"\"\n    Setup Xcode project\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" setup --buildtype {MEDIASOUP_BUILDTYPE.lower()} --backend xcode \"{MEDIASOUP_OUT_DIR}/xcode\"',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task\ndef lint(ctx):\n    \"\"\"\n    Lint source code\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{NPM}\" run lint --prefix scripts/',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    if not os.path.isdir(PIP_PYLINT_DIR):\n        # Install pylint using pip into our custom location.\n        ctx.run(\n            f'\"{PYTHON}\" -m pip install --upgrade --no-user --target=\"{PIP_PYLINT_DIR}\" pylint=={PYLINT_VERSION}',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    with cd_worker():\n        ctx.run(\n            f'\"{PYTHON}\" -m pylint tasks.py',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task\ndef format(ctx):\n    \"\"\"\n    Format source code according to lint rules\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{NPM}\" run format --prefix scripts/',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc])\ndef tidy(ctx):\n    \"\"\"\n    Performs C++ code checks according to `worker/.clang-tidy` rules\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{NPM}\" run tidy --prefix scripts/',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc])\ndef tidy_fix(ctx):\n    \"\"\"\n    Performs C++ code checks according to `worker/.clang-tidy` rules and applies\n    fixes\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{NPM}\" run tidy:fix --prefix scripts/',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc])\ndef test(ctx):\n    \"\"\"\n    Run worker tests\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} mediasoup-worker-test',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags mediasoup-worker-test',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    mediasoup_worker_test = 'mediasoup-worker-test.exe' if os.name == 'nt' else 'mediasoup-worker-test';\n    mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or '';\n\n    with cd_worker():\n        ctx.run(\n            f'\"{BUILD_DIR}/{mediasoup_worker_test}\" --invisibles --colour-mode=ansi {mediasoup_test_tags}',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true -Db_sanitize=address -Db_lundef=false'), flatc])\ndef test_asan_address(ctx):\n    \"\"\"\n    Run worker test with Address Sanitizer with '-fsanitize=address'\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} mediasoup-worker-test-asan-address',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags mediasoup-worker-test-asan-address',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or '';\n\n    with cd_worker():\n        ctx.run(\n            f'\"{BUILD_DIR}/mediasoup-worker-test-asan-address\" --invisibles {mediasoup_test_tags}',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL,\n            env={**os.environ, 'ASAN_OPTIONS': 'halt_on_error=1:print_stacktrace=1:detect_leaks=1:symbolize=1:detect_stack_use_after_return=1:strict_init_order=1:check_initialization_order=1:detect_container_overflow=1'}\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true -Db_sanitize=undefined -Db_lundef=false'), flatc])\ndef test_asan_undefined(ctx):\n    \"\"\"\n    Run worker test with undefined Sanitizer with -fsanitize=undefined\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} mediasoup-worker-test-asan-undefined',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags mediasoup-worker-test-asan-undefined',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n    mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or '';\n\n    with cd_worker():\n        ctx.run(\n            f'\"{BUILD_DIR}/mediasoup-worker-test-asan-undefined\" --invisibles {mediasoup_test_tags}',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL,\n            # Exit with error if there are issues.\n            # NOTE: Ignore well known UBSan errors in OpenSSL.\n            env={**os.environ, 'UBSAN_OPTIONS': 'halt_on_error=1:print_stacktrace=1:suppressions=ubsan_suppressions.txt'}\n        );\n\n\n@task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_fuzzer=true -Db_sanitize=address -Db_lundef=false'), flatc])\ndef fuzzer(ctx):\n    \"\"\"\n    Build the mediasoup-worker-fuzzer binary (which uses libFuzzer)\n    \"\"\"\n\n    # NOTE: We need to pass '-Db_sanitize=address' to enable fuzzer in all Meson\n    # subprojects, so we pass it to the setup() task.\n\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" compile -C \"{BUILD_DIR}\" -j {NUM_CORES} mediasoup-worker-fuzzer',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n    with cd_worker():\n        ctx.run(\n            f'\"{MESON}\" install -C \"{BUILD_DIR}\" --no-rebuild --tags mediasoup-worker-fuzzer',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task\ndef fuzzer_run_all(ctx):\n    \"\"\"\n    Run all fuzzer cases\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'LSAN_OPTIONS=verbosity=1:log_threads=1 \"{BUILD_DIR}/mediasoup-worker-fuzzer\" -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus',\n            echo=True,\n            pty=PTY_SUPPORTED,\n            shell=SHELL\n        );\n\n\n@task\ndef docker(ctx):\n    \"\"\"\n    Build a Linux Ubuntu Docker image with fuzzer capable clang++\n    \"\"\"\n    if os.getenv('DOCKER_NO_CACHE') == 'true':\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build -f Dockerfile --no-cache --tag mediasoup/docker:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n    else:\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build -f Dockerfile --tag mediasoup/docker:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n\n\n@task\ndef docker_run(ctx):\n    \"\"\"\n    Run a container of the Ubuntu Docker image created in the docker task\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{DOCKER}\" run --name=mediasoupDocker -it --rm --privileged --cap-add SYS_PTRACE -v \"{WORKER_DIR}/../:/foo bar/mediasoup\" mediasoup/docker:latest',\n            echo=True,\n            pty=True, # NOTE: Needed to enter the terminal of the Docker image.\n            shell=SHELL\n        );\n\n\n@task\ndef docker_alpine(ctx):\n    \"\"\"\n    Build a Linux Alpine Docker image\n    \"\"\"\n    if os.getenv('DOCKER_NO_CACHE') == 'true':\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build -f Dockerfile.alpine --no-cache --tag mediasoup/docker-alpine:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n    else:\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build -f Dockerfile.alpine --tag mediasoup/docker-alpine:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n\n\n@task\ndef docker_alpine_run(ctx):\n    \"\"\"\n    Run a container of the Alpine Docker image created in the docker_alpine task\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{DOCKER}\" run --name=mediasoupDockerAlpine -it --rm --privileged --cap-add SYS_PTRACE -v \"{WORKER_DIR}/../:/foo bar/mediasoup\" mediasoup/docker-alpine:latest',\n            echo=True,\n            pty=True, # NOTE: Needed to enter the terminal of the Docker image.\n            shell=SHELL\n        );\n\n\n@task\ndef docker_386(ctx):\n    \"\"\"\n    Build a 386 Linux Debian (32 bits arch) Docker image\n    \"\"\"\n    if os.getenv('DOCKER_NO_CACHE') == 'true':\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build --platform linux/386 -f Dockerfile.386 --no-cache --tag mediasoup/docker-386:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n    else:\n        with cd_worker():\n            ctx.run(\n                f'\"{DOCKER}\" build --platform linux/386 -f Dockerfile.386 --tag mediasoup/docker-386:latest .',\n                echo=True,\n                pty=PTY_SUPPORTED,\n                shell=SHELL\n            );\n\n\n@task\ndef docker_386_run(ctx):\n    \"\"\"\n    Run a container of the 386 Linux Debian (32 bits arch) Docker image created\n    in the docker_386 task\n    \"\"\"\n    with cd_worker():\n        ctx.run(\n            f'\"{DOCKER}\" run --name=mediasoupDocker386 -it --rm --privileged --cap-add SYS_PTRACE -v \"{WORKER_DIR}/../:/foo bar/mediasoup\" mediasoup/docker-386:latest',\n            echo=True,\n            pty=True, # NOTE: Needed to enter the terminal of the Docker image.\n            shell=SHELL\n        );\n"
  },
  {
    "path": "worker/test/data/H264_SVC/naluInfo/naluInfo.csv",
    "content": "type,length,SId,TID,isIDR,firstSliceID,lastSliceID\n7,13,-1,-1,-1,-1,-1\n15,16,-1,-1,-1,-1,-1\n8,10,-1,-1,-1,-1,-1\n8,11,-1,-1,-1,-1,-1\n14,9,-1,-1,-1,-1,-1\n5,2023,0,0,1,0,0\n20,4747,1,0,1,0,0\n14,9,-1,-1,-1,-1,-1\n1,144,0,1,0,0,0\n20,514,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,148,0,2,0,0,0\n20,716,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,310,0,0,0,0,0\n20,1071,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,154,0,1,0,0,0\n20,579,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,144,0,2,0,0,0\n20,719,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,320,0,0,0,0,0\n20,1039,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,148,0,1,0,0,0\n20,594,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,179,0,2,0,0,0\n20,707,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,252,0,0,0,0,0\n20,1073,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,146,0,1,0,0,0\n20,632,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,177,0,2,0,0,0\n20,722,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,312,0,0,0,0,0\n20,1157,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,179,0,1,0,0,0\n20,716,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,207,0,2,0,0,0\n20,857,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,368,0,0,0,0,0\n20,1261,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,175,0,1,0,0,0\n20,776,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,183,0,2,0,0,0\n20,824,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,326,0,0,0,0,0\n20,1258,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,188,0,1,0,0,0\n20,676,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,189,0,2,0,0,0\n20,828,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,333,0,0,0,0,0\n20,1371,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,158,0,1,0,0,0\n20,755,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,250,0,2,0,0,0\n20,884,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,492,0,0,0,0,0\n20,1744,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,274,0,1,0,0,0\n20,1004,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,330,0,2,0,0,0\n20,1122,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,580,0,0,0,0,0\n20,1807,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,404,0,1,0,0,0\n20,1397,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,462,0,2,0,0,0\n20,1605,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,682,0,0,0,0,0\n20,2205,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,439,0,1,0,0,0\n20,1380,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,464,0,2,0,0,0\n20,1525,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,694,0,0,0,0,0\n20,2232,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,503,0,1,0,0,0\n20,1488,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,548,0,2,0,0,0\n20,1647,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,718,0,0,0,0,0\n20,2207,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,624,0,1,0,0,0\n20,1811,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,631,0,2,0,0,0\n20,1739,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,790,0,0,0,0,0\n20,2487,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,523,0,1,0,0,0\n20,1684,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,563,0,2,0,0,0\n20,1660,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,784,0,0,0,0,0\n20,2331,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,535,0,1,0,0,0\n20,1516,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,480,0,2,0,0,0\n20,1579,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,679,0,0,0,0,0\n20,2205,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,379,0,1,0,0,0\n20,1363,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,393,0,2,0,0,0\n20,1342,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,596,0,0,0,0,0\n20,1902,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,351,0,1,0,0,0\n20,1081,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,256,0,2,0,0,0\n20,963,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,451,0,0,0,0,0\n20,1590,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,185,0,1,0,0,0\n20,704,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,182,0,2,0,0,0\n20,687,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,415,0,0,0,0,0\n20,1421,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,182,0,1,0,0,0\n20,705,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,217,0,2,0,0,0\n20,981,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,426,0,0,0,0,0\n20,1702,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,162,0,1,0,0,0\n20,588,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,155,0,2,0,0,0\n20,710,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,284,0,0,0,0,0\n20,1115,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,151,0,1,0,0,0\n20,541,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,147,0,2,0,0,0\n20,527,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,267,0,0,0,0,0\n20,925,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,130,0,1,0,0,0\n20,563,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,132,0,2,0,0,0\n20,603,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,275,0,0,0,0,0\n20,1089,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,111,0,1,0,0,0\n20,594,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,162,0,2,0,0,0\n20,620,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,265,0,0,0,0,0\n20,1105,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,149,0,1,0,0,0\n20,577,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,150,0,2,0,0,0\n20,586,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,296,0,0,0,0,0\n20,1009,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,86,0,1,0,0,0\n20,474,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,160,0,2,0,0,0\n20,481,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,278,0,0,0,0,0\n20,934,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,117,0,1,0,0,0\n20,500,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,155,0,2,0,0,0\n20,559,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,233,0,0,0,0,0\n20,932,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,107,0,1,0,0,0\n20,470,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,91,0,2,0,0,0\n20,574,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,202,0,0,0,0,0\n20,982,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,132,0,1,0,0,0\n20,558,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,132,0,2,0,0,0\n20,564,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,255,0,0,0,0,0\n20,1092,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,117,0,1,0,0,0\n20,528,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,144,0,2,0,0,0\n20,592,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,321,0,0,0,0,0\n20,1253,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,164,0,1,0,0,0\n20,694,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,174,0,2,0,0,0\n20,680,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,317,0,0,0,0,0\n20,1205,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,91,0,1,0,0,0\n20,487,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,114,0,2,0,0,0\n20,554,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,206,0,0,0,0,0\n20,895,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,94,0,1,0,0,0\n20,431,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,105,0,2,0,0,0\n20,481,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,183,0,0,0,0,0\n20,807,1,0,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,86,0,1,0,0,0\n20,424,1,1,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,104,0,2,0,0,0\n20,488,1,2,0,0,0\n14,9,-1,-1,-1,-1,-1\n1,191,0,0,0,0,0\n20,790,1,0,0,0,0\n"
  },
  {
    "path": "worker/test/data/packet1.info",
    "content": "Real-Time Transport Protocol\n    10.. .... = Version: RFC 1889 Version (2)\n    ..0. .... = Padding: False\n    ...1 .... = Extension: True\n    .... 0000 = Contributing source identifiers count: 0\n    0... .... = Marker: False\n    Payload type: DynamicRTP-Type-111 (111)\n    Sequence number: 23617\n    Timestamp: 1660241882\n    Synchronization Source identifier: 0x9f7108e2 (2674985186)\n    Defined by profile: Unknown (0xbede)\n    Extension length: 1\n    Header extensions\n        RFC 5285 Header Extension (One-Byte Header)\n            Identifier: 1\n            Length: 1\n            Extension Data: ff\n    Payload: bae21a9da27876098db8e277d041d9c0f1e78a699af0ee8f...\n"
  },
  {
    "path": "worker/test/data/packet2.info",
    "content": "Real-Time Transport Protocol\n    [Stream setup by HEUR RT (frame 125)]\n    10.. .... = Version: RFC 1889 Version (2)\n    ..1. .... = Padding: True\n    ...0 .... = Extension: False\n    .... 0000 = Contributing source identifiers count: 0\n    0... .... = Marker: False\n    Payload type: DynamicRTP-Type-100 (100)\n    Sequence number: 28478\n    [Extended sequence number: 94014]\n    Timestamp: 172320136\n    Synchronization Source identifier: 0xc5abdf5a (3316375386)\n    Padding data: b8ece4baca4f2ca92f22bb99b859e8bd7ce122e4fa8bcb97...\n    Padding count: 149\n"
  },
  {
    "path": "worker/test/data/packet2.raw",
    "content": "do>\nEeūZO,/\"Y|\"˗p\u0014$\u0002\u0013Olk~3%\ni&ܽ>\u001d=2\u000e\u0013\u001c\r\u001cH٦M`&6\u001c*\u0004=1<PߣW~\u001b6K)CK&\u001c7b\"$\t\u0005\b@01˴ҼLY0\u001e\u0007\u001a'\u0019U@\u0011\u0010-\u001b\u001b*\u0004c5zI1w1LLr\u0002\u0010x\u0019\u0017\n2R}&q2㔹x7\u000ew\tvK\u0011L"
  },
  {
    "path": "worker/test/data/packet3.info",
    "content": "Real-Time Transport Protocol\n    10.. .... = Version: RFC 1889 Version (2)\n    ..0. .... = Padding: False\n    ...1 .... = Extension: True\n    .... 0000 = Contributing source identifiers count: 0\n    0... .... = Marker: False\n    Payload type: DynamicRTP-Type-111 (111)\n    Sequence number: 19354\n    Timestamp: 863466045\n    Synchronization Source identifier: 0x0e0dfad2 (235797202)\n    Defined by profile: Unknown (0xbede)\n    Extension length: 2\n    Header extensions\n        RFC 5285 Header Extension (One-Byte Header)\n            Identifier: 3\n            Length: 3\n            Extension Data: 65341e\n        RFC 5285 Header Extension (One-Byte Header)\n            Identifier: 1\n            Length: 1\n            Extension Data: d0\n    Payload: 094f4383213720a16a02f5c70eeae371c28556446d5b5cfb...\n"
  },
  {
    "path": "worker/test/data/rtp-stream-1.txt",
    "content": "// pt:123, kind:video, codec:VP8\n\npacket [seq:21006, timestamp:1533790901]\npacket [seq:21007, timestamp:1533790901]\npacket [seq:21008, timestamp:1533793871]\npacket [seq:21009, timestamp:1533793871]\npacket [seq:21010, timestamp:1533796931]\npacket [seq:21011, timestamp:1533796931]\npacket [seq:21012, timestamp:1533799901]\npacket [seq:21013, timestamp:1533802871]\npacket [seq:21014, timestamp:1533805931]\npacket [seq:21015, timestamp:1533808901]\npacket [seq:21016, timestamp:1533811871]\npacket [seq:21017, timestamp:1533814931]\npacket [seq:21018, timestamp:1533817901]\npacket [seq:21019, timestamp:1533820961]\npacket [seq:21020, timestamp:1533820961]\npacket [seq:21021, timestamp:1533823931]\npacket [seq:21022, timestamp:1533826901]\npacket [seq:21023, timestamp:1533826901]\npacket [seq:21024, timestamp:1533829961]\npacket [seq:21025, timestamp:1533829961]\npacket [seq:21026, timestamp:1533832931]\npacket [seq:21027, timestamp:1533832931]\npacket [seq:21028, timestamp:1533835901]\npacket [seq:21029, timestamp:1533838961]\npacket [seq:21030, timestamp:1533838961]\npacket [seq:21031, timestamp:1533841931]\npacket [seq:21032, timestamp:1533841931]\npacket [seq:21033, timestamp:1533844901]\npacket [seq:21034, timestamp:1533844901]\npacket [seq:21035, timestamp:1533847961]\npacket [seq:21036, timestamp:1533850931]\npacket [seq:21037, timestamp:1533853901]\npacket [seq:21038, timestamp:1533856961]\npacket [seq:21039, timestamp:1533859931]\npacket [seq:21040, timestamp:1533859931]\npacket [seq:21041, timestamp:1533862991]\npacket [seq:21042, timestamp:1533862991]\npacket [seq:21043, timestamp:1533865961]\npacket [seq:21044, timestamp:1533865961]\npacket [seq:21045, timestamp:1533868931]\npacket [seq:21046, timestamp:1533868931]\npacket [seq:21047, timestamp:1533871991]\npacket [seq:21048, timestamp:1533871991]\npacket [seq:21049, timestamp:1533874961]\npacket [seq:21050, timestamp:1533874961]\npacket [seq:21051, timestamp:1533877931]\npacket [seq:21052, timestamp:1533877931]\npacket [seq:21053, timestamp:1533880991]\npacket [seq:21054, timestamp:1533883961]\npacket [seq:21055, timestamp:1533887021]\npacket [seq:21056, timestamp:1533889991]\npacket [seq:21057, timestamp:1533892961]\npacket [seq:21058, timestamp:1533892961]\npacket [seq:21059, timestamp:1533896021]\npacket [seq:21060, timestamp:1533896021]\npacket [seq:21061, timestamp:1533898991]\npacket [seq:21062, timestamp:1533898991]\npacket [seq:21063, timestamp:1533901961]\npacket [seq:21064, timestamp:1533901961]\npacket [seq:21065, timestamp:1533905021]\npacket [seq:21066, timestamp:1533905021]\npacket [seq:21067, timestamp:1533907991]\npacket [seq:21068, timestamp:1533907991]\npacket [seq:21069, timestamp:1533910961]\npacket [seq:21070, timestamp:1533910961]\npacket [seq:21071, timestamp:1533914021]\npacket [seq:21072, timestamp:1533914021]\npacket [seq:21073, timestamp:1533916991]\npacket [seq:21074, timestamp:1533920051]\npacket [seq:21075, timestamp:1533923021]\npacket [seq:21076, timestamp:1533925991]\npacket [seq:21077, timestamp:1533925991]\npacket [seq:21078, timestamp:1533929051]\npacket [seq:21079, timestamp:1533929051]\npacket [seq:21080, timestamp:1533932021]\npacket [seq:21081, timestamp:1533932021]\npacket [seq:21082, timestamp:1533934991]\npacket [seq:21083, timestamp:1533934991]\npacket [seq:21084, timestamp:1533938051]\npacket [seq:21085, timestamp:1533938051]\npacket [seq:21086, timestamp:1533941021]\npacket [seq:21087, timestamp:1533941021]\npacket [seq:21088, timestamp:1533943991]\npacket [seq:21089, timestamp:1533943991]\npacket [seq:21090, timestamp:1533947051]\npacket [seq:21091, timestamp:1533947051]\npacket [seq:21092, timestamp:1533950021]\npacket [seq:21093, timestamp:1533950021]\npacket [seq:21094, timestamp:1533953081]\npacket [seq:21095, timestamp:1533956051]\npacket [seq:21096, timestamp:1533956051]\npacket [seq:21097, timestamp:1533959021]\npacket [seq:21098, timestamp:1533959021]\npacket [seq:21099, timestamp:1533962081]\npacket [seq:21100, timestamp:1533962081]\npacket [seq:21101, timestamp:1533965051]\npacket [seq:21102, timestamp:1533965051]\npacket [seq:21103, timestamp:1533968111]\npacket [seq:21104, timestamp:1533968111]\npacket [seq:21105, timestamp:1533971081]\npacket [seq:21106, timestamp:1533971081]\npacket [seq:21107, timestamp:1533974051]\npacket [seq:21108, timestamp:1533977111]\npacket [seq:21109, timestamp:1533977111]\npacket [seq:21110, timestamp:1533980081]\npacket [seq:21111, timestamp:1533980081]\npacket [seq:21112, timestamp:1533983141]\npacket [seq:21113, timestamp:1533983141]\npacket [seq:21114, timestamp:1533986111]\npacket [seq:21115, timestamp:1533986111]\npacket [seq:21116, timestamp:1533989081]\npacket [seq:21117, timestamp:1533989081]\npacket [seq:21118, timestamp:1533992141]\npacket [seq:21119, timestamp:1533992141]\npacket [seq:21120, timestamp:1533995111]\npacket [seq:21121, timestamp:1533995111]\npacket [seq:21122, timestamp:1533998081]\npacket [seq:21123, timestamp:1534001141]\npacket [seq:21124, timestamp:1534004111]\npacket [seq:21125, timestamp:1534004111]\npacket [seq:21126, timestamp:1534007081]\n"
  },
  {
    "path": "worker/test/include/RTC/ICE/iceCommon.hpp",
    "content": "#ifndef MS_TEST_RTC_ICE_COMMON_HPP\n#define MS_TEST_RTC_ICE_COMMON_HPP\n\n#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstdlib> // std::malloc(), std::free()\n#include <cstring> // std::memcpy()\n#include <string_view>\n\nnamespace iceCommon\n{\n\t// NOTE: We need to declare them here with `extern` and then define them in\n\t// iceCommon.cpp.\n\t// NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer).\n\textern thread_local uint8_t FactoryBuffer[66661];\n\textern thread_local uint8_t ResponseFactoryBuffer[66661];\n\textern thread_local uint8_t SerializeBuffer[66662];\n\textern thread_local uint8_t CloneBuffer[66663];\n\textern thread_local uint8_t DataBuffer[66664];\n\textern thread_local uint8_t ThrowBuffer[66665];\n\n\tvoid ResetBuffers();\n} // namespace iceCommon\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_STUN_PACKET(                                                                            \\\n  /*const RTC::ICE::StunPacket**/ packet,                                                             \\\n  /*const uint8_t**/ buffer,                                                                          \\\n  /*size_t*/ bufferLength,                                                                            \\\n  /*size_t*/ length,                                                                                  \\\n  /*RTC::ICE::StunPacket::Class*/ klass,                                                              \\\n  /*RTC::ICE::StunPacket::Method*/ method,                                                            \\\n  /*bool*/ hasUsername,                                                                               \\\n  /*std::string_view*/ username,                                                                      \\\n  /*bool*/ hasPriority,                                                                               \\\n  /*uint32_t*/ priority,                                                                              \\\n  /*bool*/ hasIceControlling,                                                                         \\\n  /*uint64_t*/ iceControlling,                                                                        \\\n  /*bool*/ hasIceControlled,                                                                          \\\n  /*uint64_t*/ iceControlled,                                                                         \\\n  /*bool*/ hasUseCandidate,                                                                           \\\n  /*bool*/ hasNomination,                                                                             \\\n  /*uint32_t*/ nomination,                                                                            \\\n  /*bool*/ hasSoftware,                                                                               \\\n  /*std::string_view*/ software,                                                                      \\\n  /*bool*/ hasXorMappedAddress,                                                                       \\\n  /*bool*/ hasErrorCode,                                                                              \\\n  /*uint16_t*/ errorCode,                                                                             \\\n  /*std::string_view*/ errorReasonPhrase,                                                             \\\n  /*bool*/ hasMessageIntegrity,                                                                       \\\n  /*bool*/ hasFingerprint)                                                                            \\\n\tdo                                                                                                  \\\n\t{                                                                                                   \\\n\t\tuint8_t* originalBuffer = static_cast<uint8_t*>(std::malloc(bufferLength));                       \\\n\t\tstd::memcpy(originalBuffer, buffer, bufferLength);                                                \\\n\t\tREQUIRE(RTC::ICE::StunPacket::IsStun(buffer, bufferLength) == true);                              \\\n\t\tREQUIRE(packet);                                                                                  \\\n\t\tREQUIRE(packet->GetBuffer() != nullptr);                                                          \\\n\t\tREQUIRE(packet->GetBuffer() == buffer);                                                           \\\n\t\tREQUIRE(packet->GetBufferLength() != 0);                                                          \\\n\t\tREQUIRE(packet->GetBufferLength() == bufferLength);                                               \\\n\t\tREQUIRE(packet->GetLength() != 0);                                                                \\\n\t\tREQUIRE(packet->GetLength() == length);                                                           \\\n\t\tREQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength());         \\\n\t\tREQUIRE(packet->GetClass() == klass);                                                             \\\n\t\tREQUIRE(packet->GetMethod() == method);                                                           \\\n\t\tREQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::USERNAME) == hasUsername);      \\\n\t\tREQUIRE(packet->GetUsername() == username);                                                       \\\n\t\tREQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::PRIORITY) == hasPriority);      \\\n\t\tREQUIRE(packet->GetPriority() == priority);                                                       \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ICE_CONTROLLING) ==                   \\\n\t\t  hasIceControlling);                                                                             \\\n\t\tREQUIRE(packet->GetIceControlling() == iceControlling);                                           \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ICE_CONTROLLED) == hasIceControlled); \\\n\t\tREQUIRE(packet->GetIceControlled() == iceControlled);                                             \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::USE_CANDIDATE) == hasUseCandidate);   \\\n\t\tREQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::NOMINATION) == hasNomination);  \\\n\t\tREQUIRE(packet->GetNomination() == nomination);                                                   \\\n\t\tREQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::SOFTWARE) == hasSoftware);      \\\n\t\tREQUIRE(packet->GetSoftware() == software);                                                       \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::XOR_MAPPED_ADDRESS) ==                \\\n\t\t  hasXorMappedAddress);                                                                           \\\n\t\tREQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ERROR_CODE) == hasErrorCode);   \\\n\t\tstd::string_view obtainedErrorReasonPhrase;                                                       \\\n\t\tREQUIRE(packet->GetErrorCode(obtainedErrorReasonPhrase) == errorCode);                            \\\n\t\tREQUIRE(obtainedErrorReasonPhrase == errorReasonPhrase);                                          \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::MESSAGE_INTEGRITY) ==                 \\\n\t\t  hasMessageIntegrity);                                                                           \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::FINGERPRINT) == hasFingerprint);      \\\n\t\tif (hasMessageIntegrity || hasFingerprint)                                                        \\\n\t\t{                                                                                                 \\\n\t\t\tREQUIRE(packet->IsProtected());                                                                 \\\n\t\t\tREQUIRE_THROWS_AS(packet->Protect(), MediaSoupError);                                           \\\n\t\t\tREQUIRE_THROWS_AS(packet->AddUsername(\"foo\"), MediaSoupError);                                  \\\n\t\t}                                                                                                 \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  std::any_of(                                                                                    \\\n\t\t    packet->GetTransactionId(),                                                                   \\\n\t\t    packet->GetTransactionId() + RTC::ICE::StunPacket::TransactionIdLength,                       \\\n\t\t    [](uint8_t b)                                                                                 \\\n\t\t    {                                                                                             \\\n\t\t\t    return b != 0;                                                                              \\\n\t\t    }));                                                                                          \\\n\t\tREQUIRE(packet->Validate(/*storeAttributes*/ false));                                             \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->CheckAuthentication(\"lalala-fooo-œæ€œæ€\", \"∫∂ƒ3487345345Ω∑©™ƒ™œ\") !=                    \\\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);                                                \\\n\t\tREQUIRE(                                                                                          \\\n\t\t  packet->CheckAuthentication(\"kasjdhaksjhd\") != RTC::ICE::StunPacket::AuthenticationResult::OK); \\\n\t\tif (                                                                                              \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::MESSAGE_INTEGRITY) ||                 \\\n\t\t  packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::FINGERPRINT))                         \\\n\t\t{                                                                                                 \\\n\t\t\tREQUIRE_THROWS_AS(packet->Protect(\"isoiulkajdlkja\"), MediaSoupError);                           \\\n\t\t\tREQUIRE_THROWS_AS(packet->Protect(), MediaSoupError);                                           \\\n\t\t}                                                                                                 \\\n\t\tREQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true);    \\\n\t\tREQUIRE_THROWS_AS(                                                                                \\\n\t\t  const_cast<RTC::ICE::StunPacket*>(packet)->Serialize(iceCommon::ThrowBuffer, length - 1),       \\\n\t\t  MediaSoupError);                                                                                \\\n\t\tREQUIRE_THROWS_AS(packet->Clone(iceCommon::ThrowBuffer, length - 1), MediaSoupError);             \\\n\t\tREQUIRE(packet->Validate(/*storeAttributes*/ false));                                             \\\n\t\tstd::free(originalBuffer);                                                                        \\\n\t} while (false)\n\n#endif\n"
  },
  {
    "path": "worker/test/include/RTC/RTP/rtpCommon.hpp",
    "content": "#ifndef MS_TEST_RTC_RTP_COMMON_HPP\n#define MS_TEST_RTC_RTP_COMMON_HPP\n\n#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstdlib> // std::malloc(), std::free()\n#include <cstring> // std::memcpy()\n\nnamespace rtpCommon\n{\n\t// NOTE: We need to declare them here with `extern` and then define them in\n\t// rtpCommon.cpp.\n\t// NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer).\n\textern thread_local uint8_t FactoryBuffer[66661];\n\textern thread_local uint8_t SerializeBuffer[66662];\n\textern thread_local uint8_t CloneBuffer[66663];\n\textern thread_local uint8_t DataBuffer[66664];\n\textern thread_local uint8_t ThrowBuffer[66665];\n\n\tvoid ResetBuffers();\n} // namespace rtpCommon\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_RTP_PACKET(                                                                            \\\n  /*const RTC::RTP::Packet**/ packet,                                                                \\\n  /*const uint8_t**/ buffer,                                                                         \\\n  /*size_t*/ bufferLength,                                                                           \\\n  /*size_t*/ length,                                                                                 \\\n  /*uint8_t*/ payloadType,                                                                           \\\n  /*bool*/ hasMarker,                                                                                \\\n  /*uint16_t*/ seqNumber,                                                                            \\\n  /*uint32_t*/ timestamp,                                                                            \\\n  /*uint32_t*/ ssrc,                                                                                 \\\n  /*bool*/ hasCsrcs,                                                                                 \\\n  /*bool*/ hasHeaderExtension,                                                                       \\\n  /*size_t*/ headerExtensionValueLength,                                                             \\\n  /*bool*/ hasOneByteExtensions,                                                                     \\\n  /*bool*/ hasTwoBytesExtensions,                                                                    \\\n  /*bool*/ hasPayload,                                                                               \\\n  /*size_t*/ payloadLength,                                                                          \\\n  /*bool*/ hasPadding,                                                                               \\\n  /*uint8_t*/ paddingLength)                                                                         \\\n\tdo                                                                                                 \\\n\t{                                                                                                  \\\n\t\tuint8_t* originalBuffer = static_cast<uint8_t*>(std::malloc(bufferLength));                      \\\n\t\tstd::memcpy(originalBuffer, buffer, bufferLength);                                               \\\n\t\tREQUIRE(RTC::RTP::Packet::IsRtp(buffer, bufferLength) == true);                                  \\\n\t\tREQUIRE(packet);                                                                                 \\\n\t\tREQUIRE(packet->GetBuffer() != nullptr);                                                         \\\n\t\tREQUIRE(packet->GetBuffer() == buffer);                                                          \\\n\t\tREQUIRE(packet->GetBufferLength() != 0);                                                         \\\n\t\tREQUIRE(packet->GetBufferLength() == bufferLength);                                              \\\n\t\tREQUIRE(packet->GetLength() != 0);                                                               \\\n\t\tREQUIRE(packet->GetLength() == length);                                                          \\\n\t\tREQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength());        \\\n\t\tREQUIRE(static_cast<unsigned>(packet->GetVersion()) == 2);                                       \\\n\t\tREQUIRE(static_cast<unsigned>(packet->GetPayloadType()) == payloadType);                         \\\n\t\tREQUIRE(packet->HasMarker() == hasMarker);                                                       \\\n\t\tREQUIRE(packet->GetSequenceNumber() == seqNumber);                                               \\\n\t\tREQUIRE(packet->GetTimestamp() == timestamp);                                                    \\\n\t\tREQUIRE(packet->GetSsrc() == ssrc);                                                              \\\n\t\tREQUIRE(packet->HasCsrcs() == hasCsrcs);                                                         \\\n\t\tREQUIRE(packet->HasHeaderExtension() == hasHeaderExtension);                                     \\\n\t\tREQUIRE(packet->GetHeaderExtensionValueLength() == headerExtensionValueLength);                  \\\n\t\tREQUIRE(packet->HasExtensions() == (hasOneByteExtensions || hasTwoBytesExtensions));             \\\n\t\tREQUIRE(packet->HasOneByteExtensions() == hasOneByteExtensions);                                 \\\n\t\tREQUIRE(packet->HasTwoBytesExtensions() == hasTwoBytesExtensions);                               \\\n\t\tif (!packet->HasHeaderExtension())                                                               \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE(packet->GetHeaderExtensionValueLength() == 0);                                         \\\n\t\t\tREQUIRE(packet->HasExtensions() == false);                                                     \\\n\t\t\tREQUIRE(packet->HasOneByteExtensions() == false);                                              \\\n\t\t\tREQUIRE(packet->HasTwoBytesExtensions() == false);                                             \\\n\t\t}                                                                                                \\\n\t\tREQUIRE(packet->HasPayload() == hasPayload);                                                     \\\n\t\tREQUIRE(packet->GetPayloadLength() == payloadLength);                                            \\\n\t\tsize_t realPayloadLength;                                                                        \\\n\t\tpacket->GetPayload(realPayloadLength);                                                           \\\n\t\tREQUIRE(realPayloadLength == payloadLength);                                                     \\\n\t\tif (!packet->HasPayload())                                                                       \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE(packet->GetPayload() == nullptr);                                                      \\\n\t\t\tREQUIRE(packet->GetPayloadLength() == 0);                                                      \\\n\t\t\tsize_t realPayloadLength;                                                                      \\\n\t\t\tREQUIRE(packet->GetPayload(realPayloadLength) == nullptr);                                     \\\n\t\t\tREQUIRE(realPayloadLength == 0);                                                               \\\n\t\t}                                                                                                \\\n\t\tREQUIRE(packet->HasPadding() == hasPadding);                                                     \\\n\t\tREQUIRE(static_cast<unsigned>(packet->GetPaddingLength()) == paddingLength);                     \\\n\t\tif (!packet->HasPadding())                                                                       \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE(static_cast<unsigned>(packet->GetPaddingLength()) == 0);                               \\\n\t\t}                                                                                                \\\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));                                            \\\n\t\tREQUIRE_NOTHROW(packet->SetPayloadType(packet->GetPayloadType()));                               \\\n\t\tREQUIRE_NOTHROW(packet->SetMarker(packet->HasMarker()));                                         \\\n\t\tREQUIRE_NOTHROW(packet->SetSequenceNumber(packet->GetSequenceNumber()));                         \\\n\t\tREQUIRE_NOTHROW(packet->SetTimestamp(packet->GetTimestamp()));                                   \\\n\t\tREQUIRE_NOTHROW(packet->SetSsrc(packet->GetSsrc()));                                             \\\n\t\tif (!packet->HasHeaderExtension())                                                               \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE_NOTHROW(packet->RemoveHeaderExtension());                                              \\\n\t\t}                                                                                                \\\n\t\tif (!hasPadding)                                                                                 \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE_NOTHROW(packet->SetPayload(packet->GetPayload(), packet->GetPayloadLength()));         \\\n\t\t\tREQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \\\n\t\t\tREQUIRE_NOTHROW(packet->SetPayloadLength(packet->GetPayloadLength()));                         \\\n\t\t\tREQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \\\n\t\t}                                                                                                \\\n\t\tREQUIRE_THROWS_AS(                                                                               \\\n\t\t  packet->SetPayload(rtpCommon::DataBuffer, packet->GetBufferLength()), MediaSoupError);         \\\n\t\tREQUIRE_THROWS_AS(packet->SetPayloadLength(packet->GetBufferLength()), MediaSoupError);          \\\n\t\tREQUIRE_NOTHROW(packet->SetPaddingLength(packet->GetPaddingLength()));                           \\\n\t\tif (packet->IsPaddedTo4Bytes() && packet->GetPaddingLength() < 4)                                \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE_NOTHROW(packet->PadTo4Bytes());                                                        \\\n\t\t\tREQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \\\n\t\t}                                                                                                \\\n\t\tREQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true);   \\\n\t\tREQUIRE_THROWS_AS(                                                                               \\\n\t\t  const_cast<RTC::RTP::Packet*>(packet)->Serialize(rtpCommon::ThrowBuffer, length - 1),          \\\n\t\t  MediaSoupError);                                                                               \\\n\t\tREQUIRE_THROWS_AS(packet->Clone(rtpCommon::ThrowBuffer, length - 1), MediaSoupError);            \\\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));                                            \\\n\t\tstd::free(originalBuffer);                                                                       \\\n\t} while (false)\n\n#endif\n"
  },
  {
    "path": "worker/test/include/RTC/SCTP/sctpCommon.hpp",
    "content": "#ifndef MS_TEST_RTC_SCTP_COMMON_HPP\n#define MS_TEST_RTC_SCTP_COMMON_HPP\n\n#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nnamespace sctpCommon\n{\n\t// NOTE: We need to declare them here with `extern` and then define them in\n\t// common.cpp.\n\t// NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer).\n\textern thread_local uint8_t FactoryBuffer[66661];\n\textern thread_local uint8_t SerializeBuffer[66662];\n\textern thread_local uint8_t CloneBuffer[66663];\n\textern thread_local uint8_t DataBuffer[66664];\n\textern thread_local uint8_t ThrowBuffer[66665];\n\n\tvoid ResetBuffers();\n} // namespace sctpCommon\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_SCTP_PACKET(                                                                         \\\n  /*const RTC::SCTP::Packet**/ packet,                                                             \\\n  /*const uint8_t**/ buffer,                                                                       \\\n  /*size_t*/ bufferLength,                                                                         \\\n  /*size_t*/ length,                                                                               \\\n  /*uint16_t*/ sourcePort,                                                                         \\\n  /*uint16_t*/ destinationPort,                                                                    \\\n  /*uint32_t*/ verificationTag,                                                                    \\\n  /*uint32_t*/ checksum,                                                                           \\\n  /*bool*/ hasValidCrc32cChecksum,                                                                 \\\n  /*size_t*/ chunksCount)                                                                          \\\n\tdo                                                                                               \\\n\t{                                                                                                \\\n\t\tREQUIRE(RTC::SCTP::Packet::IsSctp(buffer, length) == true);                                    \\\n\t\tREQUIRE(packet);                                                                               \\\n\t\tREQUIRE(packet->GetBuffer() != nullptr);                                                       \\\n\t\tREQUIRE(packet->GetBuffer() == buffer);                                                        \\\n\t\tREQUIRE(packet->GetBufferLength() != 0);                                                       \\\n\t\tREQUIRE(packet->GetBufferLength() == bufferLength);                                            \\\n\t\tREQUIRE(packet->GetLength() != 0);                                                             \\\n\t\tREQUIRE(packet->GetLength() == length);                                                        \\\n\t\tREQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength());      \\\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(packet->GetLength()) == true);                           \\\n\t\tREQUIRE(packet->GetSourcePort() == sourcePort);                                                \\\n\t\tREQUIRE(packet->GetDestinationPort() == destinationPort);                                      \\\n\t\tREQUIRE(packet->GetVerificationTag() == verificationTag);                                      \\\n\t\tREQUIRE(packet->GetChecksum() == checksum);                                                    \\\n\t\tREQUIRE(packet->ValidateCRC32cChecksum() == hasValidCrc32cChecksum);                           \\\n\t\tREQUIRE(packet->GetChunksCount() == chunksCount);                                              \\\n\t\tREQUIRE(packet->HasChunks() == (chunksCount > 0));                                             \\\n\t\tREQUIRE(packet->GetChunkAt(chunksCount) == nullptr);                                           \\\n\t\tREQUIRE(                                                                                       \\\n\t\t  helpers::areBuffersEqual(packet->GetBuffer(), packet->GetLength(), buffer, length) == true); \\\n\t\tREQUIRE_THROWS_AS(                                                                             \\\n\t\t  const_cast<RTC::SCTP::Packet*>(packet)->Serialize(sctpCommon::ThrowBuffer, length - 1),      \\\n\t\t  MediaSoupError);                                                                             \\\n\t\tREQUIRE_THROWS_AS(packet->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError);         \\\n\t} while (false)\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_SCTP_CHUNK(                                                                          \\\n  /*const RTC::SCTP::Chunk**/ chunk,                                                               \\\n  /*uint8_t**/ buffer,                                                                             \\\n  /*size_t*/ bufferLength,                                                                         \\\n  /*size_t*/ length,                                                                               \\\n  /*RTC::SCTP::Chunk::ChunkType*/ chunkType,                                                       \\\n  /*bool*/ unknownType,                                                                            \\\n  /*RTC::SCTP::Chunk::ActionForUnknownChunkType*/ actionForUnknownChunkType,                       \\\n  /*uint8_t*/ flags,                                                                               \\\n  /*bool*/ canHaveParameters,                                                                      \\\n  /*size_t*/ parametersCount,                                                                      \\\n  /*bool*/ canHaveErrorCauses,                                                                     \\\n  /*size_t*/ errorCausesCount)                                                                     \\\n\tdo                                                                                               \\\n\t{                                                                                                \\\n\t\tREQUIRE(chunk);                                                                                \\\n\t\tREQUIRE(chunk->GetBuffer() != nullptr);                                                        \\\n\t\tif (buffer)                                                                                    \\\n\t\t{                                                                                              \\\n\t\t\tREQUIRE(chunk->GetBuffer() == buffer);                                                       \\\n\t\t}                                                                                              \\\n\t\tREQUIRE(chunk->GetBufferLength() != 0);                                                        \\\n\t\tREQUIRE(chunk->GetBufferLength() == bufferLength);                                             \\\n\t\tREQUIRE(chunk->GetLength() != 0);                                                              \\\n\t\tREQUIRE(chunk->GetLength() == length);                                                         \\\n\t\tREQUIRE(chunk->GetAvailableLength() == chunk->GetBufferLength() - chunk->GetLength());         \\\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(chunk->GetLength()) == true);                            \\\n\t\tREQUIRE(chunk->GetType() == chunkType);                                                        \\\n\t\tREQUIRE(chunk->HasUnknownType() == unknownType);                                               \\\n\t\tREQUIRE(chunk->GetActionForUnknownChunkType() == actionForUnknownChunkType);                   \\\n\t\tREQUIRE(chunk->GetFlags() == flags);                                                           \\\n\t\tREQUIRE(chunk->CanHaveParameters() == canHaveParameters);                                      \\\n\t\tif (!canHaveParameters)                                                                        \\\n\t\t{                                                                                              \\\n\t\t\tREQUIRE_THROWS_AS(                                                                           \\\n\t\t\t  const_cast<RTC::SCTP::Chunk*>(reinterpret_cast<const RTC::SCTP::Chunk*>(chunk))            \\\n\t\t\t    ->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>(),                            \\\n\t\t\t  MediaSoupError);                                                                           \\\n\t\t}                                                                                              \\\n\t\tREQUIRE(chunk->GetParametersCount() == parametersCount);                                       \\\n\t\tREQUIRE(chunk->HasParameters() == (parametersCount > 0));                                      \\\n\t\tREQUIRE(chunk->GetParameterAt(parametersCount) == nullptr);                                    \\\n\t\tREQUIRE(chunk->CanHaveErrorCauses() == canHaveErrorCauses);                                    \\\n\t\tif (!canHaveErrorCauses)                                                                       \\\n\t\t{                                                                                              \\\n\t\t\tREQUIRE_THROWS_AS(                                                                           \\\n\t\t\t  const_cast<RTC::SCTP::Chunk*>(reinterpret_cast<const RTC::SCTP::Chunk*>(chunk))            \\\n\t\t\t    ->BuildErrorCauseInPlace<RTC::SCTP::InvalidStreamIdentifierErrorCause>(),                \\\n\t\t\t  MediaSoupError);                                                                           \\\n\t\t}                                                                                              \\\n\t\tREQUIRE(chunk->GetErrorCausesCount() == errorCausesCount);                                     \\\n\t\tREQUIRE(chunk->HasErrorCauses() == (errorCausesCount > 0));                                    \\\n\t\tREQUIRE(chunk->GetErrorCauseAt(errorCausesCount) == nullptr);                                  \\\n\t\tif (buffer)                                                                                    \\\n\t\t{                                                                                              \\\n\t\t\tREQUIRE(                                                                                     \\\n\t\t\t  helpers::areBuffersEqual(chunk->GetBuffer(), chunk->GetLength(), buffer, length) == true); \\\n\t\t}                                                                                              \\\n\t\tREQUIRE_THROWS_AS(                                                                             \\\n\t\t  const_cast<RTC::SCTP::Chunk*>(reinterpret_cast<const RTC::SCTP::Chunk*>(chunk))              \\\n\t\t    ->Serialize(sctpCommon::ThrowBuffer, length - 1),                                          \\\n\t\t  MediaSoupError);                                                                             \\\n\t\tREQUIRE_THROWS_AS(chunk->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError);          \\\n\t} while (false)\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_SCTP_PARAMETER(                                                                       \\\n  /*const RTC::SCTP::Parameter**/ parameter,                                                        \\\n  /*const uint8_t**/ buffer,                                                                        \\\n  /*size_t*/ bufferLength,                                                                          \\\n  /*size_t*/ length,                                                                                \\\n  /*RTC::SCTP::Parameter::ParameterType*/ parameterType,                                            \\\n  /*bool*/ unknownType,                                                                             \\\n  /*RTC::SCTP::Parameter::ActionForUnknownParameterType*/ actionForUnknownParameterType)            \\\n\tdo                                                                                                \\\n\t{                                                                                                 \\\n\t\tREQUIRE(parameter);                                                                             \\\n\t\tREQUIRE(parameter->GetBuffer() != nullptr);                                                     \\\n\t\tif (buffer)                                                                                     \\\n\t\t{                                                                                               \\\n\t\t\tREQUIRE(parameter->GetBuffer() == buffer);                                                    \\\n\t\t}                                                                                               \\\n\t\tREQUIRE(parameter->GetBufferLength() != 0);                                                     \\\n\t\tREQUIRE(parameter->GetBufferLength() == bufferLength);                                          \\\n\t\tREQUIRE(parameter->GetLength() != 0);                                                           \\\n\t\tREQUIRE(parameter->GetLength() == length);                                                      \\\n\t\tREQUIRE(                                                                                        \\\n\t\t  parameter->GetAvailableLength() == parameter->GetBufferLength() - parameter->GetLength());    \\\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(parameter->GetLength()) == true);                         \\\n\t\tREQUIRE(parameter->GetType() == parameterType);                                                 \\\n\t\tREQUIRE(parameter->HasUnknownType() == unknownType);                                            \\\n\t\tREQUIRE(parameter->GetActionForUnknownParameterType() == actionForUnknownParameterType);        \\\n\t\tif (buffer)                                                                                     \\\n\t\t{                                                                                               \\\n\t\t\tREQUIRE(                                                                                      \\\n\t\t\t  helpers::areBuffersEqual(parameter->GetBuffer(), parameter->GetLength(), buffer, length) == \\\n\t\t\t  true);                                                                                      \\\n\t\t}                                                                                               \\\n\t\tREQUIRE_THROWS_AS(                                                                              \\\n\t\t  const_cast<RTC::SCTP::Parameter*>(reinterpret_cast<const RTC::SCTP::Parameter*>(parameter))   \\\n\t\t    ->Serialize(sctpCommon::ThrowBuffer, length - 1),                                           \\\n\t\t  MediaSoupError);                                                                              \\\n\t\tREQUIRE_THROWS_AS(parameter->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError);       \\\n\t} while (false)\n\n// NOLINTNEXTLINE (cppcoreguidelines-macro-usage)\n#define CHECK_SCTP_ERROR_CAUSE(                                                                      \\\n  /*const RTC::SCTP::ErrorCause**/ errorCause,                                                       \\\n  /*const uint8_t**/ buffer,                                                                         \\\n  /*size_t*/ bufferLength,                                                                           \\\n  /*size_t*/ length,                                                                                 \\\n  /*RTC::SCTP::ErrorCause::ErrorCauseCode*/ causeCode,                                               \\\n  /*bool*/ unknownCode)                                                                              \\\n\tdo                                                                                                 \\\n\t{                                                                                                  \\\n\t\tREQUIRE(errorCause);                                                                             \\\n\t\tREQUIRE(errorCause->GetBuffer() != nullptr);                                                     \\\n\t\tif (buffer)                                                                                      \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE(errorCause->GetBuffer() == buffer);                                                    \\\n\t\t}                                                                                                \\\n\t\tREQUIRE(errorCause->GetBufferLength() != 0);                                                     \\\n\t\tREQUIRE(errorCause->GetBufferLength() == bufferLength);                                          \\\n\t\tREQUIRE(errorCause->GetLength() != 0);                                                           \\\n\t\tREQUIRE(errorCause->GetLength() == length);                                                      \\\n\t\tREQUIRE(                                                                                         \\\n\t\t  errorCause->GetAvailableLength() == errorCause->GetBufferLength() - errorCause->GetLength());  \\\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(errorCause->GetLength()) == true);                         \\\n\t\tREQUIRE(errorCause->GetCode() == causeCode);                                                     \\\n\t\tREQUIRE(errorCause->HasUnknownCode() == unknownCode);                                            \\\n\t\tif (buffer)                                                                                      \\\n\t\t{                                                                                                \\\n\t\t\tREQUIRE(                                                                                       \\\n\t\t\t  helpers::areBuffersEqual(                                                                    \\\n\t\t\t    errorCause->GetBuffer(), errorCause->GetLength(), buffer, length) == true);                \\\n\t\t}                                                                                                \\\n\t\tREQUIRE_THROWS_AS(                                                                               \\\n\t\t  const_cast<RTC::SCTP::ErrorCause*>(reinterpret_cast<const RTC::SCTP::ErrorCause*>(errorCause)) \\\n\t\t    ->Serialize(sctpCommon::ThrowBuffer, length - 1),                                            \\\n\t\t  MediaSoupError);                                                                               \\\n\t\tREQUIRE_THROWS_AS(errorCause->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError);       \\\n\t} while (false)\n\n#endif\n"
  },
  {
    "path": "worker/test/include/catch2Macros.hpp",
    "content": "#ifndef MS_CATCH2_MACROS_HPP\n#define MS_CATCH2_MACROS_HPP\n\n#include <catch2/catch_test_macros.hpp>\n\n/**\n * `VerificationResult` struct is defined in mocks/include/mockTypes.hpp.\n */\n// clang-format off\n#define REQUIRE_VERIFICATION_RESULT(verificationResult) \\\n\tdo \\\n\t{ \\\n\t\tconst auto& result = (verificationResult); \\\n\t\t\\\n\t\tif (!(result.ok)) \\\n\t\t{ \\\n\t\t\tFAIL(result.errorMessage); \\\n\t\t} \\\n\t} while (false)\n\n// clang-format off\n#define REQUIRE_WITH_MESSAGE(condition, errorMessage) \\\n\tdo \\\n\t{ \\\n\t\tif (!(condition)) \\\n\t\t{ \\\n\t\t\tFAIL(errorMessage); \\\n\t\t} \\\n\t} while (false)\n\n#endif\n"
  },
  {
    "path": "worker/test/include/testHelpers.hpp",
    "content": "#ifndef MS_TEST_HELPERS_HPP\n#define MS_TEST_HELPERS_HPP\n\n#include \"common.hpp\"\n\nnamespace helpers\n{\n\tbool readBinaryFile(const char* file, uint8_t* buffer, size_t* len);\n\n\tbool areBuffersEqual(const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2);\n} // namespace helpers\n\n#endif\n"
  },
  {
    "path": "worker/test/src/RTC/ICE/TestStunPacket.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"test/include/RTC/ICE/iceCommon.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/ICE/StunPacket.hpp\"\n#include <uv.h>\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <string>\n\nSCENARIO(\"ICE StunPacket\", \"[serializable][ice][stunpacket]\")\n{\n\ticeCommon::ResetBuffers();\n\n\tSECTION(\"StunPacket::Parse() a STUN request with message integrity and fingerprint succeeds\")\n\t{\n\t\t// Binding Request\n\t\t// - buffer length: 128 bytes\n\t\t// - transaction id: 0x4A31775941764E5470644B33\n\t\t// - username: \"78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg\"\n\t\t// - priority: 1853693695\n\t\t// - ice controlling: 15897499370457501716\n\t\t// - use candidate: yes\n\t\t// - message integrity: f39a23b3a6054e75b39df2177100182da76834f8\n\t\t// - fingerprint: 1782005644\n\t\t//\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x00, 0x01, 0x00, 0x6C,\n\t\t\t0x21, 0x12, 0xA4, 0x42,\n\t\t\t0x4A, 0x31, 0x77, 0x59,\n\t\t\t0x41, 0x76, 0x4E, 0x54,\n\t\t\t0x70, 0x64, 0x4B, 0x33,\n\t\t\t0x00, 0x06, 0x00, 0x25,\n\t\t\t0x37, 0x38, 0x74, 0x61,\n\t\t\t0x6C, 0x35, 0x70, 0x63,\n\t\t\t0x36, 0x64, 0x6B, 0x79,\n\t\t\t0x76, 0x31, 0x72, 0x70,\n\t\t\t0x67, 0x35, 0x36, 0x76,\n\t\t\t0x75, 0x61, 0x79, 0x35,\n\t\t\t0x6A, 0x65, 0x31, 0x33,\n\t\t\t0x63, 0x65, 0x77, 0x6D,\n\t\t\t0x3A, 0x73, 0x33, 0x4A,\n\t\t\t0x67, 0x00, 0x00, 0x00,\n\t\t\t0xC0, 0x57, 0x00, 0x04,\n\t\t\t0x00, 0x03, 0x00, 0x0A,\n\t\t\t0x80, 0x2A, 0x00, 0x08,\n\t\t\t0xDC, 0x9F, 0x43, 0x72,\n\t\t\t0xE9, 0x1D, 0x90, 0x14,\n\t\t\t0x00, 0x25, 0x00, 0x00,\n\t\t\t0x00, 0x24, 0x00, 0x04,\n\t\t\t0x6E, 0x7D, 0x1E, 0xFF,\n\t\t\t0x00, 0x08, 0x00, 0x14,\n\t\t\t0xF3, 0x9A, 0x23, 0xB3,\n\t\t\t0xA6, 0x05, 0x4E, 0x75,\n\t\t\t0xB3, 0x9D, 0xF2, 0x17,\n\t\t\t0x71, 0x00, 0x18, 0x2D,\n\t\t\t0xA7, 0x68, 0x34, 0xF8,\n\t\t\t0x80, 0x28, 0x00, 0x04,\n\t\t\t0x6A, 0x37, 0x3F, 0x8C\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> request{ RTC::ICE::StunPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffert*/ buffer,\n\t\t                  /*bufferLength*/ sizeof(buffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg\",\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ 1853693695,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ 15897499370457501716u,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tconst std::string usernameFragment1 = \"78tal5pc6dkyv1rpg56vuay5je13cewm\";\n\t\tconst std::string password          = \"1ezk7fni4jeo5bt7ibcdk4wjl8712suw\";\n\n\t\tREQUIRE(\n\t\t  request->CheckAuthentication(usernameFragment1, password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(request->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->Protect(), MediaSoupError);\n\n\t\t/* Serialize it. */\n\n\t\trequest->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::SerializeBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::SerializeBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg\",\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ 1853693695,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ 15897499370457501716u,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  request->CheckAuthentication(usernameFragment1, password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(request->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->Protect(), MediaSoupError);\n\n\t\t/* Clone it. */\n\n\t\trequest.reset(request->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer)));\n\n\t\tstd::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::CloneBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::CloneBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg\",\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ 1853693695,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ 15897499370457501716u,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  request->CheckAuthentication(usernameFragment1, password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(request->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->Protect(), MediaSoupError);\n\t}\n\n\tSECTION(\n\t  \"StunPacket::Parse() a STUN success response without message integrity or fingerprint succeeds\")\n\t{\n\t\t// Binding Success Response\n\t\t// - buffer length: 44 bytes\n\t\t// - transaction id: 0x0102030405060708090A0B0C\n\t\t// - xor-mapped-address: ip 2001:db8:85a3:0:0:8a2e:370:7334, port 1234\n\t\t//\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x01, 0x01, 0x00, 0x18,\n\t\t\t0x21, 0x12, 0xA4, 0x42,\n\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t0x05, 0x06, 0x07, 0x08,\n\t\t\t0x09, 0x0A, 0x0B, 0x0C,\n\t\t\t0x00, 0x20, 0x00, 0x14,\n\t\t\t0x00, 0x02, 0x25, 0xC0,\n\t\t\t0x01, 0x13, 0xA9, 0xFA,\n\t\t\t0x84, 0xA1, 0x03, 0x04,\n\t\t\t0x05, 0x06, 0x8D, 0x26,\n\t\t\t0x0A, 0x7A, 0x78, 0x38\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> successResponse{ RTC::ICE::StunPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffert*/ buffer,\n\t\t                  /*bufferLength*/ sizeof(buffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tstruct sockaddr_storage obtainedXorMappedAddressStorage{};\n\n\t\tREQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage)));\n\n\t\tint family;\n\t\tuint16_t port;\n\t\tstd::string ip;\n\n\t\tUtils::IP::GetAddressInfo(\n\t\t  reinterpret_cast<sockaddr*>(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port);\n\n\t\tREQUIRE(family == AF_INET6);\n\t\tstd::string expectedIp = \"2001:db8:85a3:0:0:8a2e:370:7334\";\n\t\tREQUIRE(ip == Utils::IP::NormalizeIp(expectedIp));\n\t\tREQUIRE(port == 1234);\n\n\t\t/* Serialize it. */\n\n\t\tsuccessResponse->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::SerializeBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::SerializeBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t/* Clone it. */\n\n\t\tsuccessResponse.reset(\n\t\t  successResponse->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer)));\n\n\t\tstd::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::CloneBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::CloneBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\t}\n\n\tSECTION(\"StunPacket::Parse() a STUN error response without message integrity or fingerprint succeeds\")\n\t{\n\t\t// Binding Error Response\n\t\t// - buffer length: 108 bytes\n\t\t// - transaction id: 0x0102030405060708090A0B0C\n\t\t// - username: \"œæ€å∫∂\"\n\t\t// - ice controlled: 12345678\n\t\t// - software: \"mediasoup test\"\n\t\t// - error code: 456\n\t\t// - error reason phrase: \"Something failed Ω∑© :)\"\n\t\t//\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x01, 0x11, 0x00, 0x58,\n\t  \t0x21, 0x12, 0xA4, 0x42,\n\t  \t0x01, 0x02, 0x03, 0x04,\n\t  \t0x05, 0x06, 0x07, 0x08,\n\t  \t0x09, 0x0A, 0x0B, 0x0C,\n\t  \t0x00, 0x06, 0x00, 0x0F,\n\t  \t0xC5, 0x93, 0xC3, 0xA6,\n\t  \t0xE2, 0x82, 0xAC, 0xC3,\n\t  \t0xA5, 0xE2, 0x88, 0xAB,\n\t  \t0xE2, 0x88, 0x82, 0x00,\n\t  \t0x80, 0x29, 0x00, 0x08,\n\t  \t0x00, 0x00, 0x00, 0x00,\n\t  \t0x00, 0xBC, 0x61, 0x4E,\n\t  \t0x80, 0x22, 0x00, 0x0E,\n\t  \t0x6D, 0x65, 0x64, 0x69,\n\t  \t0x61, 0x73, 0x6F, 0x75,\n\t  \t0x70, 0x20, 0x74, 0x65,\n\t  \t0x73, 0x74, 0x00, 0x00,\n\t  \t0x00, 0x09, 0x00, 0x1F,\n\t  \t0x00, 0x00, 0x04, 0x38,\n\t  \t0x53, 0x6F, 0x6D, 0x65,\n\t  \t0x74, 0x68, 0x69, 0x6E,\n\t  \t0x67, 0x20, 0x66, 0x61,\n\t  \t0x69, 0x6C, 0x65, 0x64,\n\t  \t0x20, 0xCE, 0xA9, 0xE2,\n\t  \t0x88, 0x91, 0xC2, 0xA9,\n\t  \t0x20, 0x3A, 0x29, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> errorResponse{ RTC::ICE::StunPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffert*/ buffer,\n\t\t                  /*bufferLength*/ sizeof(buffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"œæ€å∫∂\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ true,\n\t\t                  /*iceControlled*/ 12345678,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ \"mediasoup test\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ 456,\n\t\t                  /*errorReasonPhrase*/ \"Something failed Ω∑© :)\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t/* Serialize it. */\n\n\t\terrorResponse->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::SerializeBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::SerializeBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"œæ€å∫∂\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ true,\n\t\t                  /*iceControlled*/ 12345678,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ \"mediasoup test\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ 456,\n\t\t                  /*errorReasonPhrase*/ \"Something failed Ω∑© :)\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t/* Clone it. */\n\n\t\terrorResponse.reset(errorResponse->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer)));\n\n\t\tstd::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::CloneBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::CloneBuffer),\n\t\t                  /*length*/ sizeof(buffer),\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ \"œæ€å∫∂\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ true,\n\t\t                  /*iceControlled*/ 12345678,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ \"mediasoup test\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ 456,\n\t\t                  /*errorReasonPhrase*/ \"Something failed Ω∑© :)\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\t}\n\n\tSECTION(\"StunPacket::Factory() creating a request succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t transactionId[RTC::ICE::StunPacket::TransactionIdLength] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44, 0x55, 0x66,\n\t\t\t0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> request{ RTC::ICE::StunPacket::Factory(\n\t\t\ticeCommon::FactoryBuffer,\n\t\t\tsizeof(iceCommon::FactoryBuffer),\n\t\t\tRTC::ICE::StunPacket::Class::REQUEST,\n\t\t\tRTC::ICE::StunPacket::Method::BINDING,\n\t\t\ttransactionId) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t// Byte length: 27 (1 byte of padding needed).\n\t\tstd::string username          = \"œæ€å∫∂:¢∞¬÷12\";\n\t\tstd::string usernameFragment1 = \"œæ€å∫∂\";\n\t\t// Byte length: 4.\n\t\tuint32_t priority = 999888777u;\n\t\t// Byte length: 8.\n\t\tuint64_t iceControlling = 15697499370457501716u;\n\t\t// Byte length of USE_CANDIDATE: 0.\n\t\t// // Byte length: 4.\n\t\tuint32_t nomination = 12345678u;\n\t\t// Byte length: 18 (2 byte of padding needed).\n\t\tstd::string software = \"mediasoup x.y.z :)\";\n\t\t// Byte length: 4 + 23 (1 byte of padding needed).\n\t\tuint16_t errorCode            = 666;\n\t\tstd::string errorReasonPhrase = \"UPPS UNKNOWN ERROR 😊\";\n\n\t\t// Total length of the Attributes.\n\t\tsize_t attributesLen =\n\t\t  (4 + 27 + 1) + (4 + 4) + (4 + 8) + (4) + (4 + 4) + (4 + 18 + 2) + (4 + 4 + 23 + 1);\n\n\t\trequest->AddUsername(username);\n\t\trequest->AddPriority(priority);\n\t\trequest->AddIceControlling(iceControlling);\n\t\trequest->AddUseCandidate();\n\t\trequest->AddNomination(nomination);\n\t\trequest->AddSoftware(software);\n\t\trequest->AddErrorCode(errorCode, errorReasonPhrase);\n\n\t\t// It should fail if we try to add a duplicated Attribute.\n\t\tREQUIRE_THROWS_AS(request->AddUsername(username), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddPriority(priority), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddIceControlling(iceControlling), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddUseCandidate(), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddNomination(nomination), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddSoftware(software), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->AddErrorCode(errorCode, errorReasonPhrase), MediaSoupError);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ username,\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ priority,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ iceControlling,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ true,\n\t\t                  /*nomination*/ nomination,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ software,\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    request->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength,\n\t\t    transactionId,\n\t\t    RTC::ICE::StunPacket::TransactionIdLength));\n\n\t\t/* Serialize it. */\n\n\t\trequest->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::SerializeBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::SerializeBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ username,\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ priority,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ iceControlling,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ true,\n\t\t                  /*nomination*/ nomination,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ software,\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    request->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength,\n\t\t    transactionId,\n\t\t    RTC::ICE::StunPacket::TransactionIdLength));\n\n\t\t/* Clone it. */\n\n\t\trequest.reset(request->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer)));\n\n\t\tstd::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer));\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::CloneBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::CloneBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ username,\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ priority,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ iceControlling,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ true,\n\t\t                  /*nomination*/ nomination,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ software,\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    request->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength,\n\t\t    transactionId,\n\t\t    RTC::ICE::StunPacket::TransactionIdLength));\n\n\t\t/* Protect the STUN Packet. */\n\n\t\tstd::string password = \"asjhdkjhkasd\";\n\n\t\trequest->Protect(password);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ request.get(),\n\t\t                  /*buffer*/ iceCommon::CloneBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::CloneBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 +\n\t\t                    RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::REQUEST,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ true,\n\t\t                  /*username*/ username,\n\t\t                  /*hasPriority*/ true,\n\t\t                  /*priority*/ priority,\n\t\t                  /*hasIceControlling*/ true,\n\t\t                  /*iceControlling*/ iceControlling,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ true,\n\t\t                  /*hasNomination*/ true,\n\t\t                  /*nomination*/ nomination,\n\t\t                  /*hasSoftware*/ true,\n\t\t                  /*software*/ software,\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  request->CheckAuthentication(usernameFragment1, password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(request->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(request->Protect(), MediaSoupError);\n\t}\n\n\tSECTION(\"StunPacket::Factory() creating a success response succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> successResponse{ RTC::ICE::StunPacket::Factory(\n\t\t\ticeCommon::FactoryBuffer,\n\t\t\tsizeof(iceCommon::FactoryBuffer),\n\t\t\tRTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t\tRTC::ICE::StunPacket::Method::BINDING) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tstruct sockaddr_storage xorMappedAddressStorage{};\n\n\t\t// Byte length: 8.\n\t\tauto* xorMappedAddressIn =\n\t\t  reinterpret_cast<struct sockaddr_in*>(std::addressof(xorMappedAddressStorage));\n\t\tauto* xorMappedAddress =\n\t\t  reinterpret_cast<struct sockaddr*>(std::addressof(xorMappedAddressStorage));\n\n\t\tuv_ip4_addr(\"22.33.0.125\", 5678, xorMappedAddressIn);\n\n\t\t// Total length of the Attributes.\n\t\tsize_t attributesLen = (4 + 8);\n\n\t\tsuccessResponse->AddXorMappedAddress(xorMappedAddress);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tstruct sockaddr_storage obtainedXorMappedAddressStorage{};\n\n\t\tREQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage)));\n\n\t\tint family;\n\t\tuint16_t port;\n\t\tstd::string ip;\n\n\t\tUtils::IP::GetAddressInfo(\n\t\t  reinterpret_cast<sockaddr*>(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port);\n\n\t\tREQUIRE(family == AF_INET);\n\t\tstd::string expectedIp = \"22.33.0.125\";\n\t\tREQUIRE(ip == Utils::IP::NormalizeIp(expectedIp));\n\t\tREQUIRE(port == 5678);\n\n\t\tstd::memset(iceCommon::FactoryBuffer, 0x00, sizeof(iceCommon::FactoryBuffer));\n\n\t\t/* Create a new fresh success response. */\n\n\t\tsuccessResponse.reset(\n\t\t  RTC::ICE::StunPacket::Factory(\n\t\t    iceCommon::FactoryBuffer,\n\t\t    sizeof(iceCommon::FactoryBuffer),\n\t\t    RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t    RTC::ICE::StunPacket::Method::BINDING));\n\n\t\t// Byte length: 20.\n\t\tauto* xorMappedAddressIn6 =\n\t\t  reinterpret_cast<struct sockaddr_in6*>(std::addressof(xorMappedAddressStorage));\n\t\txorMappedAddress = reinterpret_cast<struct sockaddr*>(std::addressof(xorMappedAddressStorage));\n\n\t\tuv_ip6_addr(\"2001:db8::1234\", 20002, xorMappedAddressIn6);\n\n\t\t// Total length of the Attributes.\n\t\tattributesLen = (4 + 20);\n\n\t\tsuccessResponse->AddXorMappedAddress(xorMappedAddress);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage)));\n\n\t\tUtils::IP::GetAddressInfo(\n\t\t  reinterpret_cast<sockaddr*>(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port);\n\n\t\tREQUIRE(family == AF_INET6);\n\t\texpectedIp = \"2001:db8::1234\";\n\t\tREQUIRE(ip == Utils::IP::NormalizeIp(expectedIp));\n\t\tREQUIRE(port == 20002);\n\n\t\t/* Protect the STUN Packet. */\n\n\t\tstd::string password = \"asjhdkjhkasd\";\n\n\t\tsuccessResponse->Protect(password);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 +\n\t\t                    RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ true,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  successResponse->CheckAuthentication(password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(successResponse->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(successResponse->Protect(), MediaSoupError);\n\t}\n\n\tSECTION(\"StunPacket::Factory() creating an error response succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> errorResponse{ RTC::ICE::StunPacket::Factory(\n\t\t\ticeCommon::FactoryBuffer,\n\t\t\tsizeof(iceCommon::FactoryBuffer),\n\t\t\tRTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t\tRTC::ICE::StunPacket::Method::BINDING) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t// Byte length: 4 + 23 (1 byte of padding needed).\n\t\tuint16_t errorCode            = 666;\n\t\tstd::string errorReasonPhrase = \"UPPS UNKNOWN ERROR 😊\";\n\n\t\t// Total length of the Attributes.\n\t\tsize_t attributesLen = (4 + 4 + 23 + 1);\n\n\t\terrorResponse->AddErrorCode(errorCode, errorReasonPhrase);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t/* Protect the STUN Packet. */\n\n\t\tstd::string password = \"23786asdas123\";\n\n\t\terrorResponse->Protect(password);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 +\n\t\t                    RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  errorResponse->CheckAuthentication(password) == RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError);\n\n\t\t/* Create a new fresh error response. */\n\n\t\terrorResponse.reset(\n\t\t  RTC::ICE::StunPacket::Factory(\n\t\t    iceCommon::FactoryBuffer,\n\t\t    sizeof(iceCommon::FactoryBuffer),\n\t\t    RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t    RTC::ICE::StunPacket::Method::BINDING));\n\n\t\t// Byte length: 4 + 11 (1 byte of padding needed).\n\t\terrorCode         = 400;\n\t\terrorReasonPhrase = \"Bad Request\";\n\n\t\t// Total length of the Attributes.\n\t\tattributesLen = (4 + 4 + 11 + 1);\n\n\t\terrorResponse->AddErrorCode(errorCode, errorReasonPhrase);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\t/* Protect the STUN Packet (without password). */\n\n\t\t// Protect() without password only adds FINGERPRINT Attribute.\n\t\terrorResponse->Protect();\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::FactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::FactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ errorCode,\n\t\t                  /*errorReasonPhrase*/ errorReasonPhrase,\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\t// Cannot check authentication in a STUN Packet without MESSAGE-INTEGRITY.\n\t\tREQUIRE(\n\t\t  errorResponse->CheckAuthentication(password) ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::BAD_MESSAGE);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError);\n\t}\n\n\tSECTION(\"StunPacket::CreateSuccessResponse() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> request{ RTC::ICE::StunPacket::Factory(\n\t\t\ticeCommon::FactoryBuffer,\n\t\t\tsizeof(iceCommon::FactoryBuffer),\n\t\t\tRTC::ICE::StunPacket::Class::REQUEST,\n\t\t\tRTC::ICE::StunPacket::Method::BINDING) };\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> successResponse{ request->CreateSuccessResponse(\n\t\t\ticeCommon::ResponseFactoryBuffer, sizeof(iceCommon::ResponseFactoryBuffer)) };\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::ResponseFactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    successResponse->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength,\n\t\t    request->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength) == true);\n\n\t\tsuccessResponse->Protect(\"qwekqjhwekjahsd\");\n\n\t\tCHECK_STUN_PACKET(/*packet*/ successResponse.get(),\n\t\t                  /*buffer*/ iceCommon::ResponseFactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + 4 +\n\t\t                    RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ false,\n\t\t                  /*errorCode*/ 0,\n\t\t                  /*errorReasonPhrase*/ \"\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  successResponse->CheckAuthentication(\"qwekqjhwekjahsd\") ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(successResponse->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(successResponse->Protect(), MediaSoupError);\n\t}\n\n\tSECTION(\"StunPacket::CreateErrorResponse() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> request{ RTC::ICE::StunPacket::Factory(\n\t\t\ticeCommon::FactoryBuffer,\n\t\t\tsizeof(iceCommon::FactoryBuffer),\n\t\t\tRTC::ICE::StunPacket::Class::REQUEST,\n\t\t\tRTC::ICE::StunPacket::Method::BINDING) };\n\n\t\tstd::unique_ptr<RTC::ICE::StunPacket> errorResponse{ request->CreateErrorResponse(\n\t\t\ticeCommon::ResponseFactoryBuffer, sizeof(iceCommon::ResponseFactoryBuffer), 666, \"BAD STUFF\") };\n\n\t\t// Total length of the Attributes (ERROR-CODE).\n\t\tconst size_t attributesLen = (4 + 4 + 9 + 3);\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::ResponseFactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ 666,\n\t\t                  /*errorReasonPhrase*/ \"BAD STUFF\",\n\t\t                  /*hasMessageIntegrity*/ false,\n\t\t                  /*hasFingerprint*/ false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    errorResponse->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength,\n\t\t    request->GetTransactionId(),\n\t\t    RTC::ICE::StunPacket::TransactionIdLength) == true);\n\n\t\terrorResponse->Protect(\"qwekqjhwekjahsd\");\n\n\t\tCHECK_STUN_PACKET(/*packet*/ errorResponse.get(),\n\t\t                  /*buffer*/ iceCommon::ResponseFactoryBuffer,\n\t\t                  /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer),\n\t\t                  /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 +\n\t\t                    RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4,\n\t\t                  /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE,\n\t\t                  /*method*/ RTC::ICE::StunPacket::Method::BINDING,\n\t\t                  /*hasUsername*/ false,\n\t\t                  /*username*/ \"\",\n\t\t                  /*hasPriority*/ false,\n\t\t                  /*priority*/ 0,\n\t\t                  /*hasIceControlling*/ false,\n\t\t                  /*iceControlling*/ 0,\n\t\t                  /*hasIceControlled*/ false,\n\t\t                  /*iceControlled*/ 0,\n\t\t                  /*hasUseCandidate*/ false,\n\t\t                  /*hasNomination*/ false,\n\t\t                  /*nomination*/ 0,\n\t\t                  /*hasSoftware*/ false,\n\t\t                  /*software*/ \"\",\n\t\t                  /*hasXorMappedAddress*/ false,\n\t\t                  /*hasErrorCode*/ true,\n\t\t                  /*errorCode*/ 666,\n\t\t                  /*errorReasonPhrase*/ \"BAD STUFF\",\n\t\t                  /*hasMessageIntegrity*/ true,\n\t\t                  /*hasFingerprint*/ true);\n\n\t\tREQUIRE(\n\t\t  errorResponse->CheckAuthentication(\"qwekqjhwekjahsd\") ==\n\t\t  RTC::ICE::StunPacket::AuthenticationResult::OK);\n\n\t\t// Trying to modify a STUN Packet once protected must throw.\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(\"qweqwe\"), MediaSoupError);\n\t\tREQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/ICE/iceCommon.cpp",
    "content": "#include \"test/include/RTC/ICE/iceCommon.hpp\" // in worker/test/include/\n#include <cstring>                            // std::memset\n\nnamespace iceCommon\n{\n\t// NOTE: We don't need `alignas(4)` for STUN Packet parsing. However we do it\n\t// for consistency with rtpCommon.cpp and sctpCommon.cpp.\n\talignas(4) thread_local uint8_t FactoryBuffer[];\n\talignas(4) thread_local uint8_t ResponseFactoryBuffer[];\n\talignas(4) thread_local uint8_t SerializeBuffer[];\n\talignas(4) thread_local uint8_t CloneBuffer[];\n\talignas(4) thread_local uint8_t DataBuffer[];\n\talignas(4) thread_local uint8_t ThrowBuffer[];\n\n\tvoid ResetBuffers()\n\t{\n\t\tstd::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer));\n\t\tstd::memset(ResponseFactoryBuffer, 0xAA, sizeof(ResponseFactoryBuffer));\n\t\tstd::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer));\n\t\tstd::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer));\n\t\tstd::memset(DataBuffer, 0xDD, sizeof(DataBuffer));\n\t\tstd::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer));\n\n\t\tfor (size_t i = 0; i < 256; ++i)\n\t\t{\n\t\t\tDataBuffer[i] = static_cast<uint8_t>(i);\n\t\t}\n\t}\n} // namespace iceCommon\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestBye.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/Bye.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n#include <string>\n\nSCENARIO(\"RTCP BYE\", \"[rtcp][bye]\")\n{\n\t// RCTP BYE packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x82, 0xcb, 0x00, 0x06, // Type: 203 (Bye), Count: 2, length: 2\n\t\t0x62, 0x42, 0x76, 0xe0, // SSRC: 0x624276e0\n\t\t0x26, 0x24, 0x67, 0x0e, // SSRC: 0x2624670e\n\t\t0x0e, 0x48, 0x61, 0x73, // Length: 14, Text: \"Hasta la vista\"\n\t\t0x74, 0x61, 0x20, 0x6c,\n\t\t0x61, 0x20, 0x76, 0x69,\n\t\t0x73, 0x74, 0x61, 0x00\n\t};\n\t// clang-format on\n\n\tconst uint32_t ssrc1{ 0x624276e0 };\n\tconst uint32_t ssrc2{ 0x2624670e };\n\tconst std::string reason(\"Hasta la vista\");\n\n\t// NOTE: No need to pass const integers to the lambda.\n\t// NOTE: If we pass const integers then clang-tidy complains with\n\t// 'clang-diagnostic-unused-lambda-capture'.\n\tauto verify = [&reason](RTC::RTCP::ByePacket* packet)\n\t{\n\t\tREQUIRE(packet->GetReason() == reason);\n\n\t\tauto it = packet->Begin();\n\n\t\tREQUIRE(*it == ssrc1);\n\n\t\t++it;\n\n\t\tREQUIRE(*it == ssrc2);\n\t};\n\n\tSECTION(\"parse ByePacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::ByePacket> packet{ RTC::RTCP::ByePacket::Parse(buffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized instance with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create ByePacket\")\n\t{\n\t\t// Create local Bye packet and check content.\n\t\tRTC::RTCP::ByePacket packet;\n\n\t\tpacket.AddSsrc(ssrc1);\n\t\tpacket.AddSsrc(ssrc2);\n\t\tpacket.SetReason(reason);\n\n\t\tverify(&packet);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket.Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized instance with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsAfb.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsAfb.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS AFB\", \"[rtcp][feedback-ps][afb]\")\n{\n\t// RTCP AFB packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x8f, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 15 (AFB) Length: 3\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x00, 0x00, 0x00, 0x01  // Data\n\t};\n\t// clang-format on\n\n\t// AFB values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\n\tauto verify = [](RTC::RTCP::FeedbackPsAfbPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\t\tREQUIRE(packet->GetApplication() == RTC::RTCP::FeedbackPsAfbPacket::Application::UNKNOWN);\n\t};\n\n\tSECTION(\"parse FeedbackPsAfbPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsAfbPacket> packet{ RTC::RTCP::FeedbackPsAfbPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsFir.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsFir.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS FIR\", \"[rtcp][feedback-ps][fir]\")\n{\n\t// RTCP FIR packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x84, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 4 (FIR), Length: 4\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702\n\t\t0x04, 0x00, 0x00, 0x00  // Seq: 0x04\n\t};\n\t// clang-format on\n\n\t// FIR values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint32_t ssrc{ 0x02d03702 };\n\tconst uint8_t seq{ 4 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsFirPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsFirItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item->GetSsrc() == ssrc);\n\t\tREQUIRE(item->GetSequenceNumber() == seq);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsFirItem::Header) == 4);\n\t}\n\n\tSECTION(\"parse FeedbackPsFirPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsFirPacket> packet{ RTC::RTCP::FeedbackPsFirPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackPsFirPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackPsFirPacket packet(senderSsrc, mediaSsrc);\n\n\t\tauto* item = new RTC::RTCP::FeedbackPsFirItem(ssrc, seq);\n\n\t\tpacket.AddItem(item);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsLei.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsLei.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS LEI\", \"[rtcp][feedback-ps][lei]\")\n{\n\t// RTCP LEI packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x88, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 8 (LEI), Length: 3\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702\n\t};\n\t// clang-format on\n\n\t// LEI values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint32_t ssrc{ 0x02d03702 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsLeiPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsLeiItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item->GetSsrc() == ssrc);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsLeiItem::Header) == 4);\n\t}\n\n\tSECTION(\"parse FeedbackPsLeiPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsLeiPacket> packet{ RTC::RTCP::FeedbackPsLeiPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackPsLeiPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackPsLeiPacket packet(senderSsrc, mediaSsrc);\n\n\t\tauto* item = new RTC::RTCP::FeedbackPsLeiItem(ssrc);\n\n\t\tpacket.AddItem(item);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsPli.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsPli.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP PLI\", \"[rtcp][feedback-ps][pli]\")\n{\n\t// RTCP PLI packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x81, 0xce, 0x00, 0x02, // Type: 206 (Payload Specific), Count: 1 (PLI), Length: 2\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t};\n\t// clang-format on\n\n\t// PLI values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsPliPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\t};\n\n\tSECTION(\"parse FeedbackPsPliPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsPliPacket> packet{ RTC::RTCP::FeedbackPsPliPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackPsPliPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackPsPliPacket packet(senderSsrc, mediaSsrc);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsRemb.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsRemb.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS REMB\", \"[rtcp][feedback-ps][remb]\")\n{\n\t// RTCP REMB packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x8f, 0xce, 0x00, 0x06, // Type: 206 (Payload Specific), Count: 15 (AFB), Length: 6\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x52, 0x45, 0x4d, 0x42, // Unique Identifier: REMB\n\t\t0x02, 0x01, 0xdf, 0x82, // SSRCs: 2, BR exp: 0, Mantissa: 122754\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC1: 0x02d03702\n\t\t0x04, 0xa7, 0x67, 0x47  // SSRC2: 0x04a76747\n\t};\n\t// clang-format on\n\n\t// REMB values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0u };\n\tconst uint64_t bitrate{ 122754u };\n\tconst std::vector<uint32_t> ssrcs{ 0x02d03702, 0x04a76747 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [&ssrcs](RTC::RTCP::FeedbackPsRembPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\t\tREQUIRE(packet->GetBitrate() == bitrate);\n\t\tREQUIRE(packet->GetSsrcs() == ssrcs);\n\t};\n\n\tSECTION(\"parse FeedbackPsRembPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsRembPacket> packet{ RTC::RTCP::FeedbackPsRembPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackPsRembPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackPsRembPacket packet(senderSsrc, mediaSsrc);\n\n\t\tpacket.SetSsrcs(ssrcs);\n\t\tpacket.SetBitrate(bitrate);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsRpsi.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsRpsi.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS RPSI\", \"[rtcp][feedback-ps][rpsi]\")\n{\n\t// RTCP RPSI packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x83, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 3 (RPSI), Length: 4\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x08,                   // Padding Bits\n\t\t      0x02,             // Zero | Payload Type\n\t\t            0x00, 0x00, // Native RPSI bit string\n\t\t0x00, 0x00, 0x01, 0x00\n\t};\n\t// clang-format on\n\n\t// RPSI values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint8_t payloadType{ 2 };\n\tconst uint8_t payloadMask{ 1 };\n\tconst size_t length{ 5 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsRpsiPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsRpsiItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetPayloadType() == payloadType);\n\t\tREQUIRE(item->GetLength() == length);\n\t\tREQUIRE((item->GetBitString()[item->GetLength() - 1] & 1) == payloadMask);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsRpsiItem::Header) == 1);\n\t}\n\n\tSECTION(\"parse FeedbackPsRpsiPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsRpsiPacket> packet{ RTC::RTCP::FeedbackPsRpsiPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsSli.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsSli.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS SLI\", \"[rtcp][feedback-ps][sli]\")\n{\n\t// RTCP SLI packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x82, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 2 (SLI), Length: 3\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x00, 0x08, 0x01, 0x01  // First: 1, Number: 4, PictureId: 1\n\t};\n\t// clang-format on\n\n\t// SLI values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint16_t first{ 1 };\n\tconst uint16_t number{ 4 };\n\tconst uint8_t pictureId{ 1 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsSliPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsSliItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetFirst() == first);\n\t\tREQUIRE(item->GetNumber() == number);\n\t\tREQUIRE(item->GetPictureId() == pictureId);\n\t};\n\n\tSECTION(\"parse FeedbackPsSliPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsSliPacket> packet{ RTC::RTCP::FeedbackPsSliPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsTst.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsTst.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS TSTN\", \"[rtcp][feedback-ps][tstn]\")\n{\n\t// RTCP TSTN packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x84, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 4 (TST), Length: 4\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702\n\t\t0x08, 0x00, 0x00, 0x01  // Seq: 8, Reserved, Index: 1\n\t};\n\t// clang-format on\n\n\t// TSTN values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint32_t ssrc{ 0x02d03702 };\n\tconst uint8_t seq{ 8 };\n\tconst uint8_t index{ 1 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsTstnPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsTstnItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetSsrc() == ssrc);\n\t\tREQUIRE(item->GetSequenceNumber() == seq);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsTstrItem::Header) == 1);\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsTstnItem::Header) == 1);\n\t}\n\n\tSECTION(\"parse FeedbackPsTstnPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsTstnPacket> packet{ RTC::RTCP::FeedbackPsTstnPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackPsTstnPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackPsTstnPacket packet(senderSsrc, mediaSsrc);\n\n\t\tauto* item = new RTC::RTCP::FeedbackPsTstnItem(ssrc, seq, index);\n\n\t\tpacket.AddItem(item);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackPsVbcm.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPsVbcm.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback PS VBCM\", \"[rtcp][feedback-ps][vbcm]\")\n{\n\t// RTCP VBCM packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x84, 0xce, 0x00, 0x05, // Type: 206 (Payload Specific), Count: 4 (VBCM), Length: 5\n\t\t0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17\n\t\t0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702\n\t\t0x08,                   // Seq: 8\n\t\t      0x02,             // Zero | Payload Vbcm\n\t\t            0x00, 0x01, // Length\n\t\t0x01,                   // VBCM Octet String\n\t\t      0x00, 0x00, 0x00  // Padding\n\t};\n\t// clang-format on\n\n\t// VBCM values.\n\tconst uint32_t senderSsrc{ 0xfa17fa17 };\n\tconst uint32_t mediaSsrc{ 0 };\n\tconst uint32_t ssrc{ 0x02d03702 };\n\tconst uint8_t seq{ 8 };\n\tconst uint8_t payloadType{ 2 };\n\tconst uint16_t length{ 1 };\n\tconst uint8_t valueMask{ 1 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackPsVbcmPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tconst RTC::RTCP::FeedbackPsVbcmItem* item = *(packet->Begin());\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetSsrc() == ssrc);\n\t\tREQUIRE(item->GetSequenceNumber() == seq);\n\t\tREQUIRE(item->GetPayloadType() == payloadType);\n\t\tREQUIRE(item->GetLength() == length);\n\t\tREQUIRE((item->GetValue()[item->GetLength() - 1] & 1) == valueMask);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsVbcmItem::Header) == 4);\n\t}\n\n\tSECTION(\"parse FeedbackPsVbcmPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackPsVbcmPacket> packet{ RTC::RTCP::FeedbackPsVbcmPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpEcn.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpEcn.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP ECN\", \"[rtcp][feedback-rtp][ecn]\")\n{\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x88, 0xcd, 0x00, 0x07, // Type: 205 (Generic RTP Feedback), Count: 8 (ECN) Length: 7\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t\t0x00, 0x00, 0x00, 0x01, // Extended Highest Sequence Number\n\t\t0x00, 0x00, 0x00, 0x01, // ECT (0) Counter\n\t\t0x00, 0x00, 0x00, 0x01, // ECT (1) Counter\n\t\t0x00, 0x01,             // ECN-CE Counter\n\t\t            0x00, 0x01, // not-ECT Counter\n\t\t0x00, 0x01,             // Lost Packets Counter\n\t\t            0x00, 0x01  // Duplication Counter\n\t};\n\t// clang-format on\n\n\t// ECN values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\tconst uint32_t sequenceNumber{ 1 };\n\tconst uint32_t ect0Counter{ 1 };\n\tconst uint32_t ect1Counter{ 1 };\n\tconst uint16_t ecnCeCounter{ 1 };\n\tconst uint16_t notEctCounter{ 1 };\n\tconst uint16_t lostPackets{ 1 };\n\tconst uint16_t duplicatedPackets{ 1 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackRtpEcnPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tauto it          = packet->Begin();\n\t\tconst auto* item = *it;\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetSequenceNumber() == sequenceNumber);\n\t\tREQUIRE(item->GetEct0Counter() == ect0Counter);\n\t\tREQUIRE(item->GetEct1Counter() == ect1Counter);\n\t\tREQUIRE(item->GetEcnCeCounter() == ecnCeCounter);\n\t\tREQUIRE(item->GetNotEctCounter() == notEctCounter);\n\t\tREQUIRE(item->GetLostPackets() == lostPackets);\n\t\tREQUIRE(item->GetDuplicatedPackets() == duplicatedPackets);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpEcnItem::Header) == 4);\n\t}\n\n\tSECTION(\"parse FeedbackRtpEcnPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpEcnPacket> packet{ RTC::RTCP::FeedbackRtpEcnPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpNack.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP NACK\", \"[rtcp][feedback-rtp][nack]\")\n{\n\t// RTCP NACK packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x81, 0xcd, 0x00, 0x03, // Type: 205 (Generic RTP Feedback), Length: 3\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t\t0x0b, 0x8f, 0x00, 0x03  // NACK PID: 2959, NACK BLP: 0x0003\n\t};\n\t// clang-format on\n\n\t// NACK values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\tconst uint16_t pid{ 2959 };\n\tconst uint16_t lostPacketBitmask{ 0x0003 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackRtpNackPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tauto it          = packet->Begin();\n\t\tconst auto* item = *it;\n\n\t\tREQUIRE(item->GetPacketId() == pid);\n\t\tREQUIRE(item->GetLostPacketBitmask() == lostPacketBitmask);\n\t\tREQUIRE(item->CountRequestedPackets() == 3);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpNackItem::Header) == 2);\n\t}\n\n\tSECTION(\"parse FeedbackRtpNackItem\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpNackPacket> packet{ RTC::RTCP::FeedbackRtpNackPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpNackPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackRtpNackPacket packet(senderSsrc, mediaSsrc);\n\t\tauto* item = new RTC::RTCP::FeedbackRtpNackItem(pid, lostPacketBitmask);\n\n\t\tpacket.AddItem(item);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpSrReq.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpSrReq.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP SR-REQ\", \"[rtcp][feedback-rtp][sr-req]\")\n{\n\t// RTCP SR-REQ packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x85, 0xcd, 0x00, 0x02, // Type: 205 (Generic RTP Feedback), Count: 5 (SR-REQ) Length: 3\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t};\n\t// clang-format on\n\n\t// SR-REQ values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackRtpSrReqPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\t};\n\n\tSECTION(\"parse FeedbackRtpSrReqPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpSrReqPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpSrReqPacket::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpSrReqPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackRtpSrReqPacket packet(senderSsrc, mediaSsrc);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpTllei.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTllei.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP TLLEI\", \"[rtcp][feedback-rtp][tllei]\")\n{\n\t// RTCP TLLEI packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x87, 0xcd, 0x00, 0x03, // Type: 205 (Generic RTP Feedback), Count: 7 (TLLEI) Length: 3\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t\t0x00, 0x01, 0xaa, 0x55\n\t};\n\t// clang-format on\n\n\t// TLLEI values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\tconst uint16_t packetId{ 1 };\n\tconst uint16_t lostPacketBitmask{ 0b1010101001010101 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackRtpTlleiPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tauto it          = packet->Begin();\n\t\tconst auto* item = *it;\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetPacketId() == packetId);\n\t\tREQUIRE(item->GetLostPacketBitmask() == lostPacketBitmask);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpTlleiItem::Header) == 2);\n\t}\n\n\tSECTION(\"parse RTC::RTCP::FeedbackRtpTlleiPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTlleiPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpTlleiPacket::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpTmmb.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTmmb.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP TMMBR\", \"[rtcp][feedback-rtp][tmmb]\")\n{\n\t// RTCP TMMBR packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x83, 0xcd, 0x00, 0x04, // Type: 205 (Generic RTP Feedback), Count: 8 (TMMBR) Length: 7\n\t\t0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001\n\t\t0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee\n\t\t0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702\n\t\t0x18, 0x2c, 0x9e, 0x00\n\t};\n\t// clang-format on\n\n\t// TMMBR values.\n\tconst uint32_t senderSsrc{ 0x00000001 };\n\tconst uint32_t mediaSsrc{ 0x0330bdee };\n\tconst uint32_t ssrc{ 0x02d03702 };\n\tconst uint64_t bitrate{ 365504 };\n\tconst uint16_t overhead{ 0 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::FeedbackRtpTmmbrPacket* packet)\n\t{\n\t\tREQUIRE(packet->GetSenderSsrc() == senderSsrc);\n\t\tREQUIRE(packet->GetMediaSsrc() == mediaSsrc);\n\n\t\tauto it          = packet->Begin();\n\t\tconst auto* item = *it;\n\n\t\tREQUIRE(item);\n\t\tREQUIRE(item->GetSsrc() == ssrc);\n\t\tREQUIRE(item->GetBitrate() == bitrate);\n\t\tREQUIRE(item->GetOverhead() == overhead);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpTmmbrItem::Header) == 4);\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpTmmbnItem::Header) == 4);\n\t}\n\n\tSECTION(\"parse FeedbackRtpTmmbrPacket\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTmmbrPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpTmmbrPacket::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(packet);\n\n\t\tverify(packet.get());\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\t// NOTE: Do not compare byte by byte since different binary values can\n\t\t\t// represent the same content.\n\t\t\tSECTION(\"create a packet out of the serialized buffer\")\n\t\t\t{\n\t\t\t\tconst std::unique_ptr<RTC::RTCP::FeedbackRtpTmmbrPacket> packet{\n\t\t\t\t\tRTC::RTCP::FeedbackRtpTmmbrPacket::Parse(buffer, sizeof(buffer))\n\t\t\t\t};\n\n\t\t\t\tverify(packet.get());\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpTmmbrPacket\")\n\t{\n\t\tRTC::RTCP::FeedbackRtpTmmbrPacket packet(senderSsrc, mediaSsrc);\n\t\tauto* item = new RTC::RTCP::FeedbackRtpTmmbrItem();\n\n\t\titem->SetSsrc(ssrc);\n\t\titem->SetBitrate(bitrate);\n\t\titem->SetOverhead(overhead);\n\n\t\tpacket.AddItem(item);\n\n\t\tverify(&packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestFeedbackRtpTransport.cpp",
    "content": "#include \"common.hpp\"\n#include \"Logger.hpp\"\n#include \"RTC/RTCP/FeedbackRtpTransport.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP Feedback RTP Transport\", \"[rtcp][feedback-rtp][transport]\")\n{\n\tstruct TestFeedbackRtpTransportInput\n\t{\n\t\tTestFeedbackRtpTransportInput(uint16_t sequenceNumber, uint64_t timestamp, size_t maxPacketSize)\n\t\t  : sequenceNumber(sequenceNumber), timestamp(timestamp), maxPacketSize(maxPacketSize)\n\t\t{\n\t\t}\n\n\t\tuint16_t sequenceNumber{ 0u };\n\t\tuint64_t timestamp{ 0u };\n\t\tsize_t maxPacketSize{ 0u };\n\t};\n\n\tstatic constexpr size_t RtcpMtu{ 1200u };\n\n\tconst uint32_t senderSsrc{ 1111u };\n\tconst uint32_t mediaSsrc{ 2222u };\n\n\tauto verify =\n\t  [](\n\t    const std::vector<struct TestFeedbackRtpTransportInput>& inputs,\n\t    std::vector<struct RTC::RTCP::FeedbackRtpTransportPacket::PacketResult> packetResults)\n\t{\n\t\tauto inputsIterator        = inputs.begin();\n\t\tauto packetResultsIterator = packetResults.begin();\n\t\tauto lastInput             = *inputsIterator;\n\n\t\tfor (++inputsIterator; inputsIterator != inputs.end(); ++inputsIterator, ++packetResultsIterator)\n\t\t{\n\t\t\tconst auto& input             = *inputsIterator;\n\t\t\tauto& packetResult            = *packetResultsIterator;\n\t\t\tconst uint16_t missingPackets = input.sequenceNumber - lastInput.sequenceNumber - 1;\n\n\t\t\tif (missingPackets > 0)\n\t\t\t{\n\t\t\t\t// All missing packets must be represented in packetResults.\n\t\t\t\tfor (uint16_t i{ 0u }; i < missingPackets; ++i)\n\t\t\t\t{\n\t\t\t\t\tpacketResult = *packetResultsIterator;\n\n\t\t\t\t\tREQUIRE(packetResult.sequenceNumber == lastInput.sequenceNumber + i + 1);\n\t\t\t\t\tREQUIRE(packetResult.received == false);\n\n\t\t\t\t\tpacketResultsIterator++;\n\t\t\t\t}\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tREQUIRE(packetResult.sequenceNumber == lastInput.sequenceNumber + 1);\n\t\t\t\tREQUIRE(packetResult.sequenceNumber == input.sequenceNumber);\n\t\t\t\tREQUIRE(packetResult.received == true);\n\t\t\t\tREQUIRE(\n\t\t\t\t  static_cast<int32_t>(packetResult.receivedAtMs & 0x1FFFFFC0) / 64 ==\n\t\t\t\t  static_cast<int32_t>(input.timestamp & 0x1FFFFFC0) / 64);\n\t\t\t}\n\n\t\t\tlastInput = input;\n\t\t}\n\t};\n\n\tSECTION(\n\t  \"create FeedbackRtpTransportPacket, small delta run length chunk and single large delta status packet\")\n\t{\n\t\tauto packet = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tREQUIRE(packet);\n\n\t\t/* clang-format off */\n\t\tstd::vector<struct TestFeedbackRtpTransportInput> inputs =\n\t\t{\n\t\t\t{ 999, 1000000000, RtcpMtu },  // Pre base.\n\t\t\t{ 1000, 1000000000, RtcpMtu }, // Base.\n\t\t\t{ 1001, 1000000001, RtcpMtu },\n\t\t\t{ 1002, 1000000012, RtcpMtu },\n\t\t\t{ 1003, 1000000015, RtcpMtu },\n\t\t\t{ 1004, 1000000017, RtcpMtu },\n\t\t\t{ 1005, 1000000018, RtcpMtu },\n\t\t\t{ 1006, 1000000018, RtcpMtu },\n\t\t\t{ 1007, 1000000018, RtcpMtu },\n\t\t\t{ 1008, 1000000018, RtcpMtu },\n\t\t\t{ 1009, 1000000019, RtcpMtu },\n\t\t\t{ 1010, 1000000010, RtcpMtu },\n\t\t\t{ 1011, 1000000011, RtcpMtu },\n\t\t\t{ 1012, 1000000011, RtcpMtu },\n\t\t\t{ 1013, 1000000013, RtcpMtu }\n\t\t};\n\t\t/* clang-format on */\n\n\t\tpacket->SetFeedbackPacketCount(1);\n\n\t\tfor (auto& input : inputs)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs.front()))\n\t\t\t{\n\t\t\t\tpacket->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1013);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000013);\n\n\t\t// Add a packet with greater seq number but older timestamp.\n\t\tpacket->AddPacket(1014, 1000000013 - 128, RtcpMtu);\n\t\tinputs.emplace_back(1014, 1000000013 - 128, RtcpMtu);\n\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1014);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000013 - 128);\n\n\t\tpacket->AddPacket(1015, 1000000015, RtcpMtu);\n\t\tinputs.emplace_back(1015, 1000000015, RtcpMtu);\n\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1015);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000015);\n\n\t\tpacket->Finish();\n\t\tverify(inputs, packet->GetPacketResults());\n\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1000);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 16);\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 1);\n\t\tREQUIRE(packet->GetPacketFractionLost() == 0);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(packet->GetSize() == len);\n\n\t\t\tSECTION(\"parse serialized buffer\")\n\t\t\t{\n\t\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet2{\n\t\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t\t};\n\n\t\t\t\tREQUIRE(packet2);\n\t\t\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1000);\n\t\t\t\tREQUIRE(packet2->GetPacketStatusCount() == 16);\n\t\t\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 1);\n\t\t\t\tREQUIRE(packet2->GetPacketFractionLost() == 0);\n\n\t\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\t\tauto len2 = packet2->Serialize(buffer2);\n\n\t\t\t\tREQUIRE(len == len2);\n\t\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\t\tREQUIRE(packet2->GetSize() == len2);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpTransportPacket, run length chunk (2)\")\n\t{\n\t\tauto packet = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\t/* clang-format off */\n\t\tstd::vector<TestFeedbackRtpTransportInput> inputs =\n\t\t{\n\t\t\t{ 999, 1000000000, RtcpMtu }, // Pre base.\n\t\t\t{ 1000, 1000000000, RtcpMtu }, // Base.\n\t\t\t{ 1050, 1000000216, RtcpMtu }\n\t\t};\n\t\t/* clang-format on */\n\n\t\tpacket->SetFeedbackPacketCount(10);\n\n\t\tfor (auto& input : inputs)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs.front()))\n\t\t\t{\n\t\t\t\tpacket->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tpacket->Finish();\n\t\tverify(inputs, packet->GetPacketResults());\n\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1000);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 51);\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 10);\n\t\tREQUIRE(packet->GetPacketFractionLost() > 0);\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1050);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000216);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(packet->GetSize() == len);\n\n\t\t\tSECTION(\"parse serialized buffer\")\n\t\t\t{\n\t\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet2{\n\t\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t\t};\n\n\t\t\t\tREQUIRE(packet2);\n\t\t\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1000);\n\t\t\t\tREQUIRE(packet2->GetPacketStatusCount() == 51);\n\t\t\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 10);\n\t\t\t\tREQUIRE(packet2->GetPacketFractionLost() > 0);\n\n\t\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\t\tauto len2 = packet2->Serialize(buffer2);\n\n\t\t\t\tREQUIRE(len == len2);\n\t\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\t\tREQUIRE(packet2->GetSize() == len2);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpTransportPacket, mixed chunks\")\n\t{\n\t\t/* clang-format off */\n\t\tstd::vector<TestFeedbackRtpTransportInput> inputs =\n\t\t{\n\t\t\t{ 999, 1000000000, RtcpMtu },  // Pre base.\n\t\t\t{ 1000, 1000000000, RtcpMtu }, // Base.\n\t\t\t{ 1001, 1000000100, RtcpMtu },\n\t\t\t{ 1002, 1000000200, RtcpMtu },\n\t\t\t{ 1015, 1000000300, RtcpMtu },\n\t\t\t{ 1016, 1000000400, RtcpMtu },\n\t\t\t{ 1017, 1000000500, RtcpMtu }\n\t\t};\n\t\t/* clang-format on */\n\n\t\tauto packet = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tpacket->SetFeedbackPacketCount(1);\n\n\t\tfor (auto& input : inputs)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs.front()))\n\t\t\t{\n\t\t\t\tpacket->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tpacket->Finish();\n\t\tverify(inputs, packet->GetPacketResults());\n\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1000);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 18);\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 1);\n\t\tREQUIRE(packet->GetPacketFractionLost() > 0);\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1017);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000500);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(packet->GetSize() == len);\n\n\t\t\tSECTION(\"parse serialized buffer\")\n\t\t\t{\n\t\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet2{\n\t\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t\t};\n\n\t\t\t\tREQUIRE(packet2);\n\t\t\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1000);\n\t\t\t\tREQUIRE(packet2->GetPacketStatusCount() == 18);\n\t\t\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 1);\n\t\t\t\tREQUIRE(packet2->GetPacketFractionLost() > 0);\n\n\t\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\t\tauto len2 = packet2->Serialize(buffer2);\n\n\t\t\t\tREQUIRE(len == len2);\n\t\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\t\tREQUIRE(packet2->GetSize() == len2);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create FeedbackRtpTransportPacket, incomplete two bit vector chunk\")\n\t{\n\t\tstd::vector<TestFeedbackRtpTransportInput> inputs = {\n\t\t\t{ 999,  1000000000, RtcpMtu }, // Pre base.\n\t\t\t{ 1000, 1000000100, RtcpMtu }, // Base.\n\t\t\t{ 1001, 1000000700, RtcpMtu },\n\t\t};\n\n\t\tauto packet = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tpacket->SetFeedbackPacketCount(1);\n\n\t\tfor (auto& input : inputs)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs.front()))\n\t\t\t{\n\t\t\t\tpacket->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tpacket->Finish();\n\t\tverify(inputs, packet->GetPacketResults());\n\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1000);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 2);\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 1);\n\t\tREQUIRE(packet->GetPacketFractionLost() == 0);\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1001);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000700);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(packet->GetSize() == len);\n\n\t\t\tSECTION(\"parse serialized buffer\")\n\t\t\t{\n\t\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet2{\n\t\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t\t};\n\n\t\t\t\tREQUIRE(packet2);\n\t\t\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1000);\n\t\t\t\tREQUIRE(packet2->GetPacketStatusCount() == 2);\n\t\t\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 1);\n\t\t\t\tREQUIRE(packet2->GetPacketFractionLost() == 0);\n\n\t\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\t\tauto len2 = packet2->Serialize(buffer2);\n\n\t\t\t\tREQUIRE(len == len2);\n\t\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\t\tREQUIRE(packet2->GetSize() == len2);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create two sequential FeedbackRtpTransportPackets\")\n\t{\n\t\t/* clang-format off */\n\t\tstd::vector<TestFeedbackRtpTransportInput> inputs =\n\t\t{\n\t\t\t{ 999, 1000000000, RtcpMtu },  // Pre base.\n\t\t\t{ 1000, 1000000000, RtcpMtu }, // Base.\n\t\t\t{ 1001, 1000000003, RtcpMtu },\n\t\t\t{ 1002, 1000000003, RtcpMtu },\n\t\t\t{ 1003, 1000000003, RtcpMtu },\n\t\t\t{ 1004, 1000000004, RtcpMtu },\n\t\t\t{ 1005, 1000000005, RtcpMtu },\n\t\t\t{ 1006, 1000000005, RtcpMtu },\n\t\t\t{ 1007, 1000000007, RtcpMtu }\n\t\t};\n\t\t/* clang-format on */\n\n\t\tauto packet = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tpacket->SetFeedbackPacketCount(1);\n\n\t\tfor (auto& input : inputs)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs.front()))\n\t\t\t{\n\t\t\t\tpacket->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tpacket->Finish();\n\t\tverify(inputs, packet->GetPacketResults());\n\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1000);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 8);\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 1);\n\t\tREQUIRE(packet->GetPacketFractionLost() == 0);\n\t\tREQUIRE(packet->GetLatestSequenceNumber() == 1007);\n\t\tREQUIRE(packet->GetLatestTimestamp() == 1000000007);\n\n\t\talignas(4) uint8_t buffer[1024];\n\t\tauto len = packet->Serialize(buffer);\n\n\t\tREQUIRE(packet->GetSize() == len);\n\n\t\tSECTION(\"parse serialized buffer\")\n\t\t{\n\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet2{\n\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t};\n\n\t\t\tREQUIRE(packet2);\n\t\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1000);\n\t\t\tREQUIRE(packet2->GetPacketStatusCount() == 8);\n\t\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 1);\n\t\t\tREQUIRE(packet2->GetPacketFractionLost() == 0);\n\n\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\tauto len2 = packet2->Serialize(buffer2);\n\n\t\t\tREQUIRE(len == len2);\n\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\tREQUIRE(packet2->GetSize() == len2);\n\t\t}\n\n\t\tauto latestWideSeqNumber = packet->GetLatestSequenceNumber();\n\t\tauto latestTimestamp     = packet->GetLatestTimestamp();\n\n\t\t/* clang-format off */\n\t\tstd::vector<TestFeedbackRtpTransportInput> inputs2 =\n\t\t{\n\t\t\t{ latestWideSeqNumber, latestTimestamp, RtcpMtu },\n\t\t\t{ 1008, 1000000008, RtcpMtu },\n\t\t\t{ 1009, 1000000009, RtcpMtu },\n\t\t\t{ 1010, 1000000010, RtcpMtu },\n\t\t\t{ 1011, 1000000010, RtcpMtu },\n\t\t\t{ 1012, 1000000010, RtcpMtu },\n\t\t\t{ 1013, 1000000014, RtcpMtu },\n\t\t\t{ 1014, 1000000014, RtcpMtu }\n\t\t};\n\t\t/* clang-format on */\n\n\t\tauto packet2 = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tpacket2->SetFeedbackPacketCount(2);\n\n\t\tfor (auto& input : inputs2)\n\t\t{\n\t\t\tif (std::addressof(input) == std::addressof(inputs2.front()))\n\t\t\t{\n\t\t\t\tpacket2->SetBase(input.sequenceNumber + 1, input.timestamp);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tpacket2->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize);\n\t\t\t}\n\t\t}\n\n\t\tpacket2->Finish();\n\t\tverify(inputs2, packet2->GetPacketResults());\n\n\t\tREQUIRE(packet2->GetBaseSequenceNumber() == 1008);\n\t\tREQUIRE(packet2->GetPacketStatusCount() == 7);\n\t\tREQUIRE(packet2->GetFeedbackPacketCount() == 2);\n\t\tREQUIRE(packet2->GetPacketFractionLost() == 0);\n\t\tREQUIRE(packet2->GetLatestSequenceNumber() == 1014);\n\t\tREQUIRE(packet2->GetLatestTimestamp() == 1000000014);\n\n\t\tlen = packet2->Serialize(buffer);\n\n\t\tREQUIRE(packet2->GetSize() == len);\n\n\t\tSECTION(\"parse serialized buffer\")\n\t\t{\n\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet3{\n\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len)\n\t\t\t};\n\n\t\t\tREQUIRE(packet3);\n\t\t\tREQUIRE(packet3->GetBaseSequenceNumber() == 1008);\n\t\t\tREQUIRE(packet3->GetPacketStatusCount() == 7);\n\t\t\tREQUIRE(packet3->GetFeedbackPacketCount() == 2);\n\t\t\tREQUIRE(packet3->GetPacketFractionLost() == 0);\n\n\t\t\talignas(4) uint8_t buffer2[1024];\n\t\t\tauto len2 = packet3->Serialize(buffer2);\n\n\t\t\tREQUIRE(len == len2);\n\t\t\tREQUIRE(std::memcmp(buffer, buffer2, len) == 0);\n\t\t\tREQUIRE(packet3->GetSize() == len2);\n\t\t}\n\t}\n\n\tSECTION(\"parse FeedbackRtpTransportPacket, one bit vector chunk\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t data[] =\n\t\t{\n\t\t\t0x8F, 0xCD, 0x00, 0x07,\n\t\t\t0xFA, 0x17, 0xFA, 0x17,\n\t\t\t0x09, 0xFA, 0xFF, 0x67,\n\t\t\t0x00, 0x27, 0x00, 0x0D,\n\t\t\t0x5F, 0xC2, 0xF1, 0x03,\n\t\t\t0xBF, 0x8E, 0x10, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x1C, 0x04, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data))\n\t\t};\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(packet->GetSize() == sizeof(data));\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 39);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 13);\n\t\tREQUIRE(packet->GetReferenceTime() == 6275825); // 0x5FC2F1 (signed 24 bits)\n\t\tREQUIRE(\n\t\t  packet->GetReferenceTimestamp() ==\n\t\t  RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod +\n\t\t    (static_cast<int64_t>(6275825) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick));\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 3);\n\n\t\tSECTION(\"serialize packet\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(len == sizeof(data));\n\t\t\tREQUIRE(std::memcmp(data, buffer, len) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parse FeedbackRtpTransportPacket with negative reference time\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t data[] =\n\t\t{\n\t\t\t0x8F, 0xCD, 0x00, 0x04,\n\t\t\t0xFA, 0x17, 0xFA, 0x17,\n\t\t\t0x09, 0xFA, 0xFF, 0x67,\n\t\t\t0x00, 0x27, 0x00, 0x00,\n\t\t\t0xFF, 0xFF, 0xFE, 0x01\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data))\n\t\t};\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(packet->GetSize() == sizeof(data));\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 39);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 0);\n\t\tREQUIRE(packet->GetReferenceTime() == -2); // 0xFFFFFE = -2 (signed 24 bits)\n\t\tREQUIRE(\n\t\t  packet->GetReferenceTimestamp() ==\n\t\t  RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod +\n\t\t    (static_cast<int64_t>(-2) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick));\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 1);\n\n\t\tSECTION(\"serialize packet\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(len == sizeof(data));\n\t\t\tREQUIRE(std::memcmp(data, buffer, len) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parse FeedbackRtpTransportPacket generated by Chrome\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t data[] =\n\t\t{\n\t\t\t0x8F, 0xCD, 0x00, 0x05,\n\t\t\t0xFA, 0x17, 0xFA, 0x17,\n\t\t\t0x39, 0xE9, 0x42, 0x38,\n\t\t\t0x00, 0x01, 0x00, 0x02,\n\t\t\t0xBD, 0x57, 0xAA, 0x00,\n\t\t\t0x20, 0x02, 0x8C, 0x44\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> packet{\n\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data))\n\t\t};\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(packet->GetSize() == sizeof(data));\n\t\tREQUIRE(packet->GetBaseSequenceNumber() == 1);\n\t\tREQUIRE(packet->GetPacketStatusCount() == 2);\n\t\tREQUIRE(packet->GetReferenceTime() == -4368470);\n\t\tREQUIRE(\n\t\t  packet->GetReferenceTimestamp() ==\n\t\t  RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod +\n\t\t    (static_cast<int64_t>(-4368470) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick));\n\n\t\tREQUIRE(packet->GetFeedbackPacketCount() == 0);\n\n\t\tSECTION(\"serialize packet\")\n\t\t{\n\t\t\talignas(4) uint8_t buffer[1024];\n\t\t\tauto len = packet->Serialize(buffer);\n\n\t\t\tREQUIRE(len == sizeof(data));\n\t\t\tREQUIRE(std::memcmp(data, buffer, len) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parse FeedbackRtpTransportPacket generated by Chrome with libwebrtc as a reference\")\n\t{\n\t\tusing FeedbackPacketsMeta = struct\n\t\t{\n\t\t\tint32_t baseTimeRaw;\n\t\t\tint64_t baseTimeMs;\n\t\t\tuint16_t baseSequence;\n\t\t\tsize_t packetStatusCount;\n\t\t\tstd::vector<int16_t> deltas;\n\t\t\tstd::vector<uint8_t> buffer;\n\t\t};\n\n\t\t// Metadata collected by parsing buffers with libwebrtc, buffers itself.\n\t\t// were generated by chrome in direction of mediasoup.\n\t\tconst std::vector<FeedbackPacketsMeta> feedbackPacketsMeta = {\n\t\t\t{ .baseTimeRaw       = 35504,\n       .baseTimeMs        = 1076014080,\n       .baseSequence      = 13,\n       .packetStatusCount = 1,\n       .deltas            = std::vector<int16_t>{ 57 },\n       .buffer = std::vector<uint8_t>{ 0xaf, 0xcd, 0x00, 0x05, 0xfa, 0x17, 0xfa, 0x17,\n\t\t\t                                  0x00, 0x00, 0x04, 0xd2, 0x00, 0x0d, 0x00, 0x01,\n\t\t\t                                  0x00, 0x8A, 0xB0, 0x00, 0x20, 0x01, 0xE4, 0x01 }     },\n\t\t\t{ .baseTimeRaw       = 35504,\n       .baseTimeMs        = 1076014080,\n       .baseSequence      = 14,\n       .packetStatusCount = 4,\n       .deltas            = std::vector<int16_t>{ 58, 2, 3, 55 },\n       .buffer = std::vector<uint8_t>{ 0xaf, 0xcd, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x1C, 0xB7,\n\t\t\t                                  0xDA, 0xF3, 0x00, 0x0E, 0x00, 0x04, 0x00, 0x8A, 0xB0, 0x01,\n\t\t\t                                  0x20, 0x04, 0xE8, 0x08, 0x0C, 0xDC, 0x00, 0x02 }     },\n\t\t\t{ .baseTimeRaw       = 35505,\n       .baseTimeMs        = 1076014144,\n       .baseSequence      = 18,\n       .packetStatusCount = 5,\n       .deltas            = std::vector<int16_t>{ 60, 6, 5, 9, 22 },\n       .buffer = std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x1C, 0xB7,\n\t\t\t                                  0xDA, 0xF3, 0x00, 0x12, 0x00, 0x05, 0x00, 0x8A, 0xB1, 0x02,\n\t\t\t                                  0x20, 0x05, 0xF0, 0x18, 0x14, 0x24, 0x58, 0x01 }     },\n\n\t\t\t{ .baseTimeRaw       = 617873,\n       .baseTimeMs        = 1113285696,\n       .baseSequence      = 2924,\n       .packetStatusCount = 22,\n       .deltas =\n\t\t\t    std::vector<int16_t>{ 3, 5, 5, 0, 10, 0, 0, 4, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 4 },\n       .buffer = std::vector<uint8_t>{ 0x8F, 0xCD, 0x00, 0x0A, 0xFA, 0x17, 0xFA, 0x17, 0x06,\n\t\t\t                                  0xF5, 0x11, 0x4C, 0x0B, 0x6C, 0x00, 0x16, 0x09, 0x6D,\n\t\t\t                                  0x91, 0xEE, 0x20, 0x16, 0x0C, 0x14, 0x14, 0x00, 0x28,\n\t\t\t                                  0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08,\n\t\t\t                                  0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x10 }     },\n\n\t\t\t{ .baseTimeRaw       = -4368470,\n       .baseTimeMs        = 794159744,\n       .baseSequence      = 1,\n       .packetStatusCount = 2,\n       .deltas            = std::vector<int16_t>{ 35, 17 },\n       .buffer = std::vector<uint8_t>{ 0x8F, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17,\n\t\t\t                                  0x39, 0xE9, 0x42, 0x38, 0x00, 0x01, 0x00, 0x02,\n\t\t\t                                  0xBD, 0x57, 0xAA, 0x00, 0x20, 0x02, 0x8C, 0x44 }     },\n\n\t\t\t{ .baseTimeRaw       = 818995,\n       .baseTimeMs        = 1126157504,\n       .baseSequence      = 930,\n       .packetStatusCount = 5,\n       .deltas            = std::vector<int16_t>{ 62, 18, 5, 6, 19 },\n       .buffer = std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E,\n\t\t\t                                  0x8E, 0x50, 0x03, 0xA2, 0x00, 0x05, 0x0C, 0x7F, 0x33, 0x9F,\n\t\t\t                                  0x20, 0x05, 0xF8, 0x48, 0x14, 0x18, 0x4C, 0x01 }     },\n\t\t\t{ .baseTimeRaw       = 818996,\n       .baseTimeMs        = 1126157568,\n       .baseSequence      = 921,\n       .packetStatusCount = 7,\n       .deltas            = std::vector<int16_t>{ 14, 5, 6, 6, 7, 14, 5 },\n       .buffer =\n\t\t\t    std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x33, 0xB0, 0x4A,\n\t\t\t                          0xE8, 0x03, 0x99, 0x00, 0x07, 0x0C, 0x7F, 0x34, 0x9F, 0x20, 0x07,\n\t\t\t                          0x38, 0x14, 0x18, 0x18, 0x1C, 0x38, 0x14, 0x00, 0x00, 0x03 } },\n\t\t\t{ .baseTimeRaw       = 818996,\n       .baseTimeMs        = 1126157568,\n       .baseSequence      = 935,\n       .packetStatusCount = 7,\n       .deltas            = std::vector<int16_t>{ 57, 0, 6, 5, 5, 24, 0 },\n       .buffer =\n\t\t\t    std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E, 0x8E,\n\t\t\t                          0x50, 0x03, 0xA7, 0x00, 0x07, 0x0C, 0x7F, 0x34, 0xA0, 0x20, 0x07,\n\t\t\t                          0xE4, 0x00, 0x18, 0x14, 0x14, 0x60, 0x00, 0x00, 0x00, 0x03 } },\n\t\t\t{ .baseTimeRaw       = 818996,\n       .baseTimeMs        = 1126157568,\n       .baseSequence      = 928,\n       .packetStatusCount = 5,\n       .deltas            = std::vector<int16_t>{ 63, 11, 21, 6, 0 },\n       .buffer = std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x33, 0xB0,\n\t\t\t                                  0x4A, 0xE8, 0x03, 0xA0, 0x00, 0x05, 0x0C, 0x7F, 0x34, 0xA0,\n\t\t\t                                  0x20, 0x05, 0xFC, 0x2C, 0x54, 0x18, 0x00, 0x01 }     },\n\t\t\t{ .baseTimeRaw       = 818997,\n       .baseTimeMs        = 1126157632,\n       .baseSequence      = 942,\n       .packetStatusCount = 6,\n       .deltas            = std::vector<int16_t>{ 39, 13, 9, 5, 4, 13 },\n       .buffer = std::vector<uint8_t>{ 0x8F, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E,\n\t\t\t                                  0x8E, 0x50, 0x03, 0xAE, 0x00, 0x06, 0x0C, 0x7F, 0x35, 0xA1,\n\t\t\t                                  0x20, 0x06, 0x9C, 0x34, 0x24, 0x14, 0x10, 0x34 }     },\n\t\t\t{ .baseTimeRaw       = 821523,\n       .baseTimeMs        = 1126319296,\n       .baseSequence      = 10,\n       .packetStatusCount = 7,\n       .deltas            = std::vector<int16_t>{ 25, 2, 2, 3, 1, 1, 3 },\n       .buffer =\n\t\t\t    std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x00, 0x00, 0x04,\n\t\t\t                          0xD2, 0x00, 0x0A, 0x00, 0x07, 0x0C, 0x89, 0x13, 0x00, 0x20, 0x07,\n\t\t\t                          0x64, 0x08, 0x08, 0x0C, 0x04, 0x04, 0x0C, 0x00, 0x00, 0x03 } },\n\t\t\t{ .baseTimeRaw       = 821524,\n       .baseTimeMs        = 1126319360,\n       .baseSequence      = 17,\n       .packetStatusCount = 2,\n       .deltas            = std::vector<int16_t>{ 44, 18 },\n       .buffer = std::vector<uint8_t>{ 0x8F, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17,\n\t\t\t                                  0x08, 0xEB, 0x06, 0xD7, 0x00, 0x11, 0x00, 0x02,\n\t\t\t                                  0x0C, 0x89, 0x14, 0x01, 0x20, 0x02, 0xB0, 0x48 }     },\n\t\t\t{ .baseTimeRaw       = 821524,\n       .baseTimeMs        = 1126319360,\n       .baseSequence      = 17,\n       .packetStatusCount = 1,\n       .deltas            = std::vector<int16_t>{ 62 },\n       .buffer = std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17,\n\t\t\t                                  0x20, 0x92, 0x5E, 0xB7, 0x00, 0x11, 0x00, 0x01,\n\t\t\t                                  0x0C, 0x89, 0x14, 0x00, 0x20, 0x01, 0xF8, 0x01 }     },\n\t\t\t{ .baseTimeRaw       = 821526,\n       .baseTimeMs        = 1126319488,\n       .baseSequence      = 19,\n       .packetStatusCount = 4,\n       .deltas            = std::vector<int16_t>{ 4, 0, 4, 0 },\n       .buffer = std::vector<uint8_t>{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x08, 0xEB,\n\t\t\t                                  0x06, 0xD7, 0x00, 0x13, 0x00, 0x04, 0x0C, 0x89, 0x16, 0x02,\n\t\t\t                                  0x20, 0x04, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02 }     }\n\t\t};\n\n\t\tfor (const auto& packetMeta : feedbackPacketsMeta)\n\t\t{\n\t\t\tauto buffer = packetMeta.buffer;\n\n\t\t\tstd::unique_ptr<RTC::RTCP::FeedbackRtpTransportPacket> feedback{\n\t\t\t\tRTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer.data(), buffer.size())\n\t\t\t};\n\n\t\t\tREQUIRE(feedback->GetReferenceTime() == packetMeta.baseTimeRaw);\n\t\t\tREQUIRE(feedback->GetReferenceTimestamp() == packetMeta.baseTimeMs);\n\t\t\tREQUIRE(feedback->GetBaseSequenceNumber() == packetMeta.baseSequence);\n\t\t\tREQUIRE(feedback->GetPacketStatusCount() == packetMeta.packetStatusCount);\n\n\t\t\tauto packetsResults = feedback->GetPacketResults();\n\n\t\t\tint deltasIt = 0;\n\t\t\tfor (const auto& delta : packetMeta.deltas)\n\t\t\t{\n\t\t\t\tauto resultDelta = packetsResults[deltasIt].delta;\n\t\t\t\tREQUIRE(static_cast<int16_t>(resultDelta / 4) == delta);\n\t\t\t\tdeltasIt++;\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"check GetBaseDelta() wraparound\")\n\t{\n\t\tstatic const auto MaxBaseTime = RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod -\n\t\t                                RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick;\n\n\t\tauto packet1 = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\t\tauto packet2 = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\t\tauto packet3 = std::make_unique<RTC::RTCP::FeedbackRtpTransportPacket>(senderSsrc, mediaSsrc);\n\n\t\tpacket1->SetReferenceTime(MaxBaseTime);\n\t\tpacket2->SetReferenceTime(MaxBaseTime + RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick);\n\t\tpacket3->SetReferenceTime(\n\t\t  MaxBaseTime + RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick +\n\t\t  RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick);\n\n\t\tREQUIRE(packet1->GetReferenceTime() == 16777215);\n\t\tREQUIRE(packet2->GetReferenceTime() == 0);\n\t\tREQUIRE(packet3->GetReferenceTime() == 1);\n\n\t\tREQUIRE(packet1->GetReferenceTimestamp() == 2147483584);\n\t\tREQUIRE(packet2->GetReferenceTimestamp() == 1073741824);\n\t\tREQUIRE(packet3->GetReferenceTimestamp() == 1073741888);\n\n\t\tREQUIRE(packet1->GetBaseDelta(packet1->GetReferenceTimestamp()) == 0);\n\t\tREQUIRE(packet2->GetBaseDelta(packet1->GetReferenceTimestamp()) == 64);\n\t\tREQUIRE(packet3->GetBaseDelta(packet2->GetReferenceTimestamp()) == 64);\n\t\tREQUIRE(packet3->GetBaseDelta(packet1->GetReferenceTimestamp()) == 128);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestPacket.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/FeedbackPs.hpp\"\n#include \"RTC/RTCP/FeedbackRtp.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"RTCP Packet\", \"[rtcp][packet]\")\n{\n\t// RTCP common header\n\t// Version:2, Padding:false, Count:0, Type:200(SR), Lengh:0\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x80, 0xc8, 0x00, 0x00\n\t};\n\t// clang-format on\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::Packet::CommonHeader) == 2);\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackRtpPacket::Header) == 4);\n\t\tREQUIRE(alignof(RTC::RTCP::FeedbackPsPacket::Header) == 4);\n\t}\n\n\tSECTION(\"a RTCP packet may only contain the RTCP common header\")\n\t{\n\t\tconst std::unique_ptr<RTC::RTCP::Packet> packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet);\n\t}\n\n\tSECTION(\"a too small RTCP packet should fail\")\n\t{\n\t\t// Provide a wrong packet length.\n\t\tconst size_t length = sizeof(buffer) - 1;\n\n\t\tconst std::unique_ptr<RTC::RTCP::Packet> packet{ RTC::RTCP::Packet::Parse(buffer, length) };\n\n\t\tREQUIRE(!packet);\n\t}\n\n\tSECTION(\"a RTCP packet with incorrect version should fail\")\n\t{\n\t\t// Set an incorrect version value (0).\n\t\tbuffer[0] &= 0b00111111;\n\n\t\tconst std::unique_ptr<RTC::RTCP::Packet> packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tREQUIRE(!packet);\n\t}\n\n\tSECTION(\"a RTCP packet with incorrect length should fail\")\n\t{\n\t\t// Set the packet length to zero.\n\t\tbuffer[3] = 1;\n\n\t\tconst std::unique_ptr<RTC::RTCP::Packet> packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tREQUIRE(!packet);\n\t}\n\n\tSECTION(\"a RTCP packet with unknown type should fail\")\n\t{\n\t\t// Set and unknown packet type (0).\n\t\tbuffer[1] = 0;\n\n\t\tconst std::unique_ptr<RTC::RTCP::Packet> packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tREQUIRE(!packet);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestReceiverReport.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/ReceiverReport.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"RTCP ReceiverReport\", \"[rtcp][receiver-report]\")\n{\n\t// RTCP Receiver Report Packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x81, 0xc9, 0x00, 0x07, // Type: 201 (Receiver Report), Count: 1, Length: 7\n\t\t0x5d, 0x93, 0x15, 0x34, // Sender SSRC: 0x5d931534\n\t\t// Receiver Report\n\t\t0x01, 0x93, 0x2d, 0xb4, // SSRC. 0x01932db4\n\t\t0x00, 0xFF, 0xFF, 0xFF, // Fraction lost: 0, Total lost: -1\n\t\t0x00, 0x00, 0x00, 0x00, // Extended highest sequence number: 0\n\t\t0x00, 0x00, 0x00, 0x00, // Jitter: 0\n\t\t0x00, 0x00, 0x00, 0x00, // Last SR: 0\n\t\t0x00, 0x00, 0x00, 0x05  // DLSR: 0\n\t};\n\t// clang-format on\n\n\t// Receiver Report buffer start point.\n\tconst uint8_t* rrBuffer =\n\t  buffer + RTC::RTCP::Packet::CommonHeaderSize + sizeof(uint32_t); // Sender SSRC.\n\n\tconst uint32_t ssrc{ 0x01932db4 };\n\tconst uint8_t fractionLost{ 0 };\n\tconst int32_t totalLost{ -1 };\n\tconst uint32_t lastSeq{ 0 };\n\tconst uint32_t jitter{ 0 };\n\tconst uint32_t lastSenderReport{ 0 };\n\tconst uint32_t delaySinceLastSenderReport{ 5 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::ReceiverReport* report)\n\t{\n\t\tREQUIRE(report->GetSsrc() == ssrc);\n\t\tREQUIRE(report->GetFractionLost() == fractionLost);\n\t\tREQUIRE(report->GetTotalLost() == totalLost);\n\t\tREQUIRE(report->GetLastSeq() == lastSeq);\n\t\tREQUIRE(report->GetJitter() == jitter);\n\t\tREQUIRE(report->GetLastSenderReport() == lastSenderReport);\n\t\tREQUIRE(report->GetDelaySinceLastSenderReport() == delaySinceLastSenderReport);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::ReceiverReport::Header) == 4);\n\t}\n\n\tSECTION(\"parse RR packet with a single report\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::ReceiverReportPacket> packet{ RTC::RTCP::ReceiverReportPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tREQUIRE(packet->GetCount() == 1);\n\n\t\tauto* report = *(packet->Begin());\n\n\t\tverify(report);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tstd::unique_ptr<RTC::RTCP::ReceiverReportPacket> packet2{\n\t\t\t\tRTC::RTCP::ReceiverReportPacket::Parse(serialized, sizeof(buffer))\n\t\t\t};\n\n\t\t\tREQUIRE(packet2->GetType() == RTC::RTCP::Type::RR);\n\t\t\tREQUIRE(packet2->GetCount() == 1);\n\t\t\tREQUIRE(packet2->GetSize() == 32);\n\n\t\t\tauto* buf = reinterpret_cast<RTC::RTCP::Packet::CommonHeader*>(buffer);\n\n\t\t\tREQUIRE(ntohs(buf->length) == 7);\n\n\t\t\treport = *(packet2->Begin());\n\n\t\t\tverify(report);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"parse RR\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::ReceiverReport> report{ RTC::RTCP::ReceiverReport::Parse(\n\t\t\trrBuffer, RTC::RTCP::ReceiverReport::HeaderSize) };\n\n\t\tREQUIRE(report);\n\n\t\tverify(report.get());\n\t}\n\n\tSECTION(\"create RR packet with more than 31 reports\")\n\t{\n\t\tconst size_t count = 33;\n\n\t\tRTC::RTCP::ReceiverReportPacket packet;\n\n\t\tfor (size_t i = 1; i <= count; i++)\n\t\t{\n\t\t\t// Create report and add to packet.\n\t\t\tauto* report = new RTC::RTCP::ReceiverReport();\n\n\t\t\treport->SetSsrc(i);\n\t\t\treport->SetFractionLost(i);\n\t\t\treport->SetTotalLost(i);\n\t\t\treport->SetLastSeq(i);\n\t\t\treport->SetJitter(i);\n\t\t\treport->SetLastSenderReport(i);\n\t\t\treport->SetDelaySinceLastSenderReport(i);\n\n\t\t\tpacket.AddReport(report);\n\t\t}\n\n\t\tREQUIRE(packet.GetCount() == count);\n\n\t\talignas(4) uint8_t buffer[1500] = { 0 };\n\n\t\t// Serialization must contain 2 RR packets since report count exceeds 31.\n\t\tpacket.Serialize(buffer);\n\n\t\tstd::unique_ptr<RTC::RTCP::ReceiverReportPacket> packet2{\n\t\t\tstatic_cast<RTC::RTCP::ReceiverReportPacket*>(RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)))\n\t\t};\n\n\t\tREQUIRE(packet2 != nullptr);\n\t\tREQUIRE(packet2->GetCount() == 31);\n\n\t\tauto reportIt = packet2->Begin();\n\n\t\tfor (size_t i = 1; i <= 31; ++i, ++reportIt)\n\t\t{\n\t\t\tauto* report = *reportIt;\n\n\t\t\tREQUIRE(report->GetSsrc() == i);\n\t\t\tREQUIRE(report->GetFractionLost() == i);\n\t\t\tREQUIRE(report->GetTotalLost() == static_cast<int32_t>(i));\n\t\t\tREQUIRE(report->GetLastSeq() == i);\n\t\t\tREQUIRE(report->GetJitter() == i);\n\t\t\tREQUIRE(report->GetLastSenderReport() == i);\n\t\t\tREQUIRE(report->GetDelaySinceLastSenderReport() == i);\n\t\t}\n\n\t\tauto* packet3 = static_cast<RTC::RTCP::ReceiverReportPacket*>(packet2->GetNext());\n\n\t\tREQUIRE(packet3 != nullptr);\n\t\tREQUIRE(packet3->GetCount() == 2);\n\n\t\treportIt = packet3->Begin();\n\n\t\tfor (size_t i = 1; i <= 2; ++i, ++reportIt)\n\t\t{\n\t\t\tauto* report = *reportIt;\n\n\t\t\tREQUIRE(report->GetSsrc() == 31 + i);\n\t\t\tREQUIRE(report->GetFractionLost() == 31 + i);\n\t\t\tREQUIRE(report->GetTotalLost() == static_cast<int32_t>(31 + i));\n\t\t\tREQUIRE(report->GetLastSeq() == 31 + i);\n\t\t\tREQUIRE(report->GetJitter() == 31 + i);\n\t\t\tREQUIRE(report->GetLastSenderReport() == 31 + i);\n\t\t\tREQUIRE(report->GetDelaySinceLastSenderReport() == 31 + i);\n\t\t}\n\n\t\tdelete packet3;\n\t}\n\n\tSECTION(\"create RR report\")\n\t{\n\t\t// Create local report and check content.\n\t\tRTC::RTCP::ReceiverReport report1;\n\n\t\treport1.SetSsrc(ssrc);\n\t\treport1.SetFractionLost(fractionLost);\n\t\treport1.SetTotalLost(totalLost);\n\t\treport1.SetLastSeq(lastSeq);\n\t\treport1.SetJitter(jitter);\n\t\treport1.SetLastSenderReport(lastSenderReport);\n\t\treport1.SetDelaySinceLastSenderReport(delaySinceLastSenderReport);\n\n\t\tverify(&report1);\n\n\t\tSECTION(\"create a report out of the existing one\")\n\t\t{\n\t\t\tRTC::RTCP::ReceiverReport report2(&report1);\n\n\t\t\tverify(&report2);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestSdes.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/Packet.hpp\"\n#include \"RTC/RTCP/Sdes.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n#include <string>\n\nSCENARIO(\"RTCP SDES\", \"[rtcp][sdes]\")\n{\n\t// RTCP Sdes Packet.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer1[] =\n\t{\n\t\t0x81, 0xca, 0x00, 0x06, // Type: 202 (SDES), Count: 1, Length: 6\n\t\t0x9f, 0x65, 0xe7, 0x42, // SSRC: 0x9f65e742\n\t\t// Chunk 1\n\t\t0x01, 0x10, 0x74, 0x37, // Item Type: 1 (CNAME), Length: 16, Value: t7mkYnCm46OcINy/\n\t\t0x6d, 0x6b, 0x59, 0x6e,\n\t\t0x43, 0x6d, 0x34, 0x36,\n\t\t0x4f, 0x63, 0x49, 0x4e,\n\t\t0x79, 0x2f, 0x00, 0x00  // 2 null octets\n\t};\n\t// clang-format on\n\n\t// First chunk (chunk 1).\n\tconst uint32_t ssrc1{ 0x9f65e742 };\n\t// First item (item 1).\n\tconst RTC::RTCP::SdesItem::Type item1Type{ RTC::RTCP::SdesItem::Type::CNAME };\n\tconst std::string item1Value{ \"t7mkYnCm46OcINy/\" };\n\tconst size_t item1Length{ 16u };\n\n\t// clang-format off\n\talignas(4) uint8_t buffer2[] =\n\t{\n\t\t0xa2, 0xca, 0x00, 0x0d, // Padding, Type: 202 (SDES), Count: 2, Length: 13\n\t\t// Chunk 2\n\t\t0x00, 0x00, 0x04, 0xd2, // SSRC: 1234\n\t\t0x01, 0x06, 0x71, 0x77, // Item Type: 1 (CNAME), Length: 6, Text: \"qwerty\"\n\t\t0x65, 0x72, 0x74, 0x79,\n\t\t0x06, 0x06, 0x69, 0xc3, // Item Type: 6 (TOOL), Length: 6, Text: \"iñaki\"\n\t\t0xb1, 0x61, 0x6b, 0x69,\n\t\t0x00, 0x00, 0x00, 0x00, // 4 null octets\n\t\t// Chunk 3\n\t\t0x00, 0x00, 0x16, 0x2e, // SSRC: 5678\n\t\t0x05, 0x11, 0x73, 0x6f, // Item Type: 5 (LOC), Length: 17, Text: \"somewhere œæ€\"\n\t\t0x6d, 0x65, 0x77, 0x68,\n\t\t0x65, 0x72, 0x65, 0x20,\n\t\t0xc5, 0x93, 0xc3, 0xa6,\n\t\t0xe2, 0x82, 0xac, 0x00,  // 1 null octet\n\t\t0x00, 0x00, 0x00, 0x00  // Pading (4 bytes)\n\t};\n\t// clang-format on\n\n\t// First chunk (chunk 2).\n\tconst uint32_t ssrc2{ 1234 };\n\t// First item (item 2).\n\tconst RTC::RTCP::SdesItem::Type item2Type{ RTC::RTCP::SdesItem::Type::CNAME };\n\tconst std::string item2Value{ \"qwerty\" };\n\tconst size_t item2Length{ 6u };\n\t// First item (item 3).\n\tconst RTC::RTCP::SdesItem::Type item3Type{ RTC::RTCP::SdesItem::Type::TOOL };\n\tconst std::string item3Value{ \"iñaki\" };\n\tconst size_t item3Length{ 6u };\n\n\t// Second chunk (chunk 3).\n\tconst uint32_t ssrc3{ 5678 };\n\t// First item (item 4).\n\tconst RTC::RTCP::SdesItem::Type item4Type{ RTC::RTCP::SdesItem::Type::LOC };\n\tconst std::string item4Value{ \"somewhere œæ€\" };\n\tconst size_t item4Length{ 17u };\n\n\t// clang-format off\n\talignas(4) uint8_t buffer3[] =\n\t{\n\t\t0x81, 0xca, 0x00, 0x03, // Type: 202 (SDES), Count: 1, Length: 3\n\t\t// Chunk\n\t\t0x11, 0x22, 0x33, 0x44, // SSRC: 0x11223344\n\t\t0x05, 0x02, 0x61, 0x62, // Item Type: 5 (LOC), Length: 2, Text: \"ab\"\n\t\t0x00, 0x00, 0x00, 0x00  // 4 null octets\n\t};\n\t// clang-format on\n\n\t// First chunk (chunk 4).\n\tconst uint32_t ssrc4{ 0x11223344 };\n\t// First item (item 5).\n\tconst RTC::RTCP::SdesItem::Type item5Type{ RTC::RTCP::SdesItem::Type::LOC };\n\tconst std::string item5Value{ \"ab\" };\n\tconst size_t item5Length{ 2u };\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::SdesItem::Header) == 1);\n\t}\n\n\tSECTION(\"parse packet 1\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet{ RTC::RTCP::SdesPacket::Parse(\n\t\t\tbuffer1, sizeof(buffer1)) };\n\t\tauto* header = reinterpret_cast<RTC::RTCP::Packet::CommonHeader*>(buffer1);\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(ntohs(header->length) == 6);\n\t\tREQUIRE(packet->GetSize() == 28);\n\t\tREQUIRE(packet->GetCount() == 1);\n\n\t\tsize_t chunkIdx{ 0u };\n\n\t\tfor (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx)\n\t\t{\n\t\t\tauto* chunk = *it;\n\n\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\tswitch (chunkIdx)\n\t\t\t{\n\t\t\t\t/* First chunk (chunk 1). */\n\t\t\t\tcase 0:\n\t\t\t\t{\n\t\t\t\t\t// Chunk size must be 24 bytes (including 4 null octets).\n\t\t\t\t\tREQUIRE(chunk->GetSize() == 24);\n\t\t\t\t\tREQUIRE(chunk->GetSsrc() == ssrc1);\n\n\t\t\t\t\tsize_t itemIdx{ 0u };\n\n\t\t\t\t\tfor (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* item = *it2;\n\n\t\t\t\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\t\t\t\tswitch (itemIdx)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* First item (item 1). */\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(item->GetType() == item1Type);\n\t\t\t\t\t\t\t\tREQUIRE(item->GetLength() == item1Length);\n\t\t\t\t\t\t\t\tREQUIRE(std::string(item->GetValue(), item1Length) == item1Value);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// There is 1 item.\n\t\t\t\t\tREQUIRE(itemIdx == 1);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\t// There is 1 chunk.\n\t\tREQUIRE(chunkIdx == 1);\n\n\t\tSECTION(\"serialize SdesChunk instance\")\n\t\t{\n\t\t\tauto it                     = packet->Begin();\n\t\t\tauto* chunk1                = *it;\n\t\t\tconst uint8_t* chunk1Buffer = buffer1 + RTC::RTCP::Packet::CommonHeaderSize;\n\n\t\t\t// NOTE: Length of first chunk (including null octets) is 24.\n\t\t\talignas(4) uint8_t serialized1[24] = { 0 };\n\n\t\t\tchunk1->Serialize(serialized1);\n\n\t\t\tREQUIRE(std::memcmp(chunk1Buffer, serialized1, 24) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parse packet 2\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet{ RTC::RTCP::SdesPacket::Parse(\n\t\t\tbuffer2, sizeof(buffer2)) };\n\t\tauto* header = reinterpret_cast<RTC::RTCP::Packet::CommonHeader*>(buffer2);\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(ntohs(header->length) == 13);\n\t\t// Despite total buffer size is 56 bytes, our GetSize() method doesn't not\n\t\t// consider RTCP padding (4 bytes in this case).\n\t\tREQUIRE(packet->GetSize() == 52);\n\t\tREQUIRE(packet->GetCount() == 2);\n\n\t\tsize_t chunkIdx{ 0u };\n\n\t\tfor (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx)\n\t\t{\n\t\t\tauto* chunk = *it;\n\n\t\t\tswitch (chunkIdx)\n\t\t\t{\n\t\t\t\t/* First chunk (chunk 2). */\n\t\t\t\tcase 0:\n\t\t\t\t{\n\t\t\t\t\t// Chunk size must be 24 bytes (including 4 null octets).\n\t\t\t\t\tREQUIRE(chunk->GetSize() == 24);\n\t\t\t\t\tREQUIRE(chunk->GetSsrc() == ssrc2);\n\n\t\t\t\t\tsize_t itemIdx{ 0u };\n\n\t\t\t\t\tfor (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* item = *it2;\n\n\t\t\t\t\t\tswitch (itemIdx)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* First item (item 2). */\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(item->GetType() == item2Type);\n\t\t\t\t\t\t\t\tREQUIRE(item->GetLength() == item2Length);\n\t\t\t\t\t\t\t\tREQUIRE(std::string(item->GetValue(), item2Length) == item2Value);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/* Second item (item 3). */\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(item->GetType() == item3Type);\n\t\t\t\t\t\t\t\tREQUIRE(item->GetLength() == item3Length);\n\t\t\t\t\t\t\t\tREQUIRE(std::string(item->GetValue(), item3Length) == item3Value);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// There are 2 items.\n\t\t\t\t\tREQUIRE(itemIdx == 2);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t/* Second chunk (chunk 3). */\n\t\t\t\tcase 1:\n\t\t\t\t{\n\t\t\t\t\t// Chunk size must be 24 bytes (including 1 null octet).\n\t\t\t\t\tREQUIRE(chunk->GetSize() == 24);\n\t\t\t\t\tREQUIRE(chunk->GetSsrc() == ssrc3);\n\n\t\t\t\t\tsize_t itemIdx{ 0u };\n\n\t\t\t\t\tfor (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* item = *it2;\n\n\t\t\t\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\t\t\t\tswitch (itemIdx)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* First item (item 4). */\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(item->GetType() == item4Type);\n\t\t\t\t\t\t\t\tREQUIRE(item->GetLength() == item4Length);\n\t\t\t\t\t\t\t\tREQUIRE(std::string(item->GetValue(), item4Length) == item4Value);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// There is 1 item.\n\t\t\t\t\tREQUIRE(itemIdx == 1);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\t// There are 2 chunks.\n\t\tREQUIRE(chunkIdx == 2);\n\n\t\tSECTION(\"serialize SdesChunk instances\")\n\t\t{\n\t\t\tauto it                     = packet->Begin();\n\t\t\tauto* chunk1                = *it;\n\t\t\tconst uint8_t* chunk1Buffer = buffer2 + RTC::RTCP::Packet::CommonHeaderSize;\n\n\t\t\t// NOTE: Length of first chunk (including null octets) is 24.\n\t\t\talignas(4) uint8_t serialized1[24] = { 0 };\n\n\t\t\tchunk1->Serialize(serialized1);\n\n\t\t\tREQUIRE(std::memcmp(chunk1Buffer, serialized1, 24) == 0);\n\n\t\t\tauto* chunk2                = *(++it);\n\t\t\tconst uint8_t* chunk2Buffer = buffer2 + RTC::RTCP::Packet::CommonHeaderSize + 24;\n\n\t\t\t// NOTE: Length of second chunk (including null octets) is 24.\n\t\t\talignas(4) uint8_t serialized2[24] = { 0 };\n\n\t\t\tchunk2->Serialize(serialized2);\n\n\t\t\tREQUIRE(std::memcmp(chunk2Buffer, serialized2, 24) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parse packet 3\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet{ RTC::RTCP::SdesPacket::Parse(\n\t\t\tbuffer3, sizeof(buffer3)) };\n\t\tauto* header = reinterpret_cast<RTC::RTCP::Packet::CommonHeader*>(buffer3);\n\n\t\tREQUIRE(packet);\n\t\tREQUIRE(ntohs(header->length) == 3);\n\t\tREQUIRE(packet->GetSize() == 16);\n\t\tREQUIRE(packet->GetCount() == 1);\n\n\t\tsize_t chunkIdx{ 0u };\n\n\t\tfor (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx)\n\t\t{\n\t\t\tauto* chunk = *it;\n\n\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\tswitch (chunkIdx)\n\t\t\t{\n\t\t\t\t/* First chunk (chunk 4). */\n\t\t\t\tcase 0:\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(chunk->GetSize() == 12);\n\t\t\t\t\tREQUIRE(chunk->GetSsrc() == ssrc4);\n\n\t\t\t\t\tsize_t itemIdx{ 0u };\n\n\t\t\t\t\tfor (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* item = *it2;\n\n\t\t\t\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\t\t\t\tswitch (itemIdx)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t/* First item (item 5). */\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(item->GetType() == item5Type);\n\t\t\t\t\t\t\t\tREQUIRE(item->GetLength() == item5Length);\n\t\t\t\t\t\t\t\tREQUIRE(std::string(item->GetValue(), item5Length) == item5Value);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// There is 1 item.\n\t\t\t\t\tREQUIRE(itemIdx == 1);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\t// There is 1 chunk.\n\t\tREQUIRE(chunkIdx == 1);\n\n\t\tSECTION(\"serialize SdesChunk instance\")\n\t\t{\n\t\t\tauto it                     = packet->Begin();\n\t\t\tauto* chunk1                = *it;\n\t\t\tconst uint8_t* chunk1Buffer = buffer3 + RTC::RTCP::Packet::CommonHeaderSize;\n\n\t\t\t// NOTE: Length of first chunk (including null octets) is 12.\n\t\t\talignas(4) uint8_t serialized1[12] = { 0 };\n\n\t\t\tchunk1->Serialize(serialized1);\n\n\t\t\tREQUIRE(std::memcmp(chunk1Buffer, serialized1, 12) == 0);\n\t\t}\n\t}\n\n\tSECTION(\"parsing a packet with missing null octects fails\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x81, 0xca, 0x00, 0x02, // Type: 202 (SDES), Count: 1, Length: 2\n\t\t\t// Chunk\n\t\t\t0x11, 0x22, 0x33, 0x44, // SSRC: 0x11223344\n\t\t\t0x08, 0x02, 0x61, 0x62  // Item Type: 8 (PRIV), Length: 2, Text: \"ab\"\n\t\t};\n\n\t\tconst auto* packet = RTC::RTCP::SdesPacket::Parse(buffer, sizeof(buffer));\n\n\t\tREQUIRE(!packet);\n\t}\n\n\tSECTION(\"create SDES packet with 31 chunks\")\n\t{\n\t\tconst size_t count = 31;\n\n\t\tRTC::RTCP::SdesPacket packet;\n\t\t// Create a chunk and an item to obtain their size.\n\t\tauto chunk = std::make_unique<RTC::RTCP::SdesChunk>(1234 /*ssrc*/);\n\t\tauto* item1 =\n\t\t  new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str());\n\n\t\tchunk->AddItem(item1);\n\n\t\tauto chunkSize = chunk->GetSize();\n\n\t\tfor (size_t i{ 1 }; i <= count; ++i)\n\t\t{\n\t\t\t// Create chunk and add to packet.\n\t\t\tauto* chunk = new RTC::RTCP::SdesChunk(i /*ssrc*/);\n\n\t\t\tauto* item1 =\n\t\t\t  new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str());\n\n\t\t\tchunk->AddItem(item1);\n\n\t\t\tpacket.AddChunk(chunk);\n\t\t}\n\n\t\tREQUIRE(packet.GetCount() == count);\n\t\tREQUIRE(packet.GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (count * chunkSize));\n\n\t\talignas(4) uint8_t buffer1[1500] = { 0 };\n\n\t\t// Serialization must contain 1 SDES packet since report count doesn't\n\t\t// exceed 31.\n\t\tpacket.Serialize(buffer1);\n\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet2{static_cast<RTC::RTCP::SdesPacket*>(RTC::RTCP::Packet::Parse(buffer1, sizeof(buffer1)))};\n\n\t\tREQUIRE(packet2 != nullptr);\n\t\tREQUIRE(packet2->GetCount() == count);\n\t\tREQUIRE(packet2->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (count * chunkSize));\n\n\t\tauto reportIt = packet2->Begin();\n\n\t\tfor (size_t i{ 1 }; i <= 31; ++i, ++reportIt)\n\t\t{\n\t\t\tauto* chunk = *reportIt;\n\n\t\t\tREQUIRE(chunk->GetSsrc() == i);\n\n\t\t\tauto* item = *(chunk->Begin());\n\n\t\t\tREQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME);\n\t\t\tREQUIRE(item->GetSize() == 2 + item1Value.size());\n\t\t\tREQUIRE(std::string(item->GetValue()) == item1Value);\n\t\t}\n\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet3{static_cast<RTC::RTCP::SdesPacket*>(packet2->GetNext())};\n\n\t\tREQUIRE(packet3 == nullptr);\n\n\t}\n\n\tSECTION(\"create SDES packet with more than 31 chunks\")\n\t{\n\t\tconst size_t count = 33;\n\n\t\tRTC::RTCP::SdesPacket packet;\n\t\t// Create a chunk and an item to obtain their size.\n\t\tauto chunk = std::make_unique<RTC::RTCP::SdesChunk>(1234 /*ssrc*/);\n\t\tauto* item1 =\n\t\t  new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str());\n\n\t\tchunk->AddItem(item1);\n\n\t\tauto chunkSize = chunk->GetSize();\n\n\t\tfor (size_t i{ 1 }; i <= count; ++i)\n\t\t{\n\t\t\t// Create chunk and add to packet.\n\t\t\tauto* chunk = new RTC::RTCP::SdesChunk(i /*ssrc*/);\n\n\t\t\tauto* item1 =\n\t\t\t  new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str());\n\n\t\t\tchunk->AddItem(item1);\n\n\t\t\tpacket.AddChunk(chunk);\n\t\t}\n\n\t\tREQUIRE(packet.GetCount() == count);\n\t\tREQUIRE(packet.GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (31 * chunkSize) + RTC::RTCP::Packet::CommonHeaderSize + ((count - 31) * chunkSize));\n\n\t\talignas(4) uint8_t buffer1[1500] = { 0 };\n\n\t\t// Serialization must contain 2 SDES packets since report count exceeds 31.\n\t\tpacket.Serialize(buffer1);\n\n\t\tstd::unique_ptr<RTC::RTCP::SdesPacket> packet2 {static_cast<RTC::RTCP::SdesPacket*>(RTC::RTCP::Packet::Parse(buffer1, sizeof(buffer1)))};\n\n\t\tREQUIRE(packet2 != nullptr);\n\t\tREQUIRE(packet2->GetCount() == 31);\n\t\tREQUIRE(packet2->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (31 * chunkSize));\n\n\t\tauto reportIt = packet2->Begin();\n\n\t\tfor (size_t i{ 1 }; i <= 31; ++i, ++reportIt)\n\t\t{\n\t\t\tauto* chunk = *reportIt;\n\n\t\t\tREQUIRE(chunk->GetSsrc() == i);\n\n\t\t\tauto* item = *(chunk->Begin());\n\n\t\t\tREQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME);\n\t\t\tREQUIRE(item->GetSize() == 2 + item1Value.size());\n\t\t\tREQUIRE(std::string(item->GetValue()) == item1Value);\n\t\t}\n\n\t\tauto* packet3 = static_cast<RTC::RTCP::SdesPacket*>(packet2->GetNext());\n\n\t\tREQUIRE(packet3 != nullptr);\n\t\tREQUIRE(packet3->GetCount() == count - 31);\n\t\tREQUIRE(packet3->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + ((count - 31) * chunkSize));\n\n\t\treportIt = packet3->Begin();\n\n\t\tfor (size_t i{ 1 }; i <= 2; ++i, ++reportIt)\n\t\t{\n\t\t\tauto* chunk = *reportIt;\n\n\t\t\tREQUIRE(chunk->GetSsrc() == 31 + i);\n\n\t\t\tauto* item = *(chunk->Begin());\n\n\t\t\tREQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME);\n\t\t\tREQUIRE(item->GetSize() == 2 + item1Value.size());\n\t\t\tREQUIRE(std::string(item->GetValue()) == item1Value);\n\t\t}\n\n\t\tdelete packet3;\n\t}\n\n\tSECTION(\"create SdesChunk\")\n\t{\n\t\tauto* item = new RTC::RTCP::SdesItem(item1Type, item1Length, item1Value.c_str());\n\n\t\t// Create sdes chunk.\n\t\tRTC::RTCP::SdesChunk chunk(ssrc1);\n\n\t\tchunk.AddItem(item);\n\n\t\tREQUIRE(chunk.GetSsrc() == ssrc1);\n\n\t\tconst RTC::RTCP::SdesItem* item1 = *(chunk.Begin());\n\n\t\tREQUIRE(item1->GetType() == item1Type);\n\t\tREQUIRE(item1->GetLength() == item1Length);\n\t\tREQUIRE(std::string(item1->GetValue(), item1Length) == item1Value);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestSenderReport.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/SenderReport.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"RTCP SenderReport\", \"[rtcp][sender-report]\")\n{\n\t// RTCP Packet. Sender Report and Receiver Report.\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x80, 0xc8, 0x00, 0x06, // Type: 200 (Sender Report), Count: 0, Length: 6\n\t\t0x5d, 0x93, 0x15, 0x34, // SSRC: 0x5d931534\n\t\t0xdd, 0x3a, 0xc1, 0xb4, // NTP Sec: 3711615412\n\t\t0x76, 0x54, 0x71, 0x71, // NTP Frac: 1985245553\n\t\t0x00, 0x08, 0xcf, 0x00, // RTP timestamp: 577280\n\t\t0x00, 0x00, 0x0e, 0x18, // Packet count: 3608\n\t\t0x00, 0x08, 0xcf, 0x00  // Octet count: 577280\n\t};\n\t// clang-format on\n\n\t// Sender Report buffer start point.\n\tconst uint8_t* srBuffer = buffer + RTC::RTCP::Packet::CommonHeaderSize;\n\n\t// SR values.\n\tconst uint32_t ssrc{ 0x5d931534 };\n\tconst uint32_t ntpSec{ 3711615412 };\n\tconst uint32_t ntpFrac{ 1985245553 };\n\tconst uint32_t rtpTs{ 577280 };\n\tconst uint32_t packetCount{ 3608 };\n\tconst uint32_t octetCount{ 577280 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto verify = [](RTC::RTCP::SenderReport* report)\n\t{\n\t\tREQUIRE(report->GetSsrc() == ssrc);\n\t\tREQUIRE(report->GetNtpSec() == ntpSec);\n\t\tREQUIRE(report->GetNtpFrac() == ntpFrac);\n\t\tREQUIRE(report->GetRtpTs() == rtpTs);\n\t\tREQUIRE(report->GetPacketCount() == packetCount);\n\t\tREQUIRE(report->GetOctetCount() == octetCount);\n\t};\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::SenderReport::Header) == 4);\n\t}\n\n\tSECTION(\"parse SR packet\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::SenderReportPacket> packet{ RTC::RTCP::SenderReportPacket::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tauto* report = *(packet->Begin());\n\n\t\tverify(report);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[sizeof(buffer)] = { 0 };\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized packet with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"parse SR\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::SenderReport> report{ RTC::RTCP::SenderReport::Parse(\n\t\t\tsrBuffer, RTC::RTCP::SenderReport::HeaderSize) };\n\n\t\tREQUIRE(report);\n\n\t\tverify(report.get());\n\n\t\tSECTION(\"serialize SenderReport instance\")\n\t\t{\n\t\t\talignas(4) uint8_t serialized[RTC::RTCP::SenderReport::HeaderSize] = { 0 };\n\n\t\t\treport->Serialize(serialized);\n\n\t\t\tSECTION(\"compare serialized SenderReport with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(srBuffer, serialized, RTC::RTCP::SenderReport::HeaderSize) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"create SR packet multiple reports\")\n\t{\n\t\tconst size_t count = 3;\n\n\t\tRTC::RTCP::SenderReportPacket packet;\n\n\t\tfor (size_t i = 1; i <= count; ++i)\n\t\t{\n\t\t\t// Create report and add to packet.\n\t\t\tauto* report = new RTC::RTCP::SenderReport();\n\n\t\t\treport->SetSsrc(i);\n\t\t\treport->SetNtpSec(i);\n\t\t\treport->SetNtpFrac(i);\n\t\t\treport->SetRtpTs(i);\n\t\t\treport->SetPacketCount(i);\n\t\t\treport->SetOctetCount(i);\n\n\t\t\tpacket.AddReport(report);\n\t\t}\n\n\t\talignas(4) uint8_t buffer[1500] = { 0 };\n\n\t\t// Serialization must contain 3 SR packets.\n\t\tpacket.Serialize(buffer);\n\n\t\t// NOTE: clang-tidy says that this could be `const SenderReport* const reports`\n\t\t// but that's absolutely wrong!\n\t\t// NOLINTNEXTLINE(misc-const-correctness)\n\t\tconst RTC::RTCP::SenderReport* reports[count]{ nullptr };\n\n\t\tstd::unique_ptr<RTC::RTCP::SenderReportPacket> packet2{\n\t\t\tstatic_cast<RTC::RTCP::SenderReportPacket*>(RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)))\n\t\t};\n\n\t\tREQUIRE(packet2 != nullptr);\n\n\t\treports[0] = *(packet2->Begin());\n\n\t\tauto* packet3 = static_cast<RTC::RTCP::SenderReportPacket*>(packet2->GetNext());\n\n\t\tREQUIRE(packet3 != nullptr);\n\n\t\treports[1] = *(packet3->Begin());\n\n\t\tauto* packet4 = static_cast<RTC::RTCP::SenderReportPacket*>(packet3->GetNext());\n\n\t\tREQUIRE(packet4 != nullptr);\n\n\t\treports[2] = *(packet4->Begin());\n\n\t\tfor (size_t i = 1; i <= count; ++i)\n\t\t{\n\t\t\tconst auto* report = reports[i - 1];\n\n\t\t\tREQUIRE(report != nullptr);\n\t\t\tREQUIRE(report->GetSsrc() == i);\n\t\t\tREQUIRE(report->GetNtpSec() == i);\n\t\t\tREQUIRE(report->GetNtpFrac() == i);\n\t\t\tREQUIRE(report->GetRtpTs() == i);\n\t\t\tREQUIRE(report->GetPacketCount() == i);\n\t\t\tREQUIRE(report->GetOctetCount() == i);\n\t\t}\n\n\t\tdelete packet3;\n\t\tdelete packet4;\n\t}\n\n\tSECTION(\"create SR\")\n\t{\n\t\t// Create local report and check content.\n\t\tRTC::RTCP::SenderReport report1;\n\n\t\treport1.SetSsrc(ssrc);\n\t\treport1.SetNtpSec(ntpSec);\n\t\treport1.SetNtpFrac(ntpFrac);\n\t\treport1.SetRtpTs(rtpTs);\n\t\treport1.SetPacketCount(packetCount);\n\t\treport1.SetOctetCount(octetCount);\n\n\t\tverify(&report1);\n\n\t\tSECTION(\"create a report out of the existing one\")\n\t\t{\n\t\t\tRTC::RTCP::SenderReport report2(&report1);\n\n\t\t\tverify(&report2);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTCP/TestXr.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTCP/XR.hpp\"\n#include \"RTC/RTCP/XrDelaySinceLastRr.hpp\"\n#include \"RTC/RTCP/XrReceiverReferenceTime.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp(), std::memcpy()\n\nSCENARIO(\"RTCP XR\", \"[rtcp][xr]\")\n{\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0xa0, 0xcf, 0x00, 0x09, // Padding, Type: 207 (XR), Length: 9\n\t\t0x5d, 0x93, 0x15, 0x34, // Sender SSRC: 0x5d931534\n\t\t// Extended Report DLRR\n\t\t0x05, 0x00, 0x00, 0x06, // BT: 5 (DLRR), Block Length: 6\n\t\t0x11, 0x12, 0x13, 0x14, // SSRC 1\n\t\t0x00, 0x11, 0x00, 0x11, // LRR 1\n\t\t0x11, 0x00, 0x11, 0x00, // DLRR 1\n\t\t0x21, 0x22, 0x23, 0x24, // SSRC 2\n\t\t0x00, 0x22, 0x00, 0x22, // LRR 2\n\t\t0x22, 0x00, 0x22, 0x00, // DLRR 2\n\t\t0x00, 0x00, 0x00, 0x04 // Padding (4 bytes)\n\t};\n\t// clang-format on\n\n\tSECTION(\"alignof() RTCP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTCP::ExtendedReportBlock::CommonHeader) == 2);\n\t\tREQUIRE(alignof(RTC::RTCP::DelaySinceLastRr::SsrcInfo::Body) == 4);\n\t\tREQUIRE(alignof(RTC::RTCP::ReceiverReferenceTime::Body) == 4);\n\t}\n\n\tSECTION(\"parse XR packet\")\n\t{\n\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet(\n\t\t  RTC::RTCP::ExtendedReportPacket::Parse(buffer, sizeof(buffer)));\n\n\t\tREQUIRE(packet);\n\t\t// Despite total buffer size is 40 bytes, our GetSize() method doesn't not\n\t\t// consider RTCP padding (4 bytes in this case).\n\t\t// https://github.com/versatica/mediasoup/issues/1233\n\t\tREQUIRE(packet->GetSize() == 36);\n\t\tREQUIRE(packet->GetCount() == 0);\n\t\tREQUIRE(packet->GetSsrc() == 0x5d931534);\n\n\t\tsize_t blockIdx{ 0u };\n\n\t\tfor (auto it = packet->Begin(); it != packet->End(); ++it, ++blockIdx)\n\t\t{\n\t\t\tauto* block = *it;\n\n\t\t\t// NOLINTNEXTLINE(hicpp-multiway-paths-covered)\n\t\t\tswitch (blockIdx)\n\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(block->GetSize() == 28);\n\n\t\t\t\t\tsize_t ssrcInfoIdx{ 0u };\n\t\t\t\t\tauto* dlrrBlock = reinterpret_cast<RTC::RTCP::DelaySinceLastRr*>(block);\n\n\t\t\t\t\tfor (auto it2 = dlrrBlock->Begin(); it2 != dlrrBlock->End(); ++it2, ++ssrcInfoIdx)\n\t\t\t\t\t{\n\t\t\t\t\t\tauto* ssrcInfo = *it2;\n\n\t\t\t\t\t\tswitch (ssrcInfoIdx)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcase 0:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetSsrc() == 0x11121314);\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetLastReceiverReport() == 0x00110011);\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetDelaySinceLastReceiverReport() == 0x11001100);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetSsrc() == 0x21222324);\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetLastReceiverReport() == 0x00220022);\n\t\t\t\t\t\t\t\tREQUIRE(ssrcInfo->GetDelaySinceLastReceiverReport() == 0x22002200);\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tdefault:;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// There are 2 SSRC infos.\n\t\t\t\t\tREQUIRE(ssrcInfoIdx == 2);\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\t// There are 1 block (the DLRR block).\n\t\tREQUIRE(blockIdx == 1);\n\n\t\tSECTION(\"serialize packet instance\")\n\t\t{\n\t\t\t// NOTE: Padding in RTCP is removed (if not needed) when serializing the\n\t\t\t// packet, so we must mangle the buffer content (padding bit) and the\n\t\t\t// buffer length before comparing the serialized packet with and original\n\t\t\t// buffer.\n\n\t\t\tconst size_t paddingBytes{ 4 };\n\t\t\tconst size_t serializedBufferLength                   = sizeof(buffer) - paddingBytes;\n\t\t\talignas(4) uint8_t serialized[serializedBufferLength] = { 0 };\n\n\t\t\t// Clone the original buffer into a new buffer without padding.\n\t\t\talignas(4) uint8_t clonedBuffer[serializedBufferLength] = { 0 };\n\t\t\tstd::memcpy(clonedBuffer, buffer, serializedBufferLength);\n\n\t\t\t// Remove the padding bit in the first byte of the cloned buffer.\n\t\t\tclonedBuffer[0] = 0x80;\n\n\t\t\t// Change RTCP length field in the cloned buffer.\n\t\t\tclonedBuffer[3] = clonedBuffer[3] - 1;\n\n\t\t\tpacket->Serialize(serialized);\n\n\t\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet2(\n\t\t\t  RTC::RTCP::ExtendedReportPacket::Parse(serialized, serializedBufferLength));\n\n\t\t\tREQUIRE(packet2->GetType() == RTC::RTCP::Type::XR);\n\t\t\tREQUIRE(packet2->GetCount() == 0);\n\t\t\tREQUIRE(packet2->GetSize() == 36);\n\n\t\t\tREQUIRE(std::memcmp(clonedBuffer, serialized, serializedBufferLength) == 0);\n\t\t}\n\t}\n}\n\nSCENARIO(\"RTCP XrDelaySinceLastRt\", \"[rtcp][xr]\")\n{\n\tSECTION(\"create RRT\")\n\t{\n\t\t// Create local report and check content.\n\t\t// NOTE: We cannot use unique_ptr here since the instance lifecycle will be\n\t\t// managed by the packet.\n\t\tauto* report1 = new RTC::RTCP::ReceiverReferenceTime();\n\n\t\treport1->SetNtpSec(11111111);\n\t\treport1->SetNtpFrac(22222222);\n\n\t\tREQUIRE(report1->GetType() == RTC::RTCP::ExtendedReportBlock::Type::RRT);\n\t\tREQUIRE(report1->GetNtpSec() == 11111111);\n\t\tREQUIRE(report1->GetNtpFrac() == 22222222);\n\n\t\t// Serialize the report into an external buffer.\n\t\talignas(4) uint8_t bufferReport1[256]{ 0 };\n\n\t\treport1->Serialize(bufferReport1);\n\n\t\t// Create a new report out of the external buffer.\n\t\t// NOTE: We cannot use unique_ptr here since the instance lifecycle will be\n\t\t// managed by the packet.\n\t\tauto* report2 = RTC::RTCP::ReceiverReferenceTime::Parse(bufferReport1, report1->GetSize());\n\n\t\tREQUIRE(report1->GetType() == report2->GetType());\n\t\tREQUIRE(report1->GetNtpSec() == report2->GetNtpSec());\n\t\tREQUIRE(report1->GetNtpFrac() == report2->GetNtpFrac());\n\n\t\t// Create a local packet.\n\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet1(new RTC::RTCP::ExtendedReportPacket());\n\n\t\tpacket1->SetSsrc(2222);\n\t\tpacket1->AddReport(report1);\n\t\tpacket1->AddReport(report2);\n\n\t\tREQUIRE(packet1->GetType() == RTC::RTCP::Type::XR);\n\t\tREQUIRE(packet1->GetCount() == 0);\n\t\tREQUIRE(packet1->GetSsrc() == 2222);\n\n\t\t// Total size:\n\t\t// -  RTCP common header\n\t\t// -  SSRC\n\t\t// -  block 1\n\t\t// -  block 2\n\t\tREQUIRE(packet1->GetSize() == 4 + 4 + 12 + 12);\n\n\t\t// Serialize the packet into an external buffer.\n\t\talignas(4) uint8_t bufferPacket1[256]{ 0 };\n\t\talignas(4) uint8_t bufferPacket2[256]{ 0 };\n\n\t\tpacket1->Serialize(bufferPacket1);\n\n\t\t// Create a new packet out of the external buffer.\n\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet2(\n\t\t  RTC::RTCP::ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize()));\n\n\t\tREQUIRE(packet2->GetType() == packet1->GetType());\n\t\tREQUIRE(packet2->GetCount() == packet1->GetCount());\n\t\tREQUIRE(packet2->GetSsrc() == packet1->GetSsrc());\n\t\tREQUIRE(packet2->GetSize() == packet1->GetSize());\n\n\t\tpacket2->Serialize(bufferPacket2);\n\n\t\tREQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);\n\t}\n\n\tSECTION(\"create DLRR\")\n\t{\n\t\t// Create local report and check content.\n\t\t// NOTE: We cannot use unique_ptr here since the instance lifecycle will be\n\t\t// managed by the packet.\n\t\tauto* report1 = new RTC::RTCP::DelaySinceLastRr();\n\t\t// NOTE: We cannot use unique_ptr here since the instance lifecycle will be\n\t\t// managed by the report.\n\t\tauto* ssrcInfo1 = new RTC::RTCP::DelaySinceLastRr::SsrcInfo();\n\n\t\tssrcInfo1->SetSsrc(1234);\n\t\tssrcInfo1->SetLastReceiverReport(11111111);\n\t\tssrcInfo1->SetDelaySinceLastReceiverReport(22222222);\n\n\t\tREQUIRE(ssrcInfo1->GetSsrc() == 1234);\n\t\tREQUIRE(ssrcInfo1->GetLastReceiverReport() == 11111111);\n\t\tREQUIRE(ssrcInfo1->GetDelaySinceLastReceiverReport() == 22222222);\n\t\tREQUIRE(ssrcInfo1->GetSize() == sizeof(RTC::RTCP::DelaySinceLastRr::SsrcInfo::Body));\n\n\t\treport1->AddSsrcInfo(ssrcInfo1);\n\n\t\t// Serialize the report into an external buffer.\n\t\talignas(4) uint8_t bufferReport1[256]{ 0 };\n\n\t\treport1->Serialize(bufferReport1);\n\n\t\t// Create a new report out of the external buffer.\n\t\t// NOTE: We cannot use unique_ptr here since the instance lifecycle will be\n\t\t// managed by the packet.\n\t\t// NOLINTNEXTLINE(llvm-qualified-auto,readability-qualified-auto)\n\t\tauto* report2 = RTC::RTCP::DelaySinceLastRr::Parse(bufferReport1, report1->GetSize());\n\n\t\tREQUIRE(report1->GetType() == report2->GetType());\n\n\t\tauto ssrcInfoIt = report2->Begin();\n\t\tauto* ssrcInfo2 = *ssrcInfoIt;\n\n\t\tREQUIRE(ssrcInfo1->GetSsrc() == ssrcInfo2->GetSsrc());\n\t\tREQUIRE(ssrcInfo1->GetLastReceiverReport() == ssrcInfo2->GetLastReceiverReport());\n\t\tREQUIRE(\n\t\t  ssrcInfo1->GetDelaySinceLastReceiverReport() == ssrcInfo2->GetDelaySinceLastReceiverReport());\n\t\tREQUIRE(ssrcInfo1->GetSize() == ssrcInfo2->GetSize());\n\n\t\t// Create a local packet.\n\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet1(new RTC::RTCP::ExtendedReportPacket());\n\n\t\tpacket1->SetSsrc(2222);\n\t\tpacket1->AddReport(report1);\n\t\tpacket1->AddReport(report2);\n\n\t\tREQUIRE(packet1->GetType() == RTC::RTCP::Type::XR);\n\t\tREQUIRE(packet1->GetCount() == 0);\n\t\tREQUIRE(packet1->GetSsrc() == 2222);\n\n\t\t// Total size:\n\t\t// -  RTCP common header\n\t\t// -  SSRC\n\t\t// -  block 1\n\t\t// -  block 2\n\t\tREQUIRE(packet1->GetSize() == 4 + 4 + 16 + 16);\n\n\t\t// Serialize the packet into an external buffer.\n\t\talignas(4) uint8_t bufferPacket1[256]{ 0 };\n\t\talignas(4) uint8_t bufferPacket2[256]{ 0 };\n\n\t\tpacket1->Serialize(bufferPacket1);\n\n\t\t// Create a new packet out of the external buffer.\n\t\tstd::unique_ptr<RTC::RTCP::ExtendedReportPacket> packet2(\n\t\t  RTC::RTCP::ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize()));\n\n\t\tREQUIRE(packet2->GetType() == packet1->GetType());\n\t\tREQUIRE(packet2->GetCount() == packet1->GetCount());\n\t\tREQUIRE(packet2->GetSsrc() == packet1->GetSsrc());\n\t\tREQUIRE(packet2->GetSize() == packet1->GetSize());\n\n\t\tpacket2->Serialize(bufferPacket2);\n\n\t\tREQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/Codecs/TestDependencyDescriptor.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/DependencyDescriptor.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"Dependency Descriptor\", \"[rtp][codecs][dependency-descriptor]\")\n{\n\tclass Listener : public RTC::RTP::Codecs::DependencyDescriptor::Listener\n\t{\n\tpublic:\n\t\tvoid OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override\n\t\t{\n\t\t}\n\t};\n\n\tSECTION(\"parse\")\n\t{\n\t\t/**\n\t\t * Taken from https://issues.webrtc.org/issues/42225660.\n\t\t *\n\t\t * {\n\t\t *     \"startOfFrame\" : true,\n\t\t *     \"endOfFrame\" : false,\n\t\t *     \"frameDependencyTemplateId\" : 0,\n\t\t *     \"frameNumber\" : 303,\n\t\t *     \"templateStructure\": {\n\t\t *         \"templateIdOffset\" : 0,\n\t\t *         \"templateInfo\" : {\n\t\t *           \"0\" : {\n\t\t *             \"spatialId\" : 0,\n\t\t *             \"temporalId\" : 0,\n\t\t *             \"dti\" : [ \"SWITCH\", \"SWITCH\", \"SWITCH\" ],\n\t\t *             \"fdiff\" : [],\n\t\t *               \"chains\" : [0]\n\t\t *           },\n\t\t *           \"1\" : {\n\t\t *             \"spatialId\" : 0,\n\t\t *             \"temporalId\" : 0,\n\t\t *             \"dti\" : [ \"SWITCH\", \"SWITCH\", \"SWITCH\" ],\n\t\t *             \"fdiff\" : [4],\n\t\t *             \"chains\" : [4]\n\t\t *           },\n\t\t *           \"2\" : {\n\t\t *             \"spatialId\" : 0,\n\t\t *             \"temporalId\" : 1,\n\t\t *             \"dti\" : [ \"NOT_PRESENT\", \"DISCARDABLE\", \"SWITCH\" ],\n\t\t *             \"fdiff\" : [2],\n\t\t *             \"chains\" : [2]\n\t\t *           },\n\t\t *           \"3\" : {\n\t\t *             \"spatialId\" : 0,\n\t\t *             \"temporalId\" : 2,\n\t\t *             \"dti\" : [ \"NOT_PRESENT\", \"NOT_PRESENT\", \"DISCARDABLE\" ],\n\t\t *             \"fdiff\" : [1],\n\t\t *               \"chains\" : [1]\n\t\t *           },\n\t\t *           \"4\" : {\n\t\t *             \"spatialId\" : 0,\n\t\t *             \"temporalId\" : 2,\n\t\t *             \"dti\" : [ \"NOT_PRESENT\", \"NOT_PRESENT\", \"DISCARDABLE\" ],\n\t\t *             \"fdiff\" : [1],\n\t\t *             \"chains\" : [3]\n\t\t *           }\n\t\t *         },\n\t\t *         \"decodeTargetInfo\" : {\n\t\t *           \"0\" : { \"protectedBy\" : 0, \"spatialId\" : 0, \"temporalId\" : 0 },\n\t\t *           \"1\" : { \"protectedBy\" : 0, \"spatialId\" : 0, \"temporalId\" : 1 },\n\t\t *           \"2\" : { \"protectedBy\" : 0, \"spatialId\" : 0, \"temporalId\" : 2 }\n\t\t *         },\n\t\t *         \"maxSpatialId\" : 0,\n\t\t *         \"maxTemporalId\" : 2\n\t\t *         }\n\t\t * }\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t data[] =\n\t\t{\n\t\t\t0x80, 0x01, 0x2F, 0x80, 0x02, 0x14, 0xEA, 0xA8,\n\t\t\t0x60, 0x41, 0x4D, 0x14, 0x10, 0x20, 0x84, 0x26\n\t\t};\n\n\t\tListener listener;\n\n\t\t// clang-format on\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t\t  templateDependencyStructure;\n\t\tauto dependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t\t    data, sizeof(data), std::addressof(listener), templateDependencyStructure));\n\n\t\tREQUIRE(dependencyDescriptor);\n\t\tREQUIRE(dependencyDescriptor->startOfFrame == true);\n\t\tREQUIRE(dependencyDescriptor->endOfFrame == false);\n\t\tREQUIRE(dependencyDescriptor->frameDependencyTemplateId == 0);\n\t\tREQUIRE(dependencyDescriptor->frameNumber == 303);\n\n\t\tauto* templateStructure = dependencyDescriptor->templateDependencyStructure;\n\t\tstd::vector<RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication> dtis{};\n\t\tstd::vector<uint8_t> fdiffs{};\n\t\tstd::vector<uint8_t> fdiffChains{};\n\n\t\tREQUIRE(templateStructure->templateLayers.size() == 5);\n\t\tREQUIRE(templateStructure->templateLayers[0].spatialLayer == 0);\n\t\tREQUIRE(templateStructure->templateLayers[0].temporalLayer == 0);\n\t\tdtis = {\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t};\n\t\tREQUIRE(templateStructure->templateLayers[0].decodeTargetIndications == dtis);\n\t\tfdiffs = {};\n\t\tREQUIRE(templateStructure->templateLayers[0].frameDiffs == fdiffs);\n\t\tfdiffChains = { 0 };\n\t\tREQUIRE(templateStructure->templateLayers[0].frameDiffChains == fdiffChains);\n\n\t\tREQUIRE(templateStructure->templateLayers[1].spatialLayer == 0);\n\t\tREQUIRE(templateStructure->templateLayers[1].temporalLayer == 0);\n\t\tdtis = {\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t};\n\t\tREQUIRE(templateStructure->templateLayers[1].decodeTargetIndications == dtis);\n\t\tfdiffs = { 4 };\n\t\tREQUIRE(templateStructure->templateLayers[1].frameDiffs == fdiffs);\n\t\tfdiffChains = { 4 };\n\t\tREQUIRE(templateStructure->templateLayers[1].frameDiffChains == fdiffChains);\n\n\t\tREQUIRE(templateStructure->templateLayers[2].spatialLayer == 0);\n\t\tREQUIRE(templateStructure->templateLayers[2].temporalLayer == 1);\n\t\tdtis = {\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH,\n\t\t};\n\t\tREQUIRE(templateStructure->templateLayers[2].decodeTargetIndications == dtis);\n\t\tfdiffs = { 2 };\n\t\tREQUIRE(templateStructure->templateLayers[2].frameDiffs == fdiffs);\n\t\tfdiffChains = { 2 };\n\t\tREQUIRE(templateStructure->templateLayers[2].frameDiffChains == fdiffChains);\n\n\t\tREQUIRE(templateStructure->templateLayers[3].spatialLayer == 0);\n\t\tREQUIRE(templateStructure->templateLayers[3].temporalLayer == 2);\n\t\tdtis = {\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE,\n\t\t};\n\t\tREQUIRE(templateStructure->templateLayers[3].decodeTargetIndications == dtis);\n\t\tfdiffs = { 1 };\n\t\tREQUIRE(templateStructure->templateLayers[3].frameDiffs == fdiffs);\n\t\tfdiffChains = { 1 };\n\t\tREQUIRE(templateStructure->templateLayers[3].frameDiffChains == fdiffChains);\n\n\t\tREQUIRE(templateStructure->templateLayers[4].spatialLayer == 0);\n\t\tREQUIRE(templateStructure->templateLayers[4].temporalLayer == 2);\n\t\tdtis = {\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT,\n\t\t\tRTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE,\n\t\t};\n\t\tREQUIRE(templateStructure->templateLayers[4].decodeTargetIndications == dtis);\n\t\tfdiffs = { 1 };\n\t\tREQUIRE(templateStructure->templateLayers[4].frameDiffs == fdiffs);\n\t\tfdiffChains = { 3 };\n\t\tREQUIRE(templateStructure->templateLayers[4].frameDiffChains == fdiffChains);\n\t}\n\n\tSECTION(\"serialize\")\n\t{\n\t\t/**\n\t\t * <DependencyDescriptor>\n\t\t * \tstartOfFrame: true\n\t\t * \tendOfFrame: false\n\t\t * \tframeDependencyTemplateId: 0\n\t\t * \tframeNumber: 232\n\t\t * \ttemplateId: 0\n\t\t * \ttemporalLayer: 0\n\t\t * \tspatialLayer: 0\n\t\t * \t<TemplateDependencyStructure>\n\t\t * \t\tspatialLayers: 0\n\t\t * \t\ttemporalLayers: 1\n\t\t * \t\ttemplateIdOffset: 0\n\t\t * \t\tdecodeTargetCount: 2\n\t\t * \t\t<TemplateLayers>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t    spatialLayerId: 0\n\t\t * \t\t    temporalLayerId: 0\n\t\t * \t\t    <DecodeTargetIndications> SS </DecodeTargetIndications>\n\t\t * \t\t    <FrameDiffs>  </FrameDiffs>\n\t\t * \t\t    <FrameDiffChains> 0 </FrameDiffChains>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t    spatialLayerId: 0\n\t\t * \t\t    temporalLayerId: 0\n\t\t * \t\t    <DecodeTargetIndications> SS </DecodeTargetIndications>\n\t\t * \t\t    <FrameDiffs> 2 </FrameDiffs>\n\t\t * \t\t    <FrameDiffChains> 2 </FrameDiffChains>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t    spatialLayerId: 0\n\t\t * \t\t    temporalLayerId: 1\n\t\t * \t\t    <DecodeTargetIndications> -D </DecodeTargetIndications>\n\t\t * \t\t    <FrameDiffs> 1 </FrameDiffs>\n\t\t * \t\t    <FrameDiffChains> 1 </FrameDiffChains>\n\t\t * \t\t  <FrameDependencyTemplate>\n\t\t * \t\t</TemplateLayers>\n\t\t * \t\t</TemplateDependencyStructure>\n\t\t * </DependencyDescriptor>\n\t\t */\n\t\t// clang-format off\n\t\tuint8_t data1[] =\n\t\t{\n\t\t\t0x80, 0x00, 0xE8, 0x80,\n\t\t\t0x01, 0x1E, 0xA8, 0x51,\n\t\t\t0x41, 0x01, 0x0C, 0x13,\n\t\t\t0xFC, 0x0B, 0x3C,\n\t\t};\n\t\t// clang-format on\n\n\t\tListener listener;\n\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t\t  templateDependencyStructure;\n\t\tauto dependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t\t    data1, sizeof(data1), std::addressof(listener), templateDependencyStructure));\n\n\t\tREQUIRE(dependencyDescriptor);\n\t\tREQUIRE(dependencyDescriptor->frameNumber == 232);\n\n\t\t// clang-format off\n\t\t//  000000 4D 00 D8\n\t\tuint8_t data2[] =\n\t\t{\n\t\t\t0x00, 0x00, 0xE8,\n\t\t};\n\n\t\t// clang-format on\n\t\tdependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t\t    data2, sizeof(data2), std::addressof(listener), templateDependencyStructure));\n\n\t\tREQUIRE(dependencyDescriptor);\n\t\tREQUIRE(dependencyDescriptor->frameNumber == 232);\n\n\t\tuint8_t len;\n\n\t\tconst auto* data = dependencyDescriptor->Serialize(len);\n\n\t\t// clang-format on\n\t\tdependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t\t    data, sizeof(data), std::addressof(listener), templateDependencyStructure));\n\n\t\tREQUIRE(dependencyDescriptor);\n\t\tREQUIRE(dependencyDescriptor->frameNumber == 232);\n\n\t\tdependencyDescriptor->UpdateActiveDecodeTargets(0, 1);\n\n\t\tdata = dependencyDescriptor->Serialize(len);\n\n\t\t// clang-format on\n\t\tdependencyDescriptor = std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor>(\n\t\t  RTC::RTP::Codecs::DependencyDescriptor::Parse(\n\t\t    data, sizeof(data), std::addressof(listener), templateDependencyStructure));\n\n\t\tREQUIRE(dependencyDescriptor);\n\t\tREQUIRE(dependencyDescriptor->frameNumber == 232);\n\t\t// activeDecodeTargetsBitmask == 00000011\n\t\tREQUIRE(dependencyDescriptor->activeDecodeTargetsBitmask == 3);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/Codecs/TestH264.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTP/Codecs/H264.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"H264 payload descriptor\", \"[rtp][codecs][h264]\")\n{\n\tSECTION(\"parse payload descriptor\")\n\t{\n\t\t// clang-format off\n\t\tuint8_t originalBuffer[] =\n\t\t{\n\t\t\t0x07, 0x80, 0x11, 0x00\n\t\t};\n\t\t// clang-format on\n\t\t//\n\t\t// Keep a copy of the original buffer for comparing.\n\t\tuint8_t buffer[4] = { 0 };\n\n\t\tstd::memcpy(buffer, originalBuffer, sizeof(buffer));\n\n\t\tRTC::RTP::Codecs::DependencyDescriptor* dependencyDescriptor{ nullptr };\n\n\t\tstd::unique_ptr<RTC::RTP::Codecs::H264::PayloadDescriptor> payloadDescriptor{\n\t\t\tRTC::RTP::Codecs::H264::Parse(buffer, sizeof(buffer), dependencyDescriptor)\n\t\t};\n\n\t\tREQUIRE(payloadDescriptor);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/Codecs/TestVP8.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/RTP/rtpCommon.hpp\"\n#include \"RTC/RTP/Codecs/VP8.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp(), std::memcpy()\n\nnamespace\n{\n\tRTC::RTP::Codecs::VP8::PayloadDescriptor* createVP8PayloadDescriptor(\n\t  uint8_t* buffer,\n\t  size_t bufferLen,\n\t  uint16_t pictureId,\n\t  uint8_t tl0PictureIndex,\n\t  uint8_t tlIndex,\n\t  bool layerSync = true)\n\t{\n\t\tuint16_t netPictureId = htons(pictureId);\n\t\tstd::memcpy(buffer + 2, &netPictureId, 2);\n\t\tbuffer[2] |= 0x80;\n\t\tbuffer[4] = tl0PictureIndex;\n\t\tbuffer[5] = tlIndex << 6;\n\n\t\tif (layerSync)\n\t\t{\n\t\t\tbuffer[5] |= 0x20; // y bit\n\t\t}\n\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, bufferLen);\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\treturn payloadDescriptor;\n\t}\n\n\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> processVP8Packet(\n\t  RTC::RTP::Codecs::VP8::EncodingContext& context,\n\t  uint16_t pictureId,\n\t  uint8_t tl0PictureIndex,\n\t  uint8_t tlIndex,\n\t  bool layerSync = true)\n\t{\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0x90, 0xe0, 0x80, 0x00, 0x00, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tbool marker;\n\t\tauto* payloadDescriptor = createVP8PayloadDescriptor(\n\t\t  packet->GetPayload(), packet->GetPayloadLength(), pictureId, tl0PictureIndex, tlIndex, layerSync);\n\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptorHandler> payloadDescriptorHandler(\n\t\t  new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor));\n\n\t\tif (payloadDescriptorHandler->Process(&context, packet.get(), marker))\n\t\t{\n\t\t\treturn std::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor>(\n\t\t\t  RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength()));\n\t\t}\n\n\t\treturn nullptr;\n\t}\n} // namespace\n\nSCENARIO(\"VP8 payload descriptor\", \"[rtp][codecs][vp8]\")\n{\n\tSECTION(\"parse payload descriptor\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 1 = X bit: Extended control bits present (I L T K)\n\t\t * 1 = R bit: Reserved for future use (Error should be zero)\n\t\t * 0 = N bit: Reference frame\n\t\t * 1 = S bit: Start of VP8 partition\n\t\t * Part Id: 0\n\t\t * 1 = I bit: Picture ID byte present\n\t\t * 0 = L bit: TL0PICIDX byte not present\n\t\t * 0 = T bit: TID (temporal layer index) byte not present\n\t\t * 0 = K bit: TID/KEYIDX byte not present\n\t\t * 0000 = Reserved A: 0\n\t\t * 0001 0001 = Picture Id: 17\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t originalBuffer[] =\n\t\t{\n\t\t\t0xd0, 0x80, 0x11, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\t// Keep a copy of the original buffer for comparing.\n\t\tuint8_t buffer[4] = { 0 };\n\n\t\tstd::memcpy(buffer, originalBuffer, sizeof(buffer));\n\n\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> payloadDescriptor{\n\t\t\tRTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tREQUIRE(payloadDescriptor->extended == 1);\n\t\tREQUIRE(payloadDescriptor->nonReference == 0);\n\t\tREQUIRE(payloadDescriptor->start == 1);\n\t\tREQUIRE(payloadDescriptor->partitionIndex == 0);\n\n\t\t// Optional field flags.\n\t\tREQUIRE(payloadDescriptor->i == 1);\n\t\tREQUIRE(payloadDescriptor->l == 0);\n\t\tREQUIRE(payloadDescriptor->t == 0);\n\t\tREQUIRE(payloadDescriptor->k == 0);\n\n\t\t// Optional fields.\n\t\tREQUIRE(payloadDescriptor->pictureId == 17);\n\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 0);\n\t\tREQUIRE(payloadDescriptor->tlIndex == 0);\n\t\tREQUIRE(payloadDescriptor->y == 0);\n\t\tREQUIRE(payloadDescriptor->keyIndex == 0);\n\n\t\tREQUIRE(payloadDescriptor->isKeyFrame == true);\n\t\tREQUIRE(payloadDescriptor->hasPictureId == true);\n\t\tREQUIRE(payloadDescriptor->hasOneBytePictureId == true);\n\t\tREQUIRE(payloadDescriptor->hasTwoBytesPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTl0PictureIndex == false);\n\t\tREQUIRE(payloadDescriptor->hasTlIndex == false);\n\n\t\tSECTION(\"encode payload descriptor\")\n\t\t{\n\t\t\tpayloadDescriptor->Encode(\n\t\t\t  buffer, payloadDescriptor->pictureId, payloadDescriptor->tl0PictureIndex);\n\n\t\t\tSECTION(\"compare encoded payload descriptor with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, originalBuffer, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t}\n\n\tSECTION(\"parse payload descriptor 2\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 1 = X bit: Extended control bits present (I L T K)\n\t\t * 0 = R bit: Reserved for future use\n\t\t * 0 = N bit: Reference frame\n\t\t * 0 = S bit: Continuation of VP8 partition\n\t\t * 000 = Part Id: 0\n\t\t * 0 = I bit: No Picture byte ID\n\t\t * 0 = L bit: TL0PICIDX byte not present\n\t\t * 1 = T bit: TID (temporal layer index) byte present\n\t\t * 1 = K bit: TID/KEYIDX byte present\n\t\t * 1110 = Reserved A: 14\n\t\t * 11 = Temporal layer Index (TID): 3\n\t\t * 1 = 1 Lay Sync Bit (Y): True\n\t\t * ...0 0100 = Temporal Key Frame Index (KEYIDX): 4\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t originalBuffer[] =\n\t\t{\n\t\t  0x88, 0x3e, 0xe4, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\t// Keep a copy of the original buffer for comparing.\n\t\tuint8_t buffer[4] = { 0 };\n\n\t\tstd::memcpy(buffer, originalBuffer, sizeof(buffer));\n\n\t\t// Parse the buffer.\n\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> payloadDescriptor{\n\t\t\tRTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tREQUIRE(payloadDescriptor->extended == 1);\n\t\tREQUIRE(payloadDescriptor->nonReference == 0);\n\t\tREQUIRE(payloadDescriptor->start == 0);\n\t\tREQUIRE(payloadDescriptor->partitionIndex == 0);\n\n\t\t// Optional field flags.\n\t\tREQUIRE(payloadDescriptor->i == 0);\n\t\tREQUIRE(payloadDescriptor->l == 0);\n\t\tREQUIRE(payloadDescriptor->t == 1);\n\t\tREQUIRE(payloadDescriptor->k == 1);\n\n\t\t// Optional fields.\n\t\tREQUIRE(payloadDescriptor->pictureId == 0);\n\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 0);\n\t\tREQUIRE(payloadDescriptor->tlIndex == 3);\n\t\tREQUIRE(payloadDescriptor->y == 1);\n\t\tREQUIRE(payloadDescriptor->keyIndex == 4);\n\n\t\tREQUIRE(payloadDescriptor->isKeyFrame == false);\n\t\tREQUIRE(payloadDescriptor->hasPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasOneBytePictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTwoBytesPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTl0PictureIndex == false);\n\t\tREQUIRE(payloadDescriptor->hasTlIndex == true);\n\n\t\tSECTION(\"encode payload descriptor\")\n\t\t{\n\t\t\tpayloadDescriptor->Encode(\n\t\t\t  buffer, payloadDescriptor->pictureId, payloadDescriptor->tl0PictureIndex);\n\n\t\t\tSECTION(\"compare encoded payloadDescriptor with original buffer\")\n\t\t\t{\n\t\t\t\tREQUIRE(std::memcmp(buffer, originalBuffer, sizeof(buffer)) == 0);\n\t\t\t}\n\t\t}\n\t};\n\n\tSECTION(\"parse payload descriptor, encode\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 1 = X bit: Extended control bits present (I L T K)\n\t\t * 1 = R bit: Reserved for future use (Error should be zero)\n\t\t * 0 = N bit: Reference frame\n\t\t * 1 = S bit: Start of VP8 partition\n\t\t * Part Id: 0\n\t\t * 1 = I bit: Picture ID byte present\n\t\t * 1 = L bit: TL0PICIDX byte present\n\t\t * 0 = T bit: TID (temporal layer index) byte not present\n\t\t * 0 = K bit: TID/KEYIDX byte not present\n\t\t * 0000 = Reserved A: 0\n\t\t * 0001 0001 = Picture Id: 17\n\t\t * 0000 0011 = TL0PICIDX: 3\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t originalBuffer[] =\n\t\t{\n\t\t\t0xd0, 0xc0, 0x11, 0x03\n\t\t};\n\t\t// clang-format on\n\n\t\t// Keep a copy of the original buffer for comparing.\n\t\tuint8_t buffer[4] = { 0 };\n\n\t\tstd::memcpy(buffer, originalBuffer, sizeof(buffer));\n\n\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> payloadDescriptor{\n\t\t\tRTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer))\n\t\t};\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tREQUIRE(payloadDescriptor->pictureId == 17);\n\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 3);\n\n\t\tSECTION(\"encode payload descriptor\")\n\t\t{\n\t\t\tpayloadDescriptor->Encode(buffer, 20, 1);\n\n\t\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> payloadDescriptor{\n\t\t\t\tRTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer))\n\t\t\t};\n\n\t\t\tREQUIRE(payloadDescriptor->pictureId == 20);\n\t\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 1);\n\t\t}\n\n\t\tSECTION(\"restore payload descriptor\")\n\t\t{\n\t\t\tpayloadDescriptor->Restore(buffer);\n\n\t\t\tstd::unique_ptr<RTC::RTP::Codecs::VP8::PayloadDescriptor> payloadDescriptor{\n\t\t\t\tRTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer))\n\t\t\t};\n\n\t\t\tREQUIRE(payloadDescriptor->pictureId == 17);\n\t\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 3);\n\t\t}\n\t}\n\n\tSECTION(\"parse payload descriptor, I flag set but no space for pictureId\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 1 = X bit: Extended control bits present (I L T K)\n\t\t * 1 = R bit: Reserved for future use (Error should be zero)\n\t\t * 0 = N bit: Reference frame\n\t\t * 1 = S bit: Start of VP8 partition\n\t\t * Part Id: 0\n\t\t * 1 = I bit: Picture ID byte present\n\t\t * 0 = L bit: TL0PICIDX byte not present\n\t\t * 0 = T bit: TID (temporal layer index) byte not present\n\t\t * 0 = K bit: TID/KEYIDX byte not present\n\t\t * 0000 = Reserved A: 0\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t buffer[] =\n\t\t{\n\t\t\t0xd0, 0x80\n\t\t};\n\t\t// clang-format on\n\n\t\tconst auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer));\n\n\t\tREQUIRE(!payloadDescriptor);\n\t}\n\n\tSECTION(\"parse payload descriptor, X flag is not set, no keyframe\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 0 = X bit: Extended control bits present (I L T K)\n\t\t * 1 = R bit: Reserved for future use (Error should be zero)\n\t\t * 0 = N bit: Reference frame\n\t\t * 1 = S bit: Start of VP8 partition\n\t\t * Part Id: 0\n\t\t * 000000 = Size0 | H | VER\n\t\t * 1 = P bit: Inverse Keyframe\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t buffer[] =\n\t\t{\n\t\t\t0x50, 0x01\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer));\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tREQUIRE(payloadDescriptor->extended == 0);\n\t\tREQUIRE(payloadDescriptor->nonReference == 0);\n\t\tREQUIRE(payloadDescriptor->start == 1);\n\t\tREQUIRE(payloadDescriptor->partitionIndex == 0);\n\n\t\t// Optional field flags.\n\t\tREQUIRE(payloadDescriptor->i == 0);\n\t\tREQUIRE(payloadDescriptor->l == 0);\n\t\tREQUIRE(payloadDescriptor->t == 0);\n\t\tREQUIRE(payloadDescriptor->k == 0);\n\n\t\t// Optional fields.\n\t\tREQUIRE(payloadDescriptor->pictureId == 0);\n\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 0);\n\t\tREQUIRE(payloadDescriptor->tlIndex == 0);\n\t\tREQUIRE(payloadDescriptor->y == 0);\n\t\tREQUIRE(payloadDescriptor->keyIndex == 0);\n\n\t\tREQUIRE(payloadDescriptor->isKeyFrame == false);\n\t\tREQUIRE(payloadDescriptor->hasPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasOneBytePictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTwoBytesPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTl0PictureIndex == false);\n\t\tREQUIRE(payloadDescriptor->hasTlIndex == false);\n\n\t\tdelete payloadDescriptor;\n\t}\n\n\tSECTION(\"parse payload descriptor, X flag is not set, keyframe\")\n\t{\n\t\t/**\n\t\t * VP8 Payload Descriptor\n\t\t *\n\t\t * 0 = X bit: Extended control bits present (I L T K)\n\t\t * 1 = R bit: Reserved for future use (Error should be zero)\n\t\t * 0 = N bit: Reference frame\n\t\t * 1 = S bit: Start of VP8 partition\n\t\t * Part Id: 0\n\t\t * 000000 = Size0 | H | VER\n\t\t * 0 = P bit: Inverse Keyframe\n\t\t */\n\n\t\t// clang-format off\n\t\tuint8_t buffer[] =\n\t\t{\n\t\t\t0x50, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer));\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tREQUIRE(payloadDescriptor->extended == 0);\n\t\tREQUIRE(payloadDescriptor->nonReference == 0);\n\t\tREQUIRE(payloadDescriptor->start == 1);\n\t\tREQUIRE(payloadDescriptor->partitionIndex == 0);\n\n\t\t// Optional field flags.\n\t\tREQUIRE(payloadDescriptor->i == 0);\n\t\tREQUIRE(payloadDescriptor->l == 0);\n\t\tREQUIRE(payloadDescriptor->t == 0);\n\t\tREQUIRE(payloadDescriptor->k == 0);\n\n\t\t// Optional fields.\n\t\tREQUIRE(payloadDescriptor->pictureId == 0);\n\t\tREQUIRE(payloadDescriptor->tl0PictureIndex == 0);\n\t\tREQUIRE(payloadDescriptor->tlIndex == 0);\n\t\tREQUIRE(payloadDescriptor->y == 0);\n\t\tREQUIRE(payloadDescriptor->keyIndex == 0);\n\n\t\tREQUIRE(payloadDescriptor->isKeyFrame == true);\n\t\tREQUIRE(payloadDescriptor->hasPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasOneBytePictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTwoBytesPictureId == false);\n\t\tREQUIRE(payloadDescriptor->hasTl0PictureIndex == false);\n\t\tREQUIRE(payloadDescriptor->hasTlIndex == false);\n\n\t\tdelete payloadDescriptor;\n\t}\n}\n\nSCENARIO(\"process VP8 payload descriptor\", \"[rtp][codecs][vp8]\")\n{\n\tconstexpr uint16_t MaxPictureId = (1 << 15) - 1;\n\n\tSECTION(\"do not drop TL0PICIDX from temporal layers higher than 0\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 2;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context(params);\n\n\t\tcontext.SetCurrentTemporalLayer(0);\n\t\tcontext.SetTargetTemporalLayer(0);\n\n\t\t// Frame 1.\n\t\tauto forwarded = processVP8Packet(context, 0, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 0);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 0);\n\n\t\t// Frame 2 gets lost.\n\n\t\t// Frame 3.\n\t\tforwarded = processVP8Packet(context, 2, 1, 1);\n\t\tREQUIRE(!forwarded);\n\n\t\t// Frame 2 retransmitted.\n\t\tforwarded = processVP8Packet(context, 1, 1, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 1);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\t}\n\n\tSECTION(\"drop packets that belong to other temporal layers after rolling over pictureID\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 2;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context(params);\n\t\tcontext.SyncRequired();\n\n\t\tcontext.SetCurrentTemporalLayer(0);\n\t\tcontext.SetTargetTemporalLayer(0);\n\n\t\t// Frame 1.\n\t\tauto forwarded = processVP8Packet(context, MaxPictureId, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 1);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\t// Frame 2.\n\t\tforwarded = processVP8Packet(context, 0, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 2);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\t// Frame 3.\n\t\tforwarded = processVP8Packet(context, 1, 0, 1);\n\t\tREQUIRE(!forwarded);\n\t}\n\n\tSECTION(\"old packets with higher temporal layer than current are dropped\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 2;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context(params);\n\t\tcontext.SyncRequired();\n\n\t\tcontext.SetCurrentTemporalLayer(0);\n\t\tcontext.SetTargetTemporalLayer(0);\n\n\t\t// Frame 1.\n\t\tauto forwarded = processVP8Packet(context, 1, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 1);\n\t\tREQUIRE(forwarded->tlIndex == 0);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\t// Frame 2.\n\t\tforwarded = processVP8Packet(context, 2, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 2);\n\t\tREQUIRE(forwarded->tlIndex == 0);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\t// Frame 3. Old packet with higher temporal layer than current.\n\t\tforwarded = processVP8Packet(context, 0, 0, 1);\n\t\tREQUIRE(!forwarded);\n\t\tREQUIRE(context.GetCurrentTemporalLayer() == 0);\n\t}\n\n\tSECTION(\"packets with higher temporal layer than current are dropped\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 2;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context(params);\n\t\tcontext.SyncRequired();\n\n\t\tcontext.SetCurrentTemporalLayer(0);\n\t\tcontext.SetTargetTemporalLayer(0);\n\n\t\t// Frame 1.\n\t\tauto forwarded = processVP8Packet(context, 1, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 1);\n\t\tREQUIRE(forwarded->tlIndex == 0);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\t// Frame 2.\n\t\tforwarded = processVP8Packet(context, 2, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 2);\n\t\tREQUIRE(forwarded->tlIndex == 0);\n\t\tREQUIRE(forwarded->tl0PictureIndex == 1);\n\n\t\tcontext.SetTargetTemporalLayer(2);\n\n\t\t// Frame 3. Old packet with higher temporal layer than current.\n\t\tforwarded = processVP8Packet(context, 3, 0, 1);\n\t\tREQUIRE(!forwarded);\n\t\tREQUIRE(context.GetCurrentTemporalLayer() == 0);\n\t}\n}\n\nSCENARIO(\"encode VP8 payload descriptor\", \"[rtp][codecs][vp8]\")\n{\n\t/**\n\t * VP8 Payload Descriptor\n\t *\n\t * 1 = X bit: Extended control bits present (I L T K)\n\t * 0 = R bit: Reserved for future use\n\t * 0 = N bit: Reference frame\n\t * 0 = S bit: Continuation of VP8 partition\n\t * 000 = Part Id: 0\n\t * 1 = I bit: Picture byte ID\n\t * 1 = L bit: TL0PICIDX byte present\n\t * 1 = T bit: TID (temporal layer index) byte present\n\t * 0 = K bit: TID/KEYIDX byte present\n\t * 0000 = Reserved A: 14\n\t * 0000000000000001 = PictureId\n\t */\n\n\t// clang-format off\n\tuint8_t payload[] =\n\t{\n\t\t0x80, 0xe0, 0x01, 0x01,\n\t\t0xe8, 0x40, 0x7a, 0xd8\n\t};\n\t// clang-format on\n\n\tbool marker;\n\n\tSECTION(\"encode based on specific encoder\")\n\t{\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(payload, sizeof(payload));\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 3;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context(params);\n\n\t\tcontext.SetCurrentTemporalLayer(3);\n\t\tcontext.SetTargetTemporalLayer(3);\n\n\t\tREQUIRE(payloadDescriptor->pictureId == 1);\n\n\t\tauto* payloadDescriptorHandler =\n\t\t  new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor);\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tauto forwarded = payloadDescriptorHandler->Process(&context, packet.get(), marker);\n\t\tREQUIRE(forwarded);\n\n\t\tauto encoder1 = payloadDescriptorHandler->GetEncoder();\n\t\tREQUIRE(encoder1);\n\n\t\t// Update pictureId.\n\t\tpayloadDescriptor->pictureId = 2;\n\n\t\tpacket.reset(\n\t\t  RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)));\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tforwarded = payloadDescriptorHandler->Process(&context, packet.get(), marker);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(payloadDescriptor->pictureId == 2);\n\n\t\t// encoder2 contains the pictureId value 2.\n\t\tauto encoder2 = payloadDescriptorHandler->GetEncoder();\n\t\tREQUIRE(encoder2);\n\n\t\t// Encode with encoder1.\n\t\tpacket.reset(\n\t\t  RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)));\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tpayloadDescriptorHandler->Encode(packet.get(), encoder1.get());\n\n\t\t// Parse the payload.\n\t\tauto* payloadDescriptor2 = RTC::RTP::Codecs::VP8::Parse(payload, sizeof(payload));\n\t\tREQUIRE(payloadDescriptor2);\n\t\tREQUIRE(payloadDescriptor2->pictureId == 1);\n\n\t\t// Encode with encoder2.\n\t\tpacket.reset(\n\t\t  RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)));\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tpayloadDescriptorHandler->Encode(packet.get(), encoder2.get());\n\n\t\t// Parse the payload.\n\t\tauto* payloadDescriptor3 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor3);\n\t\tREQUIRE(payloadDescriptor3->pictureId == 2);\n\n\t\tdelete payloadDescriptor3;\n\t\tdelete payloadDescriptor2;\n\t\tdelete payloadDescriptorHandler;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/Codecs/TestVP9.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/RTP/rtpCommon.hpp\"\n#include \"RTC/RTP/Codecs/VP9.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nnamespace\n{\n\tRTC::RTP::Codecs::VP9::PayloadDescriptor* createVP9PayloadDescriptor(\n\t  uint8_t* buffer, size_t bufferLen, uint16_t pictureId, uint8_t tlIndex)\n\t{\n\t\tbuffer[0]             = 0xAD; // I, L, B, E bits\n\t\tuint16_t netPictureId = htons(pictureId);\n\t\tstd::memcpy(buffer + 1, &netPictureId, 2);\n\t\tbuffer[1] |= 0x80;\n\t\tbuffer[3] = (tlIndex << 5) | (1 << 4); // tlIndex, switchingUpPoint\n\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::VP9::Parse(buffer, bufferLen);\n\n\t\tREQUIRE(payloadDescriptor);\n\n\t\treturn payloadDescriptor;\n\t}\n\n\tstd::unique_ptr<RTC::RTP::Codecs::VP9::PayloadDescriptor> processVP9Packet(\n\t  RTC::RTP::Codecs::VP9::EncodingContext& context, uint16_t pictureId, uint8_t tlIndex)\n\t{\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0xAD, 0x80, 0x00, 0x00, 0x00, 0x00\n\t\t};\n\t\t// clang-format on\n\t\tbool marker;\n\t\tauto* payloadDescriptor =\n\t\t  createVP9PayloadDescriptor(payload, sizeof(payload), pictureId, tlIndex);\n\t\tstd::unique_ptr<RTC::RTP::Codecs::VP9::PayloadDescriptorHandler> payloadDescriptorHandler(\n\t\t  new RTC::RTP::Codecs::VP9::PayloadDescriptorHandler(payloadDescriptor));\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetPayload(payload, sizeof(payload));\n\n\t\tif (payloadDescriptorHandler->Process(&context, packet.get(), marker))\n\t\t{\n\t\t\treturn std::unique_ptr<RTC::RTP::Codecs::VP9::PayloadDescriptor>(\n\t\t\t  RTC::RTP::Codecs::VP9::Parse(payload, sizeof(payload)));\n\t\t}\n\n\t\treturn nullptr;\n\t}\n} // namespace\n\nSCENARIO(\"process VP9 payload descriptor\", \"[rtp][codecs][vp9]\")\n{\n\tconstexpr uint16_t MaxPictureId = (1 << 15) - 1;\n\n\tSECTION(\"drop packets that belong to other temporal layers after rolling over pictureID\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 1;\n\t\tparams.temporalLayers = 3;\n\n\t\tRTC::RTP::Codecs::VP9::EncodingContext context(params);\n\t\tcontext.SyncRequired();\n\t\tcontext.SetCurrentTemporalLayer(0);\n\t\tcontext.SetTargetTemporalLayer(0);\n\n\t\tcontext.SetCurrentSpatialLayer(0);\n\t\tcontext.SetTargetSpatialLayer(0);\n\n\t\t// Frame 1.\n\t\tauto forwarded = processVP9Packet(context, MaxPictureId, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == MaxPictureId);\n\n\t\t// Frame 2.\n\t\tforwarded = processVP9Packet(context, 0, 0);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(forwarded->pictureId == 0);\n\n\t\t// Frame 3.\n\t\tforwarded = processVP9Packet(context, 1, 1);\n\t\tREQUIRE(!forwarded);\n\t}\n\n\tSECTION(\"PayloadDescriptorHandler\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 1;\n\t\tparams.temporalLayers = 3;\n\n\t\tRTC::RTP::Codecs::VP9::EncodingContext context(params);\n\n\t\tconst uint16_t start = MaxPictureId - 2000;\n\n\t\tcontext.SetCurrentTemporalLayer(0, start + 0);\n\t\tcontext.SetCurrentTemporalLayer(1, start + 1);\n\t\tcontext.SetCurrentTemporalLayer(2, start + 5);\n\t\tcontext.SetCurrentTemporalLayer(0, start + 6);\n\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 0) == 0);\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 1) == 1);\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 2) == 1);\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 5) == 2);\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 6) == 0);\n\n\t\tcontext.SetCurrentTemporalLayer(1, start + 1000);\n\t\tcontext.SetCurrentTemporalLayer(2, start + 1001); // This will drop the first item.\n\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 1000) == 1);\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(start + 0) == 1); // It will get the item at start+1.\n\n\t\tcontext.SetCurrentTemporalLayer(0, 0); // This will drop items from start to start+999.\n\n\t\tREQUIRE(context.GetTemporalLayerForPictureId(0) == 0);\n\t\tREQUIRE(\n\t\t  context.GetTemporalLayerForPictureId(start + 0) == 1); // It will get the item at start+1000.\n\t}\n\n\tSECTION(\"drop packets that belong to other temporal layers with unordered pictureID\")\n\t{\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 1;\n\t\tparams.temporalLayers = 3;\n\n\t\tRTC::RTP::Codecs::VP9::EncodingContext context(params);\n\t\tcontext.SyncRequired();\n\t\tcontext.SetCurrentSpatialLayer(0, 0);\n\t\tcontext.SetTargetSpatialLayer(0);\n\n\t\tconst uint16_t start                                                     = MaxPictureId - 20;\n\t\tconst std::vector<std::tuple<uint16_t, uint16_t, int16_t, bool>> packets = {\n\t\t\t// targetTemporalLayer=0\n\t\t\t{ start,      0, 0,  true  },\n\t\t\t{ start,      1, -1, false },\n\t\t\t{ start + 1,  0, -1, true  },\n\t\t\t{ start + 1,  1, -1, false },\n\t\t\t{ start + 2,  0, -1, true  },\n\t\t\t{ start + 2,  1, -1, false },\n\t\t\t// targetTemporalLayer=1\n\t\t\t{ start + 10, 0, 1,  true  },\n\t\t\t{ start + 10, 1, -1, true  },\n\t\t\t{ start + 11, 0, -1, true  },\n\t\t\t{ start + 11, 1, -1, true  },\n\t\t\t{ start + 3,  0, -1, true  }, // old packet\n\t\t\t{ start + 3,  1, -1, false },\n\t\t\t{ start + 12, 0, -1, true  },\n\t\t\t{ start + 12, 1, -1, true  },\n\t\t\t// targetTemporalLayer=0\n\t\t\t{ start + 14, 0, 0,  true  },\n\t\t\t{ start + 14, 1, -1, false },\n\t\t\t{ start + 13, 0, -1, true  }, // old packet\n\t\t\t{ start + 13, 1, -1, true  },\n\t\t\t// targetTemporalLayer=1\n\t\t\t{ start + 15, 0, 1,  true  },\n\t\t\t{ start + 15, 1, -1, true  },\n\t\t\t// targetTemporalLayer=0\n\t\t\t{ 0,          0, 0,  true  },\n\t\t\t{ 0,          1, -1, false },\n\t\t\t{ 1,          0, -1, true  },\n\t\t\t{ 1,          1, -1, false },\n\t\t\t{ start + 16, 0, -1, true  }, // old packet\n\t\t\t{ start + 16, 1, -1, true  },\n\t\t};\n\n\t\tfor (const auto& packet : packets)\n\t\t{\n\t\t\tauto pictureId           = std::get<0>(packet);\n\t\t\tauto tlIndex             = std::get<1>(packet);\n\t\t\tauto targetTemporalLayer = std::get<2>(packet);\n\t\t\tauto shouldForward       = std::get<3>(packet);\n\n\t\t\tif (targetTemporalLayer >= 0)\n\t\t\t{\n\t\t\t\tcontext.SetTargetTemporalLayer(targetTemporalLayer);\n\t\t\t}\n\n\t\t\tauto forwarded = processVP9Packet(context, pictureId, tlIndex);\n\n\t\t\tif (shouldForward)\n\t\t\t{\n\t\t\t\tREQUIRE(forwarded);\n\t\t\t\tREQUIRE(forwarded->pictureId == pictureId);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tREQUIRE(!forwarded);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestPacket.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"test/include/RTC/RTP/rtpCommon.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <string>\n\nSCENARIO(\"RTP Packet\", \"[serializable][rtp][packet]\")\n{\n\trtpCommon::ResetBuffers();\n\n\tSECTION(\"alignof() RTP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::RTP::Packet::FixedHeader) == 4);\n\t\tREQUIRE(alignof(RTC::RTP::Packet::HeaderExtension) == 2);\n\t\tREQUIRE(alignof(RTC::RTP::Packet::OneByteExtension) == 1);\n\t\tREQUIRE(alignof(RTC::RTP::Packet::TwoBytesExtension) == 1);\n\t}\n\n\tSECTION(\"Packet::Parse() packet1.raw succeeds\")\n\t{\n\t\talignas(4) uint8_t buffer[65536];\n\t\tsize_t bufferLength;\n\n\t\tif (!helpers::readBinaryFile(\"data/packet1.raw\", buffer, std::addressof(bufferLength)))\n\t\t{\n\t\t\tFAIL(\"cannot open file\");\n\t\t}\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ bufferLength,\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 23617,\n\t\t  /*timestamp*/ 1660241882,\n\t\t  /*ssrc*/ 2674985186,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 4,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 33,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 23617,\n\t\t  /*timestamp*/ 1660241882,\n\t\t  /*ssrc*/ 2674985186,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 4,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 33,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 23617,\n\t\t  /*timestamp*/ 1660241882,\n\t\t  /*ssrc*/ 2674985186,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 4,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 33,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 16);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength - 33 + 16,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 23617,\n\t\t  /*timestamp*/ 1660241882,\n\t\t  /*ssrc*/ 2674985186,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 4,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 16,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true);\n\t}\n\n\tSECTION(\"Packet::Parse() packet2.raw succeeds\")\n\t{\n\t\talignas(4) uint8_t buffer[65536];\n\t\tsize_t bufferLength;\n\n\t\tif (!helpers::readBinaryFile(\"data/packet2.raw\", buffer, std::addressof(bufferLength)))\n\t\t{\n\t\t\tFAIL(\"cannot open file\");\n\t\t}\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ bufferLength,\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 28478,\n\t\t  /*timestamp*/ 172320136,\n\t\t  /*ssrc*/ 3316375386,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 78,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 149);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 28478,\n\t\t  /*timestamp*/ 172320136,\n\t\t  /*ssrc*/ 3316375386,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 78,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 149);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 28478,\n\t\t  /*timestamp*/ 172320136,\n\t\t  /*ssrc*/ 3316375386,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 78,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 149);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 16);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength - 78 + 16 - 149,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 28478,\n\t\t  /*timestamp*/ 172320136,\n\t\t  /*ssrc*/ 3316375386,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 16,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true);\n\t}\n\n\tSECTION(\"Packet::Parse() packet3.raw succeeds\")\n\t{\n\t\talignas(4) uint8_t buffer[65536];\n\t\tsize_t bufferLength;\n\n\t\tif (!helpers::readBinaryFile(\"data/packet3.raw\", buffer, std::addressof(bufferLength)))\n\t\t{\n\t\t\tFAIL(\"cannot open file\");\n\t\t}\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ bufferLength,\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 19354,\n\t\t  /*timestamp*/ 863466045,\n\t\t  /*ssrc*/ 235797202,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 8,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 77,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 19354,\n\t\t  /*timestamp*/ 863466045,\n\t\t  /*ssrc*/ 235797202,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 8,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 77,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 19354,\n\t\t  /*timestamp*/ 863466045,\n\t\t  /*ssrc*/ 235797202,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 8,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 77,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 16);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ bufferLength - 77 + 16,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 19354,\n\t\t  /*timestamp*/ 863466045,\n\t\t  /*ssrc*/ 235797202,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 8,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 16,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true);\n\t}\n\n\tSECTION(\"Packet::Parse() without extensions or payload succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x80, 0x01, 0x00, 0x08,\n\t\t\t0x00, 0x00, 0x00, 0x04,\n\t\t\t0x00, 0x00, 0x00, 0x05\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 16);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer) + 16,\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 16,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\t}\n\n\tSECTION(\"Packet::Parse() with One-Byte extensions succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x90, 0x01, 0x00, 0x08,\n\t\t\t0x00, 0x00, 0x00, 0x04,\n\t\t\t0x00, 0x00, 0x00, 0x05,\n\t\t\t0xbe, 0xde, 0x00, 0x03, // Header Extension\n\t\t\t0x10, 0xaa, 0x21, 0xbb, // - id: 1, len: 1\n\t\t\t0xff, 0x00, 0x00, 0x33, // - id: 2, len: 2\n\t\t\t0xff, 0xff, 0xff, 0xff, // - id: 3, len: 4\n\t\t\t0x12, 0x23\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 2,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tuint8_t* extensionValue;\n\t\tuint8_t extensionLen;\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, buffer + 17, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, buffer + 19, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 4);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 4, buffer + 24, 4) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == false);\n\t\tREQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 2,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::SerializeBuffer + 17, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::SerializeBuffer + 19, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 4);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 4, rtpCommon::SerializeBuffer + 24, 4) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == false);\n\t\tREQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 2,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::CloneBuffer + 17, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::CloneBuffer + 19, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 4);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 4, rtpCommon::CloneBuffer + 24, 4) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == false);\n\t\tREQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 16);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer) - 2 + 16,\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 16,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\t}\n\n\tSECTION(\"Packet::Parse() with Two-Bytes extensions succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x90, 0x01, 0x00, 0x08,\n\t\t\t0x00, 0x00, 0x00, 0x04,\n\t\t\t0x00, 0x00, 0x00, 0x05,\n\t\t\t0x10, 0x00, 0x00, 0x04, // Header Extension\n\t\t\t0x00, 0x00, 0x01, 0x00, // - id: 1, len: 0\n\t\t\t0x02, 0x01, 0x42, 0x00, // - id: 2, len: 1\n\t\t\t0x03, 0x02, 0x11, 0x22, // - id: 3, len: 2\n\t\t\t0x00, 0x00, 0x04, 0x00  // - id: 4, len: 0\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 16,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tuint8_t* extensionValue;\n\t\tuint8_t extensionLen;\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, buffer + 22, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, buffer + 26, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == true);\n\t\textensionValue = packet->GetExtensionValue(4, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(5) == false);\n\t\tREQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 16,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::SerializeBuffer + 22, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::SerializeBuffer + 26, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == true);\n\t\textensionValue = packet->GetExtensionValue(4, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(5) == false);\n\t\tREQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 16,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionLen == 1);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::CloneBuffer + 22, 1) == true);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionLen == 2);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::CloneBuffer + 26, 2) == true);\n\n\t\tREQUIRE(packet->HasExtension(4) == true);\n\t\textensionValue = packet->GetExtensionValue(4, extensionLen);\n\t\tREQUIRE(extensionLen == 0);\n\n\t\tREQUIRE(packet->HasExtension(5) == false);\n\t\tREQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 15);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer) + 15,\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 8,\n\t\t  /*timestamp*/ 4,\n\t\t  /*ssrc*/ 5,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 16,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 15,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 15) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\t}\n\n\tSECTION(\"Packet::Parse() padding-only packet succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0xA0, 0x01, 0x00, 0x09,\n\t\t\t0x00, 0x00, 0x00, 0x05,\n\t\t\t0x00, 0x00, 0x00, 0x06,\n\t\t\t0x00, 0x00, 0x00, 0x00, // Padding (8 bytes)\n\t\t\t0x00, 0x00, 0x00, 0x08\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 9,\n\t\t  /*timestamp*/ 5,\n\t\t  /*ssrc*/ 6,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 8);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 9,\n\t\t  /*timestamp*/ 5,\n\t\t  /*ssrc*/ 6,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 8);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ sizeof(buffer),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 9,\n\t\t  /*timestamp*/ 5,\n\t\t  /*ssrc*/ 6,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 8);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 1);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ packet->GetLength(),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 9,\n\t\t  /*timestamp*/ 5,\n\t\t  /*ssrc*/ 6,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t/* Pad to 4 bytes. */\n\n\t\tpacket->PadTo4Bytes();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ packet->GetLength(),\n\t\t  /*payloadType*/ 1,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 9,\n\t\t  /*timestamp*/ 5,\n\t\t  /*ssrc*/ 6,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\t}\n\n\tSECTION(\"Packet::Parse() with wrong arguments fails\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t0x90, 0x01, 0x00, 0x08,\n\t\t\t0x00, 0x00, 0x00, 0x04,\n\t\t\t0x00, 0x00, 0x00, 0x05,\n\t\t\t0xbe, 0xde, 0x00, 0x03, // Header Extension\n\t\t\t0x10, 0xaa, 0x21, 0xbb, // - id: 1, len: 1\n\t\t\t0xff, 0x00, 0x00, 0x33, // - id: 2, len: 2\n\t\t\t0xff, 0xff, 0xff, 0xff, // - id: 3, len: 4\n\t\t\t0x12, 0x23\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ nullptr };\n\n\t\t// bufferLength is lower than packetLen.\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet.reset(RTC::RTP::Packet::Parse(buffer, sizeof(buffer), sizeof(buffer) - 1)),\n\t\t  MediaSoupTypeError);\n\t\tREQUIRE(!packet);\n\t}\n\n\tSECTION(\"Packet::Factory() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 0,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\tpacket->SetPayloadType(100);\n\t\tpacket->SetMarker(true);\n\t\tpacket->SetSequenceNumber(12345);\n\t\tpacket->SetTimestamp(987654321);\n\t\tpacket->SetSsrc(1234567890);\n\n\t\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\n\t\t// Extensions:\n\t\t//\n\t\t// Using One-Byte Extensions:\n\t\t// - Header Extension value length: 1 + 1 + 1 + 2 + 1 + 3 = 9 => 12 (padded)\n\t\t// - Header Extension length: 4 + 12 = 16\n\t\t//\n\t\t// Using Two-Bytes Extensions:\n\t\t// - Header Extension value length: 2 + 1 + 2 + 2 + 2 + 3 = 12\n\t\t// - Header Extension length: 4 + 12 = 16\n\t\t//\n\t\t// Extension id 1.\n\t\trtpCommon::DataBuffer[0] = 11;\n\t\t// Extension id 2.\n\t\trtpCommon::DataBuffer[1] = 22;\n\t\trtpCommon::DataBuffer[2] = 0xAA;\n\t\t// Extension id 14.\n\t\trtpCommon::DataBuffer[3] = 14;\n\t\trtpCommon::DataBuffer[4] = 0xBB;\n\t\trtpCommon::DataBuffer[5] = 0xCC;\n\n\t\textensions.emplace_back(\n\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::MID,\n\t\t  /*id*/ 1,\n\t\t  /*len*/ 1,\n\t\t  /*value*/ rtpCommon::DataBuffer + 0);\n\n\t\textensions.emplace_back(\n\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID,\n\t\t  /*id*/ 2,\n\t\t  /*len*/ 2,\n\t\t  /*value*/ rtpCommon::DataBuffer + 1);\n\n\t\textensions.emplace_back(\n\t\t  /*type*/ RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID,\n\t\t  /*id*/ 14,\n\t\t  /*len*/ 3,\n\t\t  /*value*/ rtpCommon::DataBuffer + 3);\n\n\t\t// Add One-Byte Extensions.\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 10);\n\t\tpacket->PadTo4Bytes(); // payload + padding = 12.\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tconst uint8_t* extensionValue;\n\t\tuint8_t extensionLen;\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer)));\n\n\t\tstd::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 1);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t/* Pad to 4 bytes. */\n\n\t\tpacket->PadTo4Bytes();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1 + 3,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 3);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Remove Header Extension. */\n\n\t\tpacket->RemoveHeaderExtension();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 1 + 3,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 3);\n\n\t\tREQUIRE(packet->HasExtension(1) == false);\n\t\tREQUIRE(packet->HasExtension(2) == false);\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\t\tREQUIRE(packet->HasExtension(14) == false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t// Add Two-Bytes Extensions.\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::CloneBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1 + 3,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ true,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 1,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 3);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == false);\n\n\t\tREQUIRE(packet->HasExtension(14) == true);\n\t\textensionValue = packet->GetExtensionValue(14, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]);\n\t\tREQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]);\n\t\tREQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\t}\n\n\tSECTION(\"Packet::SetExtensions() with ExtensionsType::Auto selects best type\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\n\t\t// Can fit into One-Byte type Extensions.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,           1,  1,  rtpCommon::DataBuffer },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 14, 16, rtpCommon::DataBuffer }\n    });\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\t\tREQUIRE(packet->HasOneByteExtensions());\n\n\t\t// Requires Two-Bytes type Extensions due to id > 14.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, 15, 2, rtpCommon::DataBuffer }\n    });\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\t\tREQUIRE(packet->HasTwoBytesExtensions());\n\n\t\t// Requires Two-Bytes type Extensions due to length 0.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, 1, 0, rtpCommon::DataBuffer }\n    });\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\t\tREQUIRE(packet->HasTwoBytesExtensions());\n\n\t\t// Requires Two-Bytes type Extensions due to length > 16.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET, 1, 17, rtpCommon::DataBuffer }\n    });\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions);\n\t\tREQUIRE(packet->HasTwoBytesExtensions());\n\t}\n\n\tSECTION(\"Packet::SetExtensions() with supported extensions succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\n\t\tstd::string mid{ \"mid-€1\" };\n\t\tstd::string rid{ \"r1-ß\" };\n\t\tuint32_t absSendtime{ 12345678 };\n\t\tuint16_t wideSeqNumber{ 5555 };\n\t\tuint8_t absSendtimeValue[100]{};\n\t\tuint8_t wideSeqNumberValue[100]{};\n\n\t\tUtils::Byte::Set3Bytes(absSendtimeValue, 0, absSendtime);\n\t\tUtils::Byte::Set2Bytes(wideSeqNumberValue, 0, wideSeqNumber);\n\n\t\t// clang-format off\n\t\textensions.assign(\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tRTC::RtpHeaderExtensionUri::Type::MID,\n\t\t\t\t\t1,\n\t\t\t\t\tstatic_cast<uint8_t>(mid.size()),\n\t\t\t\t\treinterpret_cast<uint8_t*>(mid.data())\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tRTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID,\n\t\t\t\t\t2,\n\t\t\t\t\tstatic_cast<uint8_t>(rid.size()),\n\t\t\t\t\treinterpret_cast<uint8_t*>(rid.data())\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tRTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME,\n\t\t\t\t\t3,\n\t\t\t\t\t3,\n\t\t\t\t\tabsSendtimeValue\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tRTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01,\n\t\t\t\t\t4,\n\t\t\t\t\t2,\n\t\t\t\t\twideSeqNumberValue\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\n\t\tREQUIRE(packet->HasOneByteExtensions());\n\n\t\tstd::string readMid;\n\t\tstd::string readRid;\n\t\tuint32_t readAbsSendtime;\n\t\tuint16_t readWideSeqNumber;\n\n\t\tREQUIRE(packet->ReadMid(readMid));\n\t\tREQUIRE(readMid == mid);\n\t\tREQUIRE(packet->ReadRid(readRid));\n\t\tREQUIRE(readRid == rid);\n\t\tREQUIRE(packet->ReadAbsSendTime(readAbsSendtime));\n\t\tREQUIRE(readAbsSendtime == absSendtime);\n\t\tREQUIRE(packet->ReadTransportWideCc01(readWideSeqNumber));\n\t\tREQUIRE(readWideSeqNumber == wideSeqNumber);\n\n\t\tstd::string newMid{ \"mid-®2\" };\n\t\tuint64_t newAbsSendtimeMs{ 999999 };\n\t\tuint16_t newWideSeqNumber{ 5556 };\n\n\t\tREQUIRE(packet->UpdateMid(newMid));\n\t\tREQUIRE(packet->UpdateAbsSendTime(newAbsSendtimeMs));\n\t\tREQUIRE(packet->UpdateTransportWideCc01(newWideSeqNumber));\n\n\t\tREQUIRE(packet->ReadMid(readMid));\n\t\tREQUIRE(readMid == newMid);\n\t\tREQUIRE(packet->ReadRid(readRid));\n\t\tREQUIRE(readRid == rid);\n\t\tREQUIRE(packet->ReadAbsSendTime(readAbsSendtime));\n\t\tREQUIRE(readAbsSendtime == Utils::Time::TimeMsToAbsSendTime(newAbsSendtimeMs));\n\t\tREQUIRE(packet->ReadTransportWideCc01(readWideSeqNumber));\n\t\tREQUIRE(readWideSeqNumber == newWideSeqNumber);\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet2{ RTC::RTP::Packet::Parse(\n\t\t\tpacket->GetBuffer(), packet->GetLength()) };\n\n\t\tREQUIRE(packet2);\n\t\tREQUIRE(packet2->Validate(/*storeExtensions*/ false));\n\n\t\tpacket2->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tRTC::RTP::HeaderExtensionIds headerExtensionIds{};\n\n\t\theaderExtensionIds.mid               = 1;\n\t\theaderExtensionIds.rid               = 2;\n\t\theaderExtensionIds.absSendTime       = 3;\n\t\theaderExtensionIds.transportWideCc01 = 4;\n\n\t\tpacket2->AssignExtensionIds(headerExtensionIds);\n\n\t\tREQUIRE(packet2->HasOneByteExtensions());\n\t\tREQUIRE(packet2->ReadMid(readMid));\n\t\tREQUIRE(readMid == newMid);\n\t\tREQUIRE(packet2->ReadRid(readRid));\n\t\tREQUIRE(readRid == rid);\n\t\tREQUIRE(packet2->ReadAbsSendTime(readAbsSendtime));\n\t\tREQUIRE(readAbsSendtime == Utils::Time::TimeMsToAbsSendTime(newAbsSendtimeMs));\n\t\tREQUIRE(packet2->ReadTransportWideCc01(readWideSeqNumber));\n\t\tREQUIRE(readWideSeqNumber == newWideSeqNumber);\n\t}\n\n\tSECTION(\"Packet::SetExtensions() fails if wrong extensions are given\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetPayload(rtpCommon::DataBuffer, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\t\tauto* d = rtpCommon::DataBuffer;\n\n\t\t// Invalid Extension id 0.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,           0, 4, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 1, 1, d }\n    });\n\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions),\n\t\t  MediaSoupTypeError);\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions),\n\t\t  MediaSoupTypeError);\n\n\t\t// Invalid Extension id > 14 in One-Byte.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION, 15, 2, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,               6,  6, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL,  7,  7, d }\n    });\n\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions),\n\t\t  MediaSoupTypeError);\n\t\tREQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions));\n\n\t\t// Invalid Extension length 0 in One-Byte.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,                    3, 0, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, 6, 6, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID,          7, 7, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL,       8, 8, d }\n    });\n\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions),\n\t\t  MediaSoupTypeError);\n\t\tREQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions));\n\n\t\t// Invalid Extension length > 16 in One-Byte.\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID,   3,   17, d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,                   6,   6,  d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION,     7,   7,  d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR, 8,   8,  d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY,         9,   9,  d },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME,      100, 10, d }\n    });\n\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions),\n\t\t  MediaSoupTypeError);\n\t\tREQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 4 + 72 + 10 + 2,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 0,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 72,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ true,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tconst uint8_t* extensionValue;\n\t\tuint8_t extensionLen;\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 17);\n\n\t\tREQUIRE(packet->HasExtension(1) == false);\n\n\t\tREQUIRE(packet->HasExtension(6) == true);\n\t\textensionValue = packet->GetExtensionValue(6, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 6);\n\n\t\tREQUIRE(packet->HasExtension(7) == true);\n\t\textensionValue = packet->GetExtensionValue(7, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 7);\n\n\t\tREQUIRE(packet->HasExtension(8) == true);\n\t\textensionValue = packet->GetExtensionValue(8, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 8);\n\n\t\tREQUIRE(packet->HasExtension(9) == true);\n\t\textensionValue = packet->GetExtensionValue(9, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 9);\n\n\t\tREQUIRE(packet->HasExtension(100) == true);\n\t\textensionValue = packet->GetExtensionValue(100, extensionLen);\n\t\tREQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]);\n\t\tREQUIRE(extensionLen == 10);\n\n\t\tREQUIRE(packet->HasExtension(101) == false);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 10) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\t}\n\n\tSECTION(\"Packet::SetPayload(), SetPayloadLength() and packet::RemovePayload() succeed\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\t/* Set payload. */\n\n\t\tpacket->SetPayload(payload, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 0,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\t/* Set payload length. */\n\n\t\tpacket->SetPayloadLength(501);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 501,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 0,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 501,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t/* Remove payload. */\n\n\t\t// This method removes padding.\n\t\tpacket->RemovePayload();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 0,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ false,\n\t\t  /*payloadLength*/ 0,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\t// Invalid arguments.\n\t\tREQUIRE_THROWS_AS(packet->SetPayload(nullptr, 2), MediaSoupTypeError);\n\t}\n\n\tSECTION(\"Packet::ShiftPayload() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetSsrc(12344321);\n\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tpacket->SetPayload(payload, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\tstd::vector<RTC::RTP::Packet::Extension> extensions;\n\n\t\t// One-Byte Extensions:\n\t\t// - Header Extension value length: 1 + 1 + 1 + 2 + 1 + 3 = 9 => 12 (padded)\n\t\t// - Header Extension length: 4 + 12 = 16\n\t\t//\n\t\t// clang-format off\n\t\tuint8_t extension1[] =\n\t\t{\n\t\t\t0x12\n\t\t};\n\t\tuint8_t extension2[] =\n\t\t{\n\t\t\t0x34, 0x56\n\t\t};\n\t\tuint8_t extension3[] =\n\t\t{\n\t\t\t0x78, 0x9A, 0xBC\n\t\t};\n\t\t// clang-format on\n\n\t\textensions.assign(\n\t\t  {\n\t\t    { RTC::RtpHeaderExtensionUri::Type::MID,                  1, 1, extension1 },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME,        2, 2, extension2 },\n\t\t    { RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01, 3, 3, extension3 }\n    });\n\n\t\tpacket->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions);\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 12344321,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tconst uint8_t* extensionValue;\n\t\tuint8_t extensionLen;\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(packet->GetPayload(), packet->GetPayloadLength(), payload, 10) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* Shift payload. */\n\n\t\t// This method removes padding.\n\t\tpacket->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ 1);\n\n\t\t// Fill the new byte in the payload with 0XFF.\n\t\tpacket->GetPayload()[2] = 0xFF;\n\n\t\t// clang-format off\n\t\tuint8_t shiftedPayload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0xFF, 0x33,\n\t\t\t0x44,\t0x55, 0x66, 0x77,\n\t\t\t0x88, 0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 1,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 12344321,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 11,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), shiftedPayload, 11) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\n\t\t// Reset the payload and padding.\n\t\tpacket->SetPayload(payload, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\t/* Unshift payload. */\n\n\t\t// This method removes padding.\n\t\tpacket->ShiftPayload(/*payloadOffset*/ 4, /*delta*/ -2);\n\n\t\t// clang-format off\n\t\tuint8_t unshiftedPayload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x77, 0x88, 0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 - 2,\n\t\t  /*payloadType*/ 0,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 0,\n\t\t  /*timestamp*/ 0,\n\t\t  /*ssrc*/ 12344321,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ true,\n\t\t  /*headerExtensionValueLength*/ 12,\n\t\t  /*hasOneByteExtensions*/ true,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 8,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->HasExtension(1) == true);\n\t\textensionValue = packet->GetExtensionValue(1, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true);\n\t\tREQUIRE(extensionLen == 1);\n\n\t\tREQUIRE(packet->HasExtension(2) == true);\n\t\textensionValue = packet->GetExtensionValue(2, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true);\n\t\tREQUIRE(extensionLen == 2);\n\n\t\tREQUIRE(packet->HasExtension(3) == true);\n\t\textensionValue = packet->GetExtensionValue(3, extensionLen);\n\t\tREQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true);\n\t\tREQUIRE(extensionLen == 3);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    packet->GetPayload(), packet->GetPayloadLength(), unshiftedPayload, 8) == true);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t// Reset the payload and padding.\n\t\tpacket->SetPayload(payload, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\t/* Shitf and unshift (undo). */\n\n\t\tpacket->ShiftPayload(/*payloadOffset*/ 3, /*delta*/ 5);\n\t\tpacket->ShiftPayload(/*payloadOffset*/ 3, /*delta*/ -5);\n\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(packet->GetPayload(), packet->GetPayloadLength(), payload, 10) == true);\n\t}\n\n\tSECTION(\"Packet::ShiftPayload() fails if wrong values are given\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tpacket->SetPayload(payload, 10);\n\n\t\t// payloadOffset higger or equal than payload length.\n\t\tREQUIRE_THROWS_AS(packet->ShiftPayload(/*payloadOffset*/ 10, /*delta*/ 2), MediaSoupTypeError);\n\n\t\t// delta too big.\n\t\tREQUIRE_THROWS_AS(packet->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ -9), MediaSoupTypeError);\n\n\t\t// New computed payload length too big.\n\t\tREQUIRE_THROWS_AS(\n\t\t  packet->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ packet->GetBufferLength()),\n\t\t  MediaSoupTypeError);\n\t}\n\n\tSECTION(\"Packet::RtxEncode() and packet::RtxDecode() succeed\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tpacket->SetPayloadType(100);\n\t\tpacket->SetSequenceNumber(12345);\n\t\tpacket->SetTimestamp(987654321);\n\t\tpacket->SetSsrc(1234567890);\n\n\t\t// clang-format off\n\t\tuint8_t payload[] =\n\t\t{\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t0x99, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tpacket->SetPayload(payload, 10);\n\t\tpacket->PadTo4Bytes();\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ true,\n\t\t  /*paddingLength*/ 2);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* RTX encode. */\n\n\t\t// This method removes padding.\n\t\tpacket->RtxEncode(/*payloadType*/ 111, /*ssrc*/ 999999, /*seq*/ 666);\n\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2,\n\t\t  /*payloadType*/ 111,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 666,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 999999,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 12,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == true);\n\n\t\t/* RTX decode. */\n\n\t\t// This method removes padding.\n\t\tpacket->RtxDecode(/*payloadType*/ 100, /*ssrc*/ 1234567890);\n\n\t\tREQUIRE(packet->Validate(/*storeExtensions*/ false));\n\n\t\tCHECK_RTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ rtpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer),\n\t\t  /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10,\n\t\t  /*payloadType*/ 100,\n\t\t  /*hasMarker*/ false,\n\t\t  /*seqNumber*/ 12345,\n\t\t  /*timestamp*/ 987654321,\n\t\t  /*ssrc*/ 1234567890,\n\t\t  /*hasCsrcs*/ false,\n\t\t  /*hasHeaderExtension*/ false,\n\t\t  /*headerExtensionValueLength*/ 0,\n\t\t  /*hasOneByteExtensions*/ false,\n\t\t  /*hasTwoBytesExtensions*/ false,\n\t\t  /*hasPayload*/ true,\n\t\t  /*payloadLength*/ 10,\n\t\t  /*hasPadding*/ false,\n\t\t  /*paddingLength*/ 0);\n\n\t\tREQUIRE(packet->IsPaddedTo4Bytes() == false);\n\t}\n\n\tSECTION(\"Packet::SetBufferReleasedListener() when Packet is destroyed succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tbool packetBufferReleased{ false };\n\n\t\tRTC::Serializable::BufferReleasedListener packetBufferReleasedListener =\n\t\t  [&packetBufferReleased](const RTC::Serializable* serializable, const uint8_t* serializableBuffer)\n\t\t{\n\t\t\tif (serializable->GetBuffer() == rtpCommon::FactoryBuffer && serializable->GetBuffer() == serializableBuffer)\n\t\t\t{\n\t\t\t\tpacketBufferReleased = true;\n\t\t\t}\n\t\t};\n\n\t\tpacket->SetBufferReleasedListener(std::addressof(packetBufferReleasedListener));\n\n\t\t// If we destroy the Packet it should invoke the listener.\n\t\tpacket.reset(nullptr);\n\n\t\tREQUIRE(packetBufferReleased == true);\n\t}\n\n\tSECTION(\"Packet::SetBufferReleasedListener() when Packet is serialized into another buffer succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Factory(\n\t\t\trtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) };\n\n\t\tREQUIRE(packet);\n\n\t\tbool packetBufferReleased{ false };\n\n\t\tRTC::Serializable::BufferReleasedListener packetBufferReleasedListener =\n\t\t  [&packetBufferReleased](const RTC::Serializable* serializable, const uint8_t* serializableBuffer)\n\t\t{\n\t\t\tif (serializable->GetBuffer() == rtpCommon::FactoryBuffer && serializable->GetBuffer() == serializableBuffer)\n\t\t\t{\n\t\t\t\tpacketBufferReleased = true;\n\t\t\t}\n\t\t};\n\n\t\tpacket->SetBufferReleasedListener(std::addressof(packetBufferReleasedListener));\n\n\t\t// If we serialize the Packet into another buffer it should invoke the\n\t\t// listener.\n\t\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\t\tREQUIRE(packetBufferReleased == true);\n\n\t\t// NOTE: We need to unset the buffer released listener because once the\n\t\t// unique_ptr of the Packet gets out of the scope, the Packet will be\n\t\t// deallocated and will invoke the buffer released listener, which at\n\t\t// that time is already out of the scope (it's lifetime ended) so it's\n\t\t// been destroyed.\n\t\tpacket->SetBufferReleasedListener(nullptr);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestProbationGenerator.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTP/ProbationGenerator.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"RTP ProbationGenerator\", \"[rtp][probation-generator]\")\n{\n\tSECTION(\"ProbationGenerator generates RTP Packets of the requested length\")\n\t{\n\t\tRTC::RTP::ProbationGenerator probationGenerator;\n\n\t\tconst auto* packet = probationGenerator.GetNextPacket(1000);\n\t\tconst auto seq     = packet->GetSequenceNumber();\n\n\t\tREQUIRE(packet->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc);\n\t\tREQUIRE(packet->GetPayloadType() == RTC::RTP::ProbationGenerator::PayloadType);\n\t\tREQUIRE(packet->GetLength() == 1000);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes());\n\n\t\t// If given length is higher than ProbationGenerator::ProbationPacketMaxLength\n\t\t// then that limit value is used instead.\n\t\tpacket =\n\t\t  probationGenerator.GetNextPacket(RTC::RTP::ProbationGenerator::ProbationPacketMaxLength + 10);\n\n\t\tREQUIRE(packet->GetSequenceNumber() == seq + 1);\n\t\tREQUIRE(packet->GetLength() == RTC::RTP::ProbationGenerator::ProbationPacketMaxLength);\n\t\tREQUIRE(packet->IsPaddedTo4Bytes());\n\n\t\t// If given length is less than probation packet minimum length, then that\n\t\t// limit value is used instead.\n\t\tpacket = probationGenerator.GetNextPacket(probationGenerator.GetProbationPacketMinLength() - 10);\n\n\t\tREQUIRE(packet->GetSequenceNumber() == seq + 2);\n\t\tREQUIRE(packet->GetLength() == probationGenerator.GetProbationPacketMinLength());\n\t\tREQUIRE(packet->IsPaddedTo4Bytes());\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestRetransmissionBuffer.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RetransmissionBuffer.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <vector>\n\nSCENARIO(\"RTP RetransmissionBuffer\", \"[rtp][rtx]\")\n{\n\t// Class inheriting from RtpRetransmissionBuffer so we can access its protected\n\t// buffer member.\n\tclass RtpMyRetransmissionBuffer : public RTC::RTP::RetransmissionBuffer\n\t{\n\tpublic:\n\t\tstruct VerificationItem\n\t\t{\n\t\t\tbool isPresent;\n\t\t\tuint16_t sequenceNumber;\n\t\t\tuint32_t timestamp;\n\t\t};\n\n\tpublic:\n\t\tRtpMyRetransmissionBuffer(uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate)\n\t\t  : RTC::RTP::RetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate)\n\t\t{\n\t\t}\n\n\tpublic:\n\t\tvoid Insert(uint16_t seq, uint32_t timestamp)\n\t\t{\n\t\t\t// clang-format off\n\t\tuint8_t rtpBuffer[] =\n\t\t{\n\t\t\t0b10000000, 0b01111011, 0b01010010, 0b00001110,\n\t\t\t0b01011011, 0b01101011, 0b11001010, 0b10110101,\n\t\t\t0, 0, 0, 2\n\t\t};\n\t\t\t// clang-format on\n\n\t\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(rtpBuffer, sizeof(rtpBuffer)) };\n\n\t\t\tpacket->SetSequenceNumber(seq);\n\t\t\tpacket->SetTimestamp(timestamp);\n\n\t\t\tconst RTC::RTP::SharedPacket sharedPacket;\n\n\t\t\tRTC::RTP::RetransmissionBuffer::Insert(packet.get(), sharedPacket);\n\t\t}\n\n\t\tvoid AssertBuffer(std::vector<VerificationItem> verificationBuffer)\n\t\t{\n\t\t\tREQUIRE(verificationBuffer.size() == this->buffer.size());\n\n\t\t\tfor (size_t idx{ 0u }; idx < verificationBuffer.size(); ++idx)\n\t\t\t{\n\t\t\t\tauto& verificationItem = verificationBuffer.at(idx);\n\t\t\t\tauto* item             = this->buffer.at(idx);\n\n\t\t\t\tREQUIRE(verificationItem.isPresent == !!item);\n\n\t\t\t\tif (item)\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(verificationItem.sequenceNumber == item->sequenceNumber);\n\t\t\t\t\tREQUIRE(verificationItem.timestamp == item->timestamp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\tSECTION(\"proper packets received in order\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(10001, 1000000000);\n\t\tmyRetransmissionBuffer.Insert(10002, 1000000000);\n\t\tmyRetransmissionBuffer.Insert(10003, 1000000200);\n\t\tmyRetransmissionBuffer.Insert(10004, 1000000200);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 10001, 1000000000 },\n\t\t\t\t{ true, 10002, 1000000000 },\n\t\t\t\t{ true, 10003, 1000000200 },\n\t\t\t\t{ true, 10004, 1000000200 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"proper packets received out of order\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(20004, 2000000200);\n\t\tmyRetransmissionBuffer.Insert(20001, 2000000000);\n\t\tmyRetransmissionBuffer.Insert(20003, 2000000200);\n\t\tmyRetransmissionBuffer.Insert(20002, 2000000000);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 20001, 2000000000 },\n\t\t\t\t{ true, 20002, 2000000000 },\n\t\t\t\t{ true, 20003, 2000000200 },\n\t\t\t\t{ true, 20004, 2000000200 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"packet with too new sequence number produces buffer emptying\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(30001, 3000000000);\n\t\tmyRetransmissionBuffer.Insert(30002, 3000000000);\n\t\tmyRetransmissionBuffer.Insert(30003, 3000000200);\n\t\tmyRetransmissionBuffer.Insert(40000, 3000003000);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 40000, 3000003000 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"blank slots are properly created\")\n\t{\n\t\tconst uint16_t maxItems{ 10 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(40002, 4000000002);\n\t\t// Packet must be discarded since its timestamp is lower than in seq 40002.\n\t\tmyRetransmissionBuffer.Insert(40003, 4000000001);\n\t\t// Must produce 1 blank slot.\n\t\tmyRetransmissionBuffer.Insert(40004, 4000000004);\n\t\t// Discarded (duplicated).\n\t\tmyRetransmissionBuffer.Insert(40002, 4000000002);\n\t\t// Must produce 4 blank slot.\n\t\tmyRetransmissionBuffer.Insert(40008, 4000000008);\n\t\tmyRetransmissionBuffer.Insert(40006, 4000000006);\n\t\t// Must produce 1 blank slot at the front.\n\t\tmyRetransmissionBuffer.Insert(40000, 4000000000);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 40000, 4000000000 },\n\t\t\t\t{ false, 0, 0 },\n\t\t\t\t{ true, 40002, 4000000002 },\n\t\t\t\t{ false, 0, 0 },\n\t\t\t\t{ true, 40004, 4000000004 },\n\t\t\t\t{ false, 0, 0 },\n\t\t\t\t{ true, 40006, 4000000006 },\n\t\t\t\t{ false, 0, 0 },\n\t\t\t\t{ true, 40008, 4000000008 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"packet with too old sequence number is discarded\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(10001, 1000000001);\n\t\tmyRetransmissionBuffer.Insert(10002, 1000000002);\n\t\tmyRetransmissionBuffer.Insert(10003, 1000000003);\n\t\t// Too old seq.\n\t\tmyRetransmissionBuffer.Insert(40000, 1000000000);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 10001, 1000000001 },\n\t\t\t\t{ true, 10002, 1000000002 },\n\t\t\t\t{ true, 10003, 1000000003 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"packet with too old timestamp is discarded\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tauto maxDiffTs = static_cast<uint32_t>(maxRetransmissionDelayMs * clockRate / 1000);\n\n\t\tmyRetransmissionBuffer.Insert(10001, 1000000001);\n\t\tmyRetransmissionBuffer.Insert(10002, 1000000002);\n\t\tmyRetransmissionBuffer.Insert(10003, 1000000003);\n\t\t// Too old timestamp (subtract 100 to avoid math issues).\n\t\tmyRetransmissionBuffer.Insert(10000, 1000000003 - maxDiffTs - 100);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 10001, 1000000001 },\n\t\t\t\t{ true, 10002, 1000000002 },\n\t\t\t\t{ true, 10003, 1000000003 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"packet with very newest timestamp is inserted as newest item despite its seq is old\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\t// Scenario based on https://github.com/versatica/mediasoup/issues/1037.\n\n\t\tmyRetransmissionBuffer.Insert(24816, 1024930187);\n\t\tmyRetransmissionBuffer.Insert(24980, 1025106407);\n\t\tmyRetransmissionBuffer.Insert(18365, 1026593387);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 18365, 1026593387 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\n\t  \"packet with lower seq than newest packet in the buffer and higher timestamp forces buffer emptying\")\n\t{\n\t\tconst uint16_t maxItems{ 4 };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\tmyRetransmissionBuffer.Insert(33331, 1000000001);\n\t\tmyRetransmissionBuffer.Insert(33332, 1000000002);\n\t\tmyRetransmissionBuffer.Insert(33330, 1000000003);\n\n\t\t// clang-format off\n\t\tmyRetransmissionBuffer.AssertBuffer(\n\t\t\t{\n\t\t\t\t{ true, 33330, 1000000003 }\n\t\t\t}\n\t\t);\n\t\t// clang-format on\n\t}\n\n\tSECTION(\"fuzzer generated packets\")\n\t{\n\t\tconst uint16_t maxItems{ 2500u };\n\t\tconst uint32_t maxRetransmissionDelayMs{ 2000u };\n\t\tconst uint32_t clockRate{ 90000 };\n\n\t\tRtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate);\n\n\t\t// These packets reproduce an already fixed crash reported here:\n\t\t// https://github.com/versatica/mediasoup/issues/1027#issuecomment-1478464584\n\t\t// I've commented first packets and just left those that produce the crash.\n\n\t\t// myRetransmissionBuffer.Insert(14906, 976891962);\n\t\t// myRetransmissionBuffer.Insert(14906, 976891962);\n\t\t// myRetransmissionBuffer.Insert(14906, 976892730);\n\t\t// myRetransmissionBuffer.Insert(13157, 862283031);\n\t\t// myRetransmissionBuffer.Insert(13114, 859453491);\n\t\t// myRetransmissionBuffer.Insert(14906, 976892264);\n\t\t// myRetransmissionBuffer.Insert(14906, 976897098);\n\t\t// myRetransmissionBuffer.Insert(13114, 859464290);\n\t\t// myRetransmissionBuffer.Insert(14906, 976889088);\n\t\t// myRetransmissionBuffer.Insert(13056, 855638184);\n\t\t// myRetransmissionBuffer.Insert(14906, 976891950);\n\t\t// myRetransmissionBuffer.Insert(17722, 1161443894);\n\t\t// myRetransmissionBuffer.Insert(12846, 841888049);\n\t\t// myRetransmissionBuffer.Insert(14906, 976905830);\n\t\t// myRetransmissionBuffer.Insert(15677, 1027420485);\n\t\t// myRetransmissionBuffer.Insert(33742, 2211317269);\n\t\t// myRetransmissionBuffer.Insert(14906, 976892672);\n\t\t// myRetransmissionBuffer.Insert(13102, 858665774);\n\t\t// myRetransmissionBuffer.Insert(12850, 842150702);\n\t\t// myRetransmissionBuffer.Insert(14906, 976891941);\n\t\t// myRetransmissionBuffer.Insert(15677, 1027423549);\n\t\t// myRetransmissionBuffer.Insert(12346, 809120580);\n\t\t// myRetransmissionBuffer.Insert(12645, 828715313);\n\t\tmyRetransmissionBuffer.Insert(12645, 828702743);\n\t\tmyRetransmissionBuffer.Insert(33998, 2228092928);\n\t\tmyRetransmissionBuffer.Insert(33998, 2228092928);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <vector>\n\n// 17: 16 bit mask + the initial sequence number.\nstatic constexpr size_t MaxRequestedPackets{ 17 };\nstatic constexpr unsigned int SendNackDelay{ 0u }; // In ms.\nstatic const bool UseRtpInactivityCheck{ false };\n\nSCENARIO(\"RtpStreamRecv\", \"[rtp][rtpstream][rtpstreamrecv]\")\n{\n\tclass RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener\n\t{\n\tpublic:\n\t\tvoid OnRtpStreamScore(\n\t\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override\n\t\t{\n\t\t}\n\n\t\tvoid OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* packet) override\n\t\t{\n\t\t\tswitch (packet->GetType())\n\t\t\t{\n\t\t\t\tcase RTC::RTCP::Type::PSFB:\n\t\t\t\t{\n\t\t\t\t\tswitch (dynamic_cast<RTC::RTCP::FeedbackPsPacket*>(packet)->GetMessageType())\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::PLI:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tINFO(\"PLI required\");\n\n\t\t\t\t\t\t\tREQUIRE(this->shouldTriggerPLI == true);\n\n\t\t\t\t\t\t\tthis->shouldTriggerPLI = false;\n\t\t\t\t\t\t\tthis->nackedSeqNumbers.clear();\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcase RTC::RTCP::FeedbackPs::MessageType::FIR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tINFO(\"FIR required\");\n\n\t\t\t\t\t\t\tREQUIRE(this->shouldTriggerFIR == true);\n\n\t\t\t\t\t\t\tthis->shouldTriggerFIR = false;\n\t\t\t\t\t\t\tthis->nackedSeqNumbers.clear();\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase RTC::RTCP::Type::RTPFB:\n\t\t\t\t{\n\t\t\t\t\tswitch (dynamic_cast<RTC::RTCP::FeedbackRtpPacket*>(packet)->GetMessageType())\n\t\t\t\t\t{\n\t\t\t\t\t\tcase RTC::RTCP::FeedbackRtp::MessageType::NACK:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tINFO(\"NACK required\");\n\n\t\t\t\t\t\t\tREQUIRE(this->shouldTriggerNack == true);\n\n\t\t\t\t\t\t\tthis->shouldTriggerNack = false;\n\n\t\t\t\t\t\t\tauto* nackPacket = dynamic_cast<RTC::RTCP::FeedbackRtpNackPacket*>(packet);\n\n\t\t\t\t\t\t\tfor (auto it = nackPacket->Begin(); it != nackPacket->End(); ++it)\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tconst RTC::RTCP::FeedbackRtpNackItem* item = *it;\n\n\t\t\t\t\t\t\t\tconst uint16_t firstSeq = item->GetPacketId();\n\t\t\t\t\t\t\t\tuint16_t bitmask        = item->GetLostPacketBitmask();\n\n\t\t\t\t\t\t\t\tthis->nackedSeqNumbers.push_back(firstSeq);\n\n\t\t\t\t\t\t\t\tfor (size_t i{ 1 }; i < MaxRequestedPackets; ++i)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tif ((bitmask & 1) != 0)\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tthis->nackedSeqNumbers.push_back(firstSeq + i);\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tbitmask >>= 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdefault:;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault:;\n\t\t\t}\n\t\t}\n\n\t\tvoid OnRtpStreamNeedWorstRemoteFractionLost(\n\t\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override\n\t\t{\n\t\t}\n\n\tpublic:\n\t\tbool shouldTriggerNack = false;\n\t\tbool shouldTriggerPLI  = false;\n\t\tbool shouldTriggerFIR  = false;\n\t\tstd::vector<uint16_t> nackedSeqNumbers;\n\t};\n\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x80, 0x01, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x05,\n\t\t0x00, 0x00, 0x00, 0x00 // Extra space for RTX encoding.\n\t};\n\t// clang-format on\n\n\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, 12, 12 + 4) };\n\n\tif (!packet)\n\t{\n\t\tFAIL(\"not a RTP packet\");\n\t}\n\n\tRTC::RTP::RtpStream::Params params;\n\n\tparams.ssrc           = packet->GetSsrc();\n\tparams.rtxSsrc        = 1234;\n\tparams.rtxPayloadType = 96;\n\tparams.clockRate      = 90000;\n\tparams.useNack        = true;\n\tparams.usePli         = true;\n\tparams.useFir         = false;\n\n\tSECTION(\"NACK one packet\")\n\t{\n\t\tRtpStreamRecvListener listener;\n\t\tRTC::RTP::RtpStreamRecv rtpStream(\n\t\t  std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck);\n\n\t\tpacket->SetSequenceNumber(1);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(3);\n\t\tlistener.shouldTriggerNack = true;\n\t\tlistener.shouldTriggerPLI  = false;\n\t\tlistener.shouldTriggerFIR  = false;\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tREQUIRE(listener.nackedSeqNumbers.size() == 1);\n\t\tREQUIRE(listener.nackedSeqNumbers[0] == 2);\n\t\tlistener.nackedSeqNumbers.clear();\n\n\t\tpacket->SetSequenceNumber(2);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tREQUIRE(listener.nackedSeqNumbers.empty());\n\n\t\tpacket->SetSequenceNumber(4);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tREQUIRE(listener.nackedSeqNumbers.empty());\n\t}\n\n\tSECTION(\"receive RTX before corresponding RTP\")\n\t{\n\t\tRtpStreamRecvListener listener;\n\t\tRTC::RTP::RtpStreamRecv rtpStream(\n\t\t  std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck);\n\n\t\tpacket->SetSequenceNumber(1);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(2);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(3);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(4);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(5);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\t// Sequence number 6 arrives via RTX before the original RTP packet.\n\n\t\tauto originalSsrc        = packet->GetSsrc();\n\t\tauto originalPayloadType = packet->GetPayloadType();\n\n\t\tpacket->SetSequenceNumber(6);\n\t\tpacket->RtxEncode(params.rtxPayloadType, params.rtxSsrc, 1000 /*seq=*/);\n\n\t\tREQUIRE(rtpStream.ReceiveRtxPacket(packet.get()));\n\n\t\tpacket->RtxDecode(originalPayloadType, originalSsrc);\n\t}\n\n\tSECTION(\"wrapping sequence numbers\")\n\t{\n\t\tRtpStreamRecvListener listener;\n\t\tRTC::RTP::RtpStreamRecv rtpStream(\n\t\t  std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck);\n\n\t\tpacket->SetSequenceNumber(0xfffe);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tpacket->SetSequenceNumber(1);\n\t\tlistener.shouldTriggerNack = true;\n\t\tlistener.shouldTriggerPLI  = false;\n\t\tlistener.shouldTriggerFIR  = false;\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\tREQUIRE(listener.nackedSeqNumbers.size() == 2);\n\t\tREQUIRE(listener.nackedSeqNumbers[0] == 0xffff);\n\t\tREQUIRE(listener.nackedSeqNumbers[1] == 0);\n\t\tlistener.nackedSeqNumbers.clear();\n\t}\n\n\tSECTION(\"require key frame\")\n\t{\n\t\tRtpStreamRecvListener listener;\n\t\tRTC::RTP::RtpStreamRecv rtpStream(\n\t\t  std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck);\n\n\t\tpacket->SetSequenceNumber(1);\n\t\trtpStream.ReceivePacket(packet.get());\n\n\t\t// Seq different is bigger than MaxNackPackets in NackGenerator, so it\n\t\t// triggers a key frame.\n\t\tpacket->SetSequenceNumber(1003);\n\t\tlistener.shouldTriggerPLI = true;\n\t\tlistener.shouldTriggerFIR = false;\n\t\trtpStream.ReceivePacket(packet.get());\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestRtpStreamSend.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"RTC/RTCP/FeedbackRtpNack.hpp\"\n#include \"RTC/RTP/Codecs/AV1.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Codecs/VP8.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"RTC/RTP/RtpStreamSend.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcpy()\n#include <memory>\n#include <vector>\n\n// #define PERFORMANCE_TEST 1\n\nSCENARIO(\"RtpStreamSend\", \"[rtp][rtcp][nack][rtpstream][rtpstreamsend]\")\n{\n\tclass TestRtpStreamListener : public RTC::RTP::RtpStreamSend::Listener\n\t{\n\tpublic:\n\t\tvoid OnRtpStreamScore(\n\t\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override\n\t\t{\n\t\t}\n\n\t\tvoid OnRtpStreamRetransmitRtpPacket(\n\t\t  RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) override\n\t\t{\n\t\t\tthis->retransmittedPackets.push_back(packet);\n\t\t}\n\n\tpublic:\n\t\tstd::vector<RTC::RTP::Packet*> retransmittedPackets;\n\t};\n\n\tauto createRtpPacket = [](uint8_t* buffer, size_t len, uint16_t seq, uint32_t timestamp)\n\t{\n\t\tauto* packet = RTC::RTP::Packet::Parse(buffer, len);\n\n\t\tREQUIRE(packet);\n\n\t\tpacket->SetPayloadType(123);\n\t\tpacket->SetSequenceNumber(seq);\n\t\tpacket->SetTimestamp(timestamp);\n\n\t\treturn std::unique_ptr<RTC::RTP::Packet>(packet);\n\t};\n\n\tauto sendRtpPacket = [](\n\t                       // NOTE: clang-tidy suggests passing `streams` by reference but that's\n\t                       // wrong because we create `streams` in place when calling this function.\n\t                       // NOLINTNEXTLINE(performance-unnecessary-value-param)\n\t                       std::vector<std::pair<RTC::RTP::RtpStreamSend*, uint32_t>> streams,\n\t                       RTC::RTP::Packet* packet)\n\t{\n\t\tRTC::RTP::SharedPacket sharedPacket;\n\n\t\tfor (auto& kv : streams)\n\t\t{\n\t\t\tauto* stream  = kv.first;\n\t\t\tauto ssrc     = kv.second;\n\t\t\tauto origSsrc = packet->GetSsrc();\n\n\t\t\tpacket->SetSsrc(ssrc);\n\n\t\t\tauto result = stream->ReceivePacket(packet, sharedPacket);\n\n\t\t\tpacket->SetSsrc(origSsrc);\n\n\t\t\t// NOTE: Here we must replicate the behaviour of Consumer::sendRtpPacket()\n\t\t\t// in which, if the shared packet has been stored and it didn't contain the\n\t\t\t// packet yet, we fill it with a cloned packet.\n\t\t\tif (\n\t\t\t  result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED &&\n\t\t\t  !sharedPacket.HasPacket())\n\t\t\t{\n\t\t\t\tsharedPacket.Assign(packet);\n\t\t\t}\n\t\t}\n\t};\n\n\tauto checkRtxPacket = [](RTC::RTP::Packet* rtxPacket, RTC::RTP::Packet* origPacket)\n\t{\n\t\tREQUIRE(rtxPacket);\n\t\tREQUIRE(rtxPacket->GetSequenceNumber() == origPacket->GetSequenceNumber());\n\t\tREQUIRE(rtxPacket->GetTimestamp() == origPacket->GetTimestamp());\n\t\tREQUIRE(rtxPacket->HasMarker() == origPacket->HasMarker());\n\t};\n\n\tauto parseAV1RtpPacket =\n\t  [](\n\t    RTC::RTP::Packet* packet,\n\t    std::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>&\n\t      templateDependencyStructure)\n\t{\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor> dependencyDescriptor;\n\t\tpacket->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure);\n\t\tREQUIRE(dependencyDescriptor);\n\n\t\tauto* payloadDescriptor = RTC::RTP::Codecs::AV1::Parse(dependencyDescriptor);\n\t\tauto* payloadDescriptorHandler =\n\t\t  new RTC::RTP::Codecs::AV1::PayloadDescriptorHandler(payloadDescriptor);\n\t\tpacket->SetPayloadDescriptorHandler(payloadDescriptorHandler);\n\t};\n\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\n\t// clang-format off\n\tuint8_t rtpBuffer1[] =\n\t{\n\t\t0b10000000, 0b01111011, 0b01010010, 0b00001110,\n\t\t0b01011011, 0b01101011, 0b11001010, 0b10110101,\n\t\t0, 0, 0, 2\n\t};\n\t// clang-format on\n\n\tuint8_t rtpBuffer2[1500];\n\tuint8_t rtpBuffer3[1500];\n\tuint8_t rtpBuffer4[1500];\n\tuint8_t rtpBuffer5[1500];\n\n\tstd::memcpy(rtpBuffer2, rtpBuffer1, sizeof(rtpBuffer1));\n\tstd::memcpy(rtpBuffer3, rtpBuffer1, sizeof(rtpBuffer1));\n\tstd::memcpy(rtpBuffer4, rtpBuffer1, sizeof(rtpBuffer1));\n\tstd::memcpy(rtpBuffer5, rtpBuffer1, sizeof(rtpBuffer1));\n\n\tSECTION(\"receive NACK and get retransmitted packets\")\n\t{\n\t\t// packet1 [seq:21006, timestamp:1533790901]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901));\n\t\t// packet2 [seq:21007, timestamp:1533790901]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901));\n\t\tpacket2->SetMarker(true);\n\t\t// packet3 [seq:21008, timestamp:1533793871]\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871));\n\t\t// packet4 [seq:21009, timestamp:1533793871]\n\t\tauto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871));\n\t\t// packet5 [seq:21010, timestamp:1533796931]\n\t\tauto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931));\n\t\tpacket5->SetMarker(true);\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc          = 1111;\n\t\tparams.clockRate     = 90000;\n\t\tparams.useNack       = true;\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);\n\n\t\t// Receive all the packets (some of them not in order and/or duplicated).\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet2.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet4.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\n\t\t// Create a NACK item that request for all the packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111);\n\n\t\tstream->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.size() == 5);\n\n\t\tauto* rtxPacket1 = testRtpStreamListener.retransmittedPackets[0];\n\t\tauto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[1];\n\t\tauto* rtxPacket3 = testRtpStreamListener.retransmittedPackets[2];\n\t\tauto* rtxPacket4 = testRtpStreamListener.retransmittedPackets[3];\n\t\tauto* rtxPacket5 = testRtpStreamListener.retransmittedPackets[4];\n\n\t\ttestRtpStreamListener.retransmittedPackets.clear();\n\n\t\tcheckRtxPacket(rtxPacket1, packet1.get());\n\t\tcheckRtxPacket(rtxPacket2, packet2.get());\n\t\tcheckRtxPacket(rtxPacket3, packet3.get());\n\t\tcheckRtxPacket(rtxPacket4, packet4.get());\n\t\tcheckRtxPacket(rtxPacket5, packet5.get());\n\t}\n\n\tSECTION(\"receive NACK and get zero retransmitted packets if useNack is not set\")\n\t{\n\t\t// packet1 [seq:21006, timestamp:1533790901]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901));\n\t\t// packet2 [seq:21007, timestamp:1533790901]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901));\n\t\t// packet3 [seq:21008, timestamp:1533793871]\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871));\n\t\t// packet4 [seq:21009, timestamp:1533793871]\n\t\tauto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871));\n\t\t// packet5 [seq:21010, timestamp:1533796931]\n\t\tauto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc          = 1111;\n\t\tparams.clockRate     = 90000;\n\t\tparams.useNack       = false;\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);\n\n\t\t// Receive all the packets (some of them not in order and/or duplicated).\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet2.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet4.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\n\t\t// Create a NACK item that request for all the packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111);\n\n\t\tstream->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.empty());\n\n\t\ttestRtpStreamListener.retransmittedPackets.clear();\n\t}\n\n\tSECTION(\"receive NACK and get zero retransmitted packets for audio\")\n\t{\n\t\t// packet1 [seq:21006, timestamp:1533790901]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901));\n\t\t// packet2 [seq:21007, timestamp:1533790901]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901));\n\t\t// packet3 [seq:21008, timestamp:1533793871]\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871));\n\t\t// packet4 [seq:21009, timestamp:1533793871]\n\t\tauto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871));\n\t\t// packet5 [seq:21010, timestamp:1533796931]\n\t\tauto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc          = 1111;\n\t\tparams.clockRate     = 90000;\n\t\tparams.useNack       = false;\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::AUDIO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);\n\n\t\t// Receive all the packets (some of them not in order and/or duplicated).\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet2.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet4.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params.ssrc }\n    },\n\t\t  packet5.get());\n\n\t\t// Create a NACK item that request for all the packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111);\n\n\t\tstream->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.empty());\n\n\t\ttestRtpStreamListener.retransmittedPackets.clear();\n\t}\n\n\tSECTION(\"receive NACK in different RtpStreamSend instances and get retransmitted packets\")\n\t{\n\t\t// packet1 [seq:21006, timestamp:1533790901]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901));\n\t\t// packet2 [seq:21007, timestamp:1533790901]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901));\n\n\t\t// Create two RtpStreamSend instances.\n\t\tTestRtpStreamListener testRtpStreamListener1;\n\t\tTestRtpStreamListener testRtpStreamListener2;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = 90000;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream1(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid));\n\n\t\tRTC::RTP::RtpStream::Params params2;\n\n\t\tparams2.ssrc          = 2222;\n\t\tparams2.clockRate     = 90000;\n\t\tparams2.useNack       = true;\n\t\tparams2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream2(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid));\n\n\t\t// Receive all the packets in both streams.\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream1.get(), params1.ssrc },\n        { stream2.get(), params2.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream1.get(), params1.ssrc },\n        { stream2.get(), params2.ssrc }\n    },\n\t\t  packet2.get());\n\n\t\t// Create a NACK item that request for all the packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream1->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 2);\n\n\t\tauto* rtxPacket1 = testRtpStreamListener1.retransmittedPackets[0];\n\t\tauto* rtxPacket2 = testRtpStreamListener1.retransmittedPackets[1];\n\n\t\ttestRtpStreamListener1.retransmittedPackets.clear();\n\n\t\tcheckRtxPacket(rtxPacket1, packet1.get());\n\t\tcheckRtxPacket(rtxPacket2, packet2.get());\n\n\t\t// Process the NACK packet on stream2.\n\t\tstream2->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 2);\n\n\t\trtxPacket1 = testRtpStreamListener2.retransmittedPackets[0];\n\t\trtxPacket2 = testRtpStreamListener2.retransmittedPackets[1];\n\n\t\ttestRtpStreamListener2.retransmittedPackets.clear();\n\n\t\tcheckRtxPacket(rtxPacket1, packet1.get());\n\t\tcheckRtxPacket(rtxPacket2, packet2.get());\n\t}\n\n\tSECTION(\"retransmitted packets are correctly encoded [VP8]\")\n\t{\n\t\t// clang-format off\n\t\tuint8_t rtpBuffer1[] =\n\t\t{\n\t\t\t0x80, 0x7b, 0x52, 0x0e,\n\t\t\t0x5b, 0x6b, 0xca, 0xb5,\n\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t0x80, 0xe0, 0x80, 0x01,\n\t\t\t0xe8, 0x40, 0x7a, 0xd8\n\t\t};\n\t\tuint8_t rtpBuffer2[] =\n\t\t{\n\t\t\t0x80, 0x7b, 0x52, 0x0e,\n\t\t\t0x5b, 0x6b, 0xca, 0xb5,\n\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t0x80, 0xe0, 0x80, 0x02,\n\t\t\t0xe9, 0x40, 0x7a, 0xd8\n\t\t};\n\t\tuint8_t rtpBuffer3[] =\n\t\t{\n\t\t\t0x80, 0x7b, 0x52, 0x0e,\n\t\t\t0x5b, 0x6b, 0xca, 0xb5,\n\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t0x80, 0xe0, 0x80, 0x03,\n\t\t\t0xea, 0x40, 0x7a, 0xd8\n\t\t};\n\t\t// clang-format on\n\n\t\t// packet1 [seq:1, timestamp:1]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 1, 1));\n\t\t// packet2 [seq:2, timestamp:1]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 2, 1));\n\t\t// packet3 [seq:3, timestamp:1]\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 3, 1));\n\n\t\t// Create two RtpStreamSend instances.\n\t\tTestRtpStreamListener testRtpStreamListener1;\n\t\tTestRtpStreamListener testRtpStreamListener2;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = 90000;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream1(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid));\n\n\t\tRTC::RTP::RtpStream::Params params2;\n\n\t\tparams2.ssrc          = 2222;\n\t\tparams2.clockRate     = 90000;\n\t\tparams2.useNack       = true;\n\t\tparams2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream2(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid));\n\n\t\t// Create two VP8 encoding contexts.\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 0;\n\t\tparams.temporalLayers = 3;\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context1(params);\n\n\t\tcontext1.SetCurrentTemporalLayer(3);\n\t\tcontext1.SetTargetTemporalLayer(3);\n\n\t\tRTC::RTP::Codecs::VP8::EncodingContext context2(params);\n\n\t\tcontext2.SetCurrentTemporalLayer(0);\n\t\tcontext2.SetTargetTemporalLayer(0);\n\n\t\t// Parse the first packet.\n\t\tauto* payloadDescriptor1 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet1->GetPayload(), packet1->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor1->pictureId == 1);\n\n\t\tauto* payloadDescriptorHandler1 =\n\t\t  new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor1);\n\t\tpacket1->SetPayloadDescriptorHandler(payloadDescriptorHandler1);\n\n\t\tbool marker = false;\n\n\t\t// Process the first packet with context1.\n\t\tauto forwarded = payloadDescriptorHandler1->Process(&context1, packet1.get(), marker);\n\t\tREQUIRE(forwarded);\n\n\t\t// Parse the second packet.\n\t\tauto* payloadDescriptor2 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet2->GetPayload(), packet2->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor2->pictureId == 2);\n\n\t\tauto* payloadDescriptorHandler2 =\n\t\t  new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor2);\n\t\tpacket2->SetPayloadDescriptorHandler(payloadDescriptorHandler2);\n\n\t\t// Process the second packet with context1.\n\t\tforwarded = payloadDescriptorHandler2->Process(&context1, packet2.get(), marker);\n\t\tREQUIRE(forwarded);\n\n\t\t// Process the second packet for context2.\n\t\tforwarded = payloadDescriptorHandler2->Process(&context2, packet2.get(), marker);\n\t\t// It must not forwared because the target temporal layer is 0.\n\t\tREQUIRE(!forwarded);\n\n\t\t// Parse the third packet\n\t\tauto* payloadDescriptor3 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet3->GetPayload(), packet3->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor3->pictureId == 3);\n\n\t\tauto* payloadDescriptorHandler3 =\n\t\t  new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor3);\n\t\tpacket2->SetPayloadDescriptorHandler(payloadDescriptorHandler3);\n\n\t\t// Process the third packet for context1.\n\t\tforwarded = payloadDescriptorHandler3->Process(&context1, packet3.get(), marker);\n\t\tREQUIRE(forwarded);\n\n\t\t// Receive the third packet in the first stream.\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream1.get(), params1.ssrc }\n    },\n\t\t  packet3.get());\n\n\t\t// Update current/target temporal layers for context2.\n\t\tcontext2.SetCurrentTemporalLayer(3);\n\t\tcontext2.SetTargetTemporalLayer(3);\n\n\t\tforwarded = payloadDescriptorHandler3->Process(&context2, packet3.get(), marker);\n\t\tREQUIRE(forwarded);\n\n\t\t// Receive the third packet in the second stream.\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream2.get(), params2.ssrc }\n    },\n\t\t  packet3.get());\n\n\t\t// Create a NACK item that requests the third packet.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(3, 0b0000000000000000);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 3);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000000);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream1->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 1);\n\n\t\tauto* packet = testRtpStreamListener1.retransmittedPackets[0];\n\n\t\t// Parse payload and check pictureId.\n\t\tauto* payloadDescriptor4 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor4->pictureId == 3);\n\n\t\t// Process the NACK packet on stream2.\n\t\tstream2->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 1);\n\n\t\tpacket = testRtpStreamListener2.retransmittedPackets[0];\n\n\t\t// Parse payload and check pictureId.\n\t\tauto* payloadDescriptor5 =\n\t\t  RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength());\n\t\tREQUIRE(payloadDescriptor5);\n\t\tREQUIRE(payloadDescriptor5->pictureId == 2);\n\n\t\tdelete payloadDescriptor4;\n\t\tdelete payloadDescriptor5;\n\t}\n\n\tSECTION(\"retransmitted packets are correctly encoded [AV1]\")\n\t{\n\t\t/*\n\t\t * <DependencyDescriptor>\n\t\t *\t startOfFrame: true\n\t\t *\t endOfFrame: false\n\t\t *\t frameDependencyTemplateId: 0\n\t\t *\t frameNumber: 1\n\t\t *\t templateId: 0\n\t\t *\t spatialLayer: 0\n\t\t *\t temporalLayer: 0\n\t\t *\t <TemplateDependencyStructure>\n\t\t *\t spatialLayers: 0\n\t\t *\t temporalLayers: 1\n\t\t *\t templateIdOffset: 0\n\t\t *\t decodeTargetCount: 2\n\t\t *\t <TemplateLayers>\n\t\t *    <FrameDependencyTemplate>\n\t\t *      spatialLayerId: 0\n\t\t *      temporalLayerId: 0\n\t\t *      <DecodeTargetIndications> SS </DecodeTargetIndications>\n\t\t *      <FrameDiffs>  </FrameDiffs>\n\t\t *      <FrameDiffChains> 0 </FrameDiffChains>\n\t\t *    <FrameDependencyTemplate>\n\t\t *    <FrameDependencyTemplate>\n\t\t *      spatialLayerId: 0\n\t\t *      temporalLayerId: 0\n\t\t *      <DecodeTargetIndications> SS </DecodeTargetIndications>\n\t\t *      <FrameDiffs> 2 </FrameDiffs>\n\t\t *      <FrameDiffChains> 2 </FrameDiffChains>\n\t\t *    </FrameDependencyTemplate>\n\t\t *    <FrameDependencyTemplate>\n\t\t *      spatialLayerId: 0\n\t\t *      temporalLayerId: 1\n\t\t *      <DecodeTargetIndications> -D </DecodeTargetIndications>\n\t\t *      <FrameDiffs> 1 </FrameDiffs>\n\t\t *      <FrameDiffChains> 1 </FrameDiffChains>\n\t\t *    </FrameDependencyTemplate>\n\t\t *    <FrameDependencyTemplate>\n\t\t *      spatialLayerId: 0\n\t\t *      temporalLayerId: 1\n\t\t *      <DecodeTargetIndications> -D </DecodeTargetIndications>\n\t\t *      <FrameDiffs> 1 </FrameDiffs>\n\t\t *      <FrameDiffChains> 1 </FrameDiffChains>\n\t\t *    </FrameDependencyTemplate>\n\t\t *\t </TemplateLayers>\n\t\t *\t </TemplateDependencyStructure>\n\t\t *\t</DependencyDescriptor>\n\t\t */\n\t\t// clang-format off\n\t\tuint8_t rtpBuffer1[] =\n\t\t{\n\t\t\t0x90, 0x2D, 0x56, 0xA5,\n\t\t\t0x8D, 0x76, 0xF5, 0x02,\n\t\t\t0xDD, 0xD5, 0x4C, 0xB9,\n\t\t\t0xBE, 0xDE, 0x00, 0x07,\n\t\t\t0x22, 0x89, 0xDF, 0xFE,\n\t\t\t0x31, 0x00, 0x07, 0x40,\n\t\t\t0x31, 0xCE, 0x80, 0x00,\n\t\t\t0x01, 0x80, 0x01, 0x1E,\n\t\t\t0xA8, 0x51, 0x41, 0x01,\n\t\t\t0x0C, 0x13, 0xFC, 0x0B,\n\t\t\t0x3C, 0x00, 0x00, 0x00\n\t\t};\n\n\t\t/*\n\t\t * <DependencyDescriptor>\n\t\t * \t startOfFrame: true\n\t\t * \t endOfFrame: true\n\t\t * \t frameDependencyTemplateId: 2\n\t\t * \t frameNumber: 2\n\t\t * \t templateId: 2\n\t\t * \t temporalLayer: 1\n\t\t * \t spatialLayer: 0\n\t\t * \t</DependencyDescriptor>\n\t\t */\n\t\tuint8_t rtpBuffer2[] =\n\t\t{\n\t\t\t0x90, 0xAD, 0x56, 0xA9,\n\t\t\t0x8D, 0x77, 0x02, 0xB8,\n\t\t\t0xDD, 0xD5, 0x4C, 0xB9,\n\t\t\t0xBE, 0xDE, 0x00, 0x04,\n\t\t\t0x22, 0x8A, 0x07, 0xAB,\n\t\t\t0x31, 0x00, 0x18, 0x40,\n\t\t\t0x31, 0xC2, 0xC2, 0x00,\n\t\t\t0x02, 0x00, 0x00, 0x00\n\t\t};\n\n\t\t/*\n\t\t * <DependencyDescriptor>\n\t\t * \t startOfFrame: false\n\t\t * \t endOfFrame: true\n\t\t * \t frameDependencyTemplateId: 0\n\t\t * \t frameNumber: 1\n\t\t * \t templateId: 0\n\t\t * \t spatialLayer: 0\n\t\t * \t temporalLayer: 0\n\t\t * \t</DependencyDescriptor>\n\t\t */\n\t\tuint8_t rtpBuffer3[] =\n\t\t{\n\t\t\t0x90, 0xAD, 0x56, 0xA8,\n\t\t\t0x8D, 0x76, 0xF5, 0x02,\n\t\t\t0xDD, 0xD5, 0x4C, 0xB9,\n\t\t\t0xBE, 0xDE, 0x00, 0x04,\n\t\t\t0x22, 0x8A, 0x03, 0xE5,\n\t\t\t0xD0, 0x00, 0x31, 0x00,\n\t\t\t0x17, 0xC2, 0x40, 0x00,\n\t\t\t0x01, 0x40, 0x31, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tRTC::RTP::HeaderExtensionIds headerExtensionIds{};\n\n\t\theaderExtensionIds.dependencyDescriptor = 12;\n\n\t\t// packet1 [seq:1, timestamp:1]\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 1, 1));\n\t\tpacket1->AssignExtensionIds(headerExtensionIds);\n\n\t\t// packet2 [seq:2, timestamp:1]\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 2, 1));\n\t\tpacket2->AssignExtensionIds(headerExtensionIds);\n\n\t\t// packet3 [seq:3, timestamp:1]\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 3, 1));\n\t\tpacket3->AssignExtensionIds(headerExtensionIds);\n\n\t\t// Create two RtpStreamSend instances.\n\t\tTestRtpStreamListener testRtpStreamListener1;\n\t\tTestRtpStreamListener testRtpStreamListener2;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = 90000;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream1(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid));\n\n\t\tRTC::RTP::RtpStream::Params params2;\n\n\t\tparams2.ssrc          = 2222;\n\t\tparams2.clockRate     = 90000;\n\t\tparams2.useNack       = true;\n\t\tparams2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream2(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid));\n\n\t\t// Create two AV1 encoding contexts.\n\t\tRTC::RTP::Codecs::EncodingContext::Params params;\n\t\tparams.spatialLayers  = 1;\n\t\tparams.temporalLayers = 2;\n\n\t\tRTC::RTP::Codecs::AV1::EncodingContext context1(params);\n\t\tcontext1.SetCurrentSpatialLayer(0);\n\t\tcontext1.SetCurrentTemporalLayer(0);\n\t\tcontext1.SetTargetSpatialLayer(0);\n\t\tcontext1.SetTargetTemporalLayer(0);\n\n\t\tRTC::RTP::Codecs::AV1::EncodingContext context2(params);\n\t\tcontext2.SetCurrentSpatialLayer(0);\n\t\tcontext2.SetCurrentTemporalLayer(0);\n\t\tcontext2.SetTargetSpatialLayer(0);\n\t\tcontext2.SetTargetTemporalLayer(1);\n\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor::TemplateDependencyStructure>\n\t\t  templateDependencyStructure;\n\n\t\t// Parse the first packet for the shake of having the template dependency structure.\n\t\tparseAV1RtpPacket(packet1.get(), templateDependencyStructure);\n\t\t// Parse the second packet.\n\t\tparseAV1RtpPacket(packet2.get(), templateDependencyStructure);\n\n\t\tbool marker    = false;\n\t\tbool forwarded = false;\n\n\t\t// Process the second packet for context1.\n\t\tforwarded = packet2->ProcessPayload(&context1, marker);\n\t\tREQUIRE(!forwarded);\n\n\t\t// Process the second packet with context2.\n\t\tforwarded = packet2->ProcessPayload(&context2, marker);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(context2.GetCurrentSpatialLayer() == 0);\n\t\tREQUIRE(context2.GetCurrentTemporalLayer() == 1);\n\n\t\t// Parse the third packet\n\t\tparseAV1RtpPacket(packet3.get(), templateDependencyStructure);\n\n\t\t// Process the third packet with context1 and verify current spatial layers.\n\t\tforwarded = packet3->ProcessPayload(&context1, marker);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(context1.GetCurrentSpatialLayer() == 0);\n\t\tREQUIRE(context1.GetCurrentTemporalLayer() == 0);\n\n\t\tRTC::RTP::SharedPacket sharedPacket;\n\n\t\tpacket3->SetSsrc(params1.ssrc);\n\t\t// Whenever packet3 is Nacked on stream1, it must always be set a\n\t\t// 00000001 (S0_T1) active decode target bitmas.\n\t\tauto result = stream1->ReceivePacket(packet3.get(), sharedPacket);\n\n\t\tREQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED);\n\t\tsharedPacket.Assign(packet3.get());\n\n\t\t// Process the third packet with context2 and verify current spatial layers.\n\t\tforwarded = packet3->ProcessPayload(&context2, marker);\n\t\tREQUIRE(forwarded);\n\t\tREQUIRE(context2.GetCurrentSpatialLayer() == 0);\n\t\tREQUIRE(context2.GetCurrentTemporalLayer() == 1);\n\n\t\tpacket3->SetSsrc(params2.ssrc);\n\n\t\t// Whenever packet3 is Nacked on stream2, it must always be set a\n\t\t// 00000011 (S0_T1) active decode target bitmas.\n\t\tstream2->ReceivePacket(packet3.get(), sharedPacket);\n\n\t\t// Create a NACK item that requests the third packet.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(3, 0b0000000000000000);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 3);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000000);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream1->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 1);\n\n\t\tauto* packet = testRtpStreamListener1.retransmittedPackets[0];\n\n\t\t// Parse DD and check bitmask.\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor> dependencyDescriptor4;\n\n\t\tpacket->ReadDependencyDescriptor(dependencyDescriptor4, templateDependencyStructure);\n\t\tREQUIRE(dependencyDescriptor4);\n\t\t// TODO: Enable once we write DD.\n\t\t// REQUIRE(dependencyDescriptor4->activeDecodeTargetsBitmask == 0b0000000000000001);\n\n\t\t// Process the NACK packet on stream2.\n\t\tstream2->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 1);\n\n\t\tpacket = testRtpStreamListener2.retransmittedPackets[0];\n\n\t\t// Parse DD and check bitmask.\n\t\tstd::unique_ptr<RTC::RTP::Codecs::DependencyDescriptor> dependencyDescriptor5;\n\n\t\tpacket->ReadDependencyDescriptor(dependencyDescriptor5, templateDependencyStructure);\n\t\tREQUIRE(dependencyDescriptor5);\n\t\t// TODO: Enable once we write DD.\n\t\t// REQUIRE(dependencyDescriptor5->activeDecodeTargetsBitmask == 0b0000000000000011);\n\t}\n\n\tSECTION(\"packets get retransmitted as long as they don't exceed MaxRetransmissionDelayForVideoMs\")\n\t{\n\t\tconst uint32_t clockRate = 90000;\n\t\tconst uint32_t firstTs   = 1533790901;\n\t\tconst uint32_t diffTs =\n\t\t  RTC::RTP::RtpStreamSend::MaxRetransmissionDelayForVideoMs * clockRate / 1000;\n\t\tconst uint32_t secondTs = firstTs + diffTs;\n\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, firstTs));\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, secondTs - 1));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = clockRate;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid);\n\n\t\t// Receive all the packets.\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet2.get());\n\n\t\t// Create a NACK item that request for all the packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.size() == 2);\n\n\t\tauto* rtxPacket1 = testRtpStreamListener.retransmittedPackets[0];\n\t\tauto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[1];\n\n\t\ttestRtpStreamListener.retransmittedPackets.clear();\n\n\t\tcheckRtxPacket(rtxPacket1, packet1.get());\n\t\tcheckRtxPacket(rtxPacket2, packet2.get());\n\t}\n\n\tSECTION(\"packets don't get retransmitted if MaxRetransmissionDelayForVideoMs is exceeded\")\n\t{\n\t\tconst uint32_t clockRate = 90000;\n\t\tconst uint32_t firstTs   = 1533790901;\n\t\tconst uint32_t diffTs =\n\t\t  RTC::RTP::RtpStreamSend::MaxRetransmissionDelayForVideoMs * clockRate / 1000;\n\t\t// Make second packet arrive more than MaxRetransmissionDelayForVideoMs later.\n\t\tconst uint32_t secondTs = firstTs + diffTs + 100;\n\t\t// Send a third packet so it will clean old packets from the buffer.\n\t\tconst uint32_t thirdTs = firstTs + (2 * diffTs);\n\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, firstTs));\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, secondTs));\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, thirdTs));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = clockRate;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid);\n\n\t\t// Receive all the packets.\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet2.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet3.get());\n\n\t\t// Create a NACK item that requests for all packets.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc);\n\t\tauto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001);\n\n\t\tnackPacket.AddItem(nackItem);\n\n\t\tREQUIRE(nackItem->GetPacketId() == 21006);\n\t\tREQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream->ReceiveNack(&nackPacket);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.size() == 1);\n\n\t\tauto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[0];\n\n\t\ttestRtpStreamListener.retransmittedPackets.clear();\n\n\t\tcheckRtxPacket(rtxPacket2, packet2.get());\n\t}\n\n\tSECTION(\"packets get removed from the retransmission buffer if seq number of the stream is reset\")\n\t{\n\t\t// This scenario reproduce the \"too bad sequence number\" and \"bad sequence\n\t\t// number\" scenarios in RtpStream::UpdateSeq().\n\t\tauto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 50001, 1000001));\n\t\tauto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 50002, 1000002));\n\t\t// Third packet has bad sequence number (its seq is more than MaxDropout=3000\n\t\t// older than current max seq) and will be dropped.\n\t\tauto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 40003, 1000003));\n\t\t// Forth packet has seq=badSeq+1 so will be accepted and will trigger a\n\t\t// stream reset.\n\t\tauto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 40004, 1000004));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params1;\n\n\t\tparams1.ssrc          = 1111;\n\t\tparams1.clockRate     = 90000;\n\t\tparams1.useNack       = true;\n\t\tparams1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid);\n\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet1.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet2.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet3.get());\n\t\tsendRtpPacket(\n\t\t  {\n\t\t    { stream.get(), params1.ssrc }\n    },\n\t\t  packet4.get());\n\n\t\t// Create a NACK item that requests for packets 1 and 2.\n\t\tRTC::RTCP::FeedbackRtpNackPacket nackPacket2(0, params1.ssrc);\n\t\tauto* nackItem2 = new RTC::RTCP::FeedbackRtpNackItem(50001, 0b0000000000000001);\n\n\t\tnackPacket2.AddItem(nackItem2);\n\n\t\t// Process the NACK packet on stream1.\n\t\tstream->ReceiveNack(&nackPacket2);\n\n\t\tREQUIRE(testRtpStreamListener.retransmittedPackets.empty());\n\t}\n\n\tSECTION(\"duplicated packets are discarded\")\n\t{\n\t\tauto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 50001, 1000001));\n\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc          = packet->GetSsrc();\n\t\tparams.clockRate     = 90000;\n\t\tparams.useNack       = true;\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tauto stream = std::make_unique<RTC::RTP::RtpStreamSend>(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);\n\n\t\tconst RTC::RTP::SharedPacket sharedPacket;\n\n\t\tauto result = stream->ReceivePacket(packet.get(), sharedPacket);\n\n\t\tREQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED);\n\n\t\tresult = stream->ReceivePacket(packet.get(), sharedPacket);\n\n\t\tREQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED);\n\t}\n\n#ifdef PERFORMANCE_TEST\n\tSECTION(\"Performance\")\n\t{\n\t\t// Create a RtpStreamSend instance.\n\t\tTestRtpStreamListener testRtpStreamListener;\n\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\tparams.ssrc          = 1111;\n\t\tparams.clockRate     = 90000;\n\t\tparams.useNack       = true;\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;\n\n\t\tstd::string mid;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream1(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid));\n\n\t\tsize_t iterations = 10000000;\n\n\t\tauto start = std::chrono::system_clock::now();\n\n\t\tfor (size_t i = 0; i < iterations; i++)\n\t\t{\n\t\t\t// Create packet.\n\t\t\tauto* packet = RTC::RTP::Packet::Parse(rtpBuffer1, 1500);\n\t\t\tpacket->SetSsrc(1111);\n\n\t\t\tstd::shared_ptr<RTC::RTP::Packet> sharedPacket(packet);\n\n\t\t\tstream1->ReceivePacket(packet, sharedPacket);\n\t\t}\n\n\t\tstd::chrono::duration<double> dur = std::chrono::system_clock::now() - start;\n\t\tstd::cout << \"nullptr && initialized shared_ptr: \\t\" << dur.count() << \" seconds\" << std::endl;\n\n\t\tparams.mimeType.type = RTC::RtpCodecMimeType::Type::AUDIO;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamSend> stream2(new RTC::RTP::RtpStreamSend(\n\t\t  std::addressof(testRtpStreamListener), std::addressof(shared), params, mid));\n\n\t\tstart = std::chrono::system_clock::now();\n\n\t\tfor (size_t i = 0; i < iterations; i++)\n\t\t{\n\t\t\tstd::shared_ptr<RTC::RTP::Packet> sharedPacket;\n\n\t\t\t// Create packet.\n\t\t\tauto* packet = RTC::RTP::Packet::Parse(rtpBuffer1, 1500);\n\t\t\tpacket->SetSsrc(1111);\n\n\t\t\tstream2->ReceivePacket(packet, sharedPacket);\n\t\t}\n\n\t\tdur = std::chrono::system_clock::now() - start;\n\t\tstd::cout << \"raw && empty shared_ptr duration: \\t\" << dur.count() << \" seconds\" << std::endl;\n\t}\n#endif\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/TestSharedPacket.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/RTP/rtpCommon.hpp\" // in worker/test/include/\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"RTP SharedPacket\", \"[rtp][sharedpacket]\")\n{\n\tauto compareRtpPackets = [](const RTC::RTP::Packet* packet1, const RTC::RTP::Packet* packet2)\n\t{\n\t\tREQUIRE(packet1->GetSsrc() == packet2->GetSsrc());\n\t\tREQUIRE(packet1->GetSequenceNumber() == packet2->GetSequenceNumber());\n\t\tREQUIRE(packet1->GetTimestamp() == packet2->GetTimestamp());\n\t\tREQUIRE(packet1->GetLength() == packet2->GetLength());\n\t};\n\n\tauto* packetA = RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, 2000);\n\n\tpacketA->SetSequenceNumber(1111);\n\tpacketA->SetTimestamp(111111);\n\tpacketA->SetSsrc(11111111);\n\n\tauto* packetB = RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer + 2000, 2000);\n\n\tpacketB->SetSequenceNumber(2222);\n\tpacketB->SetTimestamp(222222);\n\tpacketB->SetSsrc(22222222);\n\n\tSECTION(\"default constructor and assign later\")\n\t{\n\t\tRTC::RTP::SharedPacket sharedPacket;\n\n\t\tREQUIRE(!sharedPacket.HasPacket());\n\t\tREQUIRE(sharedPacket.GetPacket() == nullptr);\n\n\t\tsharedPacket.Assign(packetA);\n\n\t\tREQUIRE(sharedPacket.HasPacket());\n\t\tcompareRtpPackets(sharedPacket.GetPacket(), packetA);\n\n\t\tsharedPacket.Reset();\n\n\t\tREQUIRE(!sharedPacket.HasPacket());\n\t\tREQUIRE(sharedPacket.GetPacket() == nullptr);\n\n\t\tdelete packetA;\n\t\tdelete packetB;\n\t}\n\n\tSECTION(\"constructor with packet and copy constructor\")\n\t{\n\t\t// Create sharedPacket1 using constructor with a Packet.\n\t\tRTC::RTP::SharedPacket sharedPacket1(packetA);\n\n\t\tREQUIRE(sharedPacket1.HasPacket());\n\t\tcompareRtpPackets(sharedPacket1.GetPacket(), packetA);\n\n\t\t// Create sharedPacket2 using copy constructor.\n\t\tRTC::RTP::SharedPacket sharedPacket2(sharedPacket1);\n\n\t\tREQUIRE(sharedPacket2.HasPacket());\n\t\tcompareRtpPackets(sharedPacket2.GetPacket(), packetA);\n\n\t\tsharedPacket2.Assign(packetB);\n\n\t\tREQUIRE(sharedPacket1.HasPacket());\n\t\tcompareRtpPackets(sharedPacket1.GetPacket(), packetB);\n\t\tREQUIRE(sharedPacket2.HasPacket());\n\t\tcompareRtpPackets(sharedPacket2.GetPacket(), packetB);\n\t\tREQUIRE(sharedPacket1.GetPacket() == sharedPacket2.GetPacket());\n\n\t\tsharedPacket1.AssertSamePacket(sharedPacket1.GetPacket());\n\t\tsharedPacket1.AssertSamePacket(sharedPacket2.GetPacket());\n\t\tsharedPacket2.AssertSamePacket(sharedPacket2.GetPacket());\n\t\tsharedPacket2.AssertSamePacket(sharedPacket1.GetPacket());\n\n\t\tsharedPacket1.Reset();\n\n\t\tREQUIRE(!sharedPacket1.HasPacket());\n\t\tREQUIRE(sharedPacket1.GetPacket() == nullptr);\n\t\tREQUIRE(!sharedPacket2.HasPacket());\n\t\tREQUIRE(sharedPacket2.GetPacket() == nullptr);\n\n\t\tdelete packetA;\n\t\tdelete packetB;\n\t}\n\n\tSECTION(\"copy assignment operator\")\n\t{\n\t\tRTC::RTP::SharedPacket sharedPacket1(packetA);\n\n\t\tREQUIRE(sharedPacket1.HasPacket());\n\t\tcompareRtpPackets(sharedPacket1.GetPacket(), packetA);\n\n\t\tRTC::RTP::SharedPacket sharedPacket2;\n\n\t\t// Fill sharedPacket2 using copy assignment operator.\n\t\tsharedPacket2 = sharedPacket1;\n\n\t\tREQUIRE(sharedPacket2.HasPacket());\n\t\tcompareRtpPackets(sharedPacket2.GetPacket(), packetA);\n\n\t\tsharedPacket2.Assign(packetB);\n\n\t\tREQUIRE(sharedPacket1.HasPacket());\n\t\tcompareRtpPackets(sharedPacket1.GetPacket(), packetB);\n\t\tREQUIRE(sharedPacket2.HasPacket());\n\t\tcompareRtpPackets(sharedPacket2.GetPacket(), packetB);\n\t\tREQUIRE(sharedPacket1.GetPacket() == sharedPacket2.GetPacket());\n\n\t\tsharedPacket1.AssertSamePacket(sharedPacket1.GetPacket());\n\t\tsharedPacket1.AssertSamePacket(sharedPacket2.GetPacket());\n\t\tsharedPacket2.AssertSamePacket(sharedPacket2.GetPacket());\n\t\tsharedPacket2.AssertSamePacket(sharedPacket1.GetPacket());\n\n\t\tsharedPacket1.Reset();\n\n\t\tREQUIRE(!sharedPacket1.HasPacket());\n\t\tREQUIRE(sharedPacket1.GetPacket() == nullptr);\n\t\tREQUIRE(!sharedPacket2.HasPacket());\n\t\tREQUIRE(sharedPacket2.GetPacket() == nullptr);\n\n\t\tdelete packetA;\n\t\tdelete packetB;\n\t}\n\n\tSECTION(\"assign nullptr\")\n\t{\n\t\tRTC::RTP::SharedPacket sharedPacket(packetA);\n\n\t\tREQUIRE(sharedPacket.HasPacket());\n\t\tcompareRtpPackets(sharedPacket.GetPacket(), packetA);\n\n\t\tsharedPacket.Assign(nullptr);\n\n\t\tREQUIRE(!sharedPacket.HasPacket());\n\t\tREQUIRE(sharedPacket.GetPacket() == nullptr);\n\n\t\tdelete packetA;\n\t\tdelete packetB;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/RTP/rtpCommon.cpp",
    "content": "#include \"test/include/RTC/RTP/rtpCommon.hpp\" // in worker/test/include/\n#include <cstring>                            // std::memset\n\nnamespace rtpCommon\n{\n\t// NOTE: Buffers must be 4-byte aligned since RTP Packet parsing casts them\n\t// to structs (e.g. FixedHeader, HeaderExtension) that require 4-byte\n\t// alignment. Without this, accessing multi-byte fields would be undefined\n\t// behavior on strict-alignment architectures.\n\talignas(4) thread_local uint8_t FactoryBuffer[];\n\talignas(4) thread_local uint8_t SerializeBuffer[];\n\talignas(4) thread_local uint8_t CloneBuffer[];\n\talignas(4) thread_local uint8_t DataBuffer[];\n\talignas(4) thread_local uint8_t ThrowBuffer[];\n\n\tvoid ResetBuffers()\n\t{\n\t\tstd::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer));\n\t\tstd::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer));\n\t\tstd::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer));\n\t\tstd::memset(DataBuffer, 0xDD, sizeof(DataBuffer));\n\t\tstd::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer));\n\n\t\tfor (size_t i = 0; i < 256; ++i)\n\t\t{\n\t\t\tDataBuffer[i] = static_cast<uint8_t>(i);\n\t\t}\n\t}\n} // namespace rtpCommon\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/association/TestHeartbeatHandler.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"mocks/include/RTC/SCTP/association/MockAssociationListener.hpp\"\n#include \"mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"test/include/catch2Macros.hpp\"\n#include \"test/include/testHelpers.hpp\"\n#include \"RTC/SCTP/association/HeartbeatHandler.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <vector>\n\nSCENARIO(\"SCTP HeartbeatHandler\", \"[sctp][heartbeathandler]\")\n{\n\tconstexpr uint64_t InitialNowMs{ 1000000 };\n\tconstexpr uint64_t HeartbeatIntervalMs{ 30000 };\n\n\tclass TestHeartbeatHandler\n\t{\n\tpublic:\n\t\texplicit TestHeartbeatHandler(uint64_t heartbeatIntervalMs)\n\t\t  // NOTE: The order in which these members are initialized is **critical**.\n\t\t  : sctpOptions(\n\t\t      RTC::SCTP::SctpOptions{\n\t\t        .heartbeatIntervalMs         = heartbeatIntervalMs,\n\t\t        .heartbeatIntervalIncludeRtt = false,\n\t\t        .zeroChecksumAlternateErrorDetectionMethod =\n\t\t          RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE }),\n\t\t    tcbContext(this->associationListener, this->sctpOptions),\n\t\t    shared(/*getTimeMs*/\n\t\t           [this]()\n\t\t           {\n\t\t\t           return this->nowMs;\n\t\t           }),\n\t\t    heartbeatHandler(\n\t\t      this->associationListener,\n\t\t      this->sctpOptions,\n\t\t      std::addressof(this->shared),\n\t\t      std::addressof(this->tcbContext))\n\t\t{\n\t\t\t// Simulate that the SCTP assiciation is connected.\n\t\t\tthis->tcbContext.SetAssociationEstablished(true);\n\t\t};\n\n\tpublic:\n\t\tvoid AdvanceTimeMs(int64_t incrementMs)\n\t\t{\n\t\t\tthis->nowMs += incrementMs;\n\t\t}\n\n\tprivate:\n\t\tuint64_t nowMs{ InitialNowMs };\n\n\t\t// NOTE: Public members for testing.\n\tpublic:\n\t\tRTC::SCTP::SctpOptions sctpOptions;\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tmocks::RTC::SCTP::MockTransmissionControlBlockContext tcbContext;\n\t\tmocks::MockShared shared;\n\t\tRTC::SCTP::HeartbeatHandler heartbeatHandler;\n\t};\n\n\tSECTION(\"has running heartbeat interval timer\")\n\t{\n\t\tTestHeartbeatHandler test(HeartbeatIntervalMs);\n\n\t\ttest.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs);\n\n\t\tauto* heartbeatIntervalTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-interval\");\n\n\t\tREQUIRE(heartbeatIntervalTimer);\n\t\tREQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true);\n\t\tREQUIRE(test.associationListener.HasSentPackets() == true);\n\n\t\tconst std::vector<uint8_t> sentBuffer = test.associationListener.ConsumeFirstSentPacket();\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> sentPacket{ RTC::SCTP::Packet::Parse(\n\t\t\tsentBuffer.data(), sentBuffer.size()) };\n\n\t\tREQUIRE(sentPacket);\n\t\tREQUIRE(sentPacket->GetChunksCount() == 1);\n\n\t\tconst auto* sentHeartbeatRequestChunk =\n\t\t  sentPacket->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>();\n\n\t\tREQUIRE(sentHeartbeatRequestChunk);\n\n\t\tconst auto* sentHeartbeatInfoParameter =\n\t\t  sentHeartbeatRequestChunk->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\tREQUIRE(sentHeartbeatInfoParameter);\n\t\tREQUIRE(sentHeartbeatInfoParameter->HasInfo());\n\t}\n\n\tSECTION(\"replies to heartbeat requests\")\n\t{\n\t\tTestHeartbeatHandler test(HeartbeatIntervalMs);\n\n\t\tstd::unique_ptr<RTC::SCTP::HeartbeatRequestChunk> receivedHeartbeatRequestChunk{\n\t\t\tRTC::SCTP::HeartbeatRequestChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu)\n\t\t};\n\n\t\tauto* receivedHeartbeatInfoParameter =\n\t\t  receivedHeartbeatRequestChunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\treceivedHeartbeatInfoParameter->SetInfo(sctpCommon::DataBuffer, 10);\n\t\treceivedHeartbeatInfoParameter->Consolidate();\n\n\t\ttest.heartbeatHandler.HandleReceivedHeartbeatRequestChunk(receivedHeartbeatRequestChunk.get());\n\n\t\tconst std::vector<uint8_t> sentBuffer = test.associationListener.ConsumeFirstSentPacket();\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> sentPacket{ RTC::SCTP::Packet::Parse(\n\t\t\tsentBuffer.data(), sentBuffer.size()) };\n\n\t\tREQUIRE(sentPacket);\n\t\tREQUIRE(sentPacket->GetChunksCount() == 1);\n\n\t\tconst auto* sentHeartbeatAckChunk =\n\t\t  sentPacket->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>();\n\n\t\tREQUIRE(sentHeartbeatAckChunk);\n\n\t\tconst auto* sentHeartbeatInfoParameter =\n\t\t  sentHeartbeatAckChunk->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\tREQUIRE(sentHeartbeatInfoParameter);\n\t\tREQUIRE(sentHeartbeatInfoParameter->HasInfo());\n\t\tREQUIRE(\n\t\t  helpers::areBuffersEqual(\n\t\t    sentHeartbeatInfoParameter->GetBuffer(),\n\t\t    sentHeartbeatInfoParameter->GetLength(),\n\t\t    receivedHeartbeatInfoParameter->GetBuffer(),\n\t\t    receivedHeartbeatInfoParameter->GetLength()) == true);\n\t}\n\n\tSECTION(\"sends heartbeat requests on idle connections\")\n\t{\n\t\tTestHeartbeatHandler test(HeartbeatIntervalMs);\n\n\t\ttest.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs);\n\n\t\tauto* heartbeatIntervalTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-interval\");\n\n\t\tREQUIRE(heartbeatIntervalTimer);\n\t\tREQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true);\n\t\tREQUIRE(test.associationListener.HasSentPackets() == true);\n\n\t\tconst std::vector<uint8_t> sentBuffer = test.associationListener.ConsumeFirstSentPacket();\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> sentPacket{ RTC::SCTP::Packet::Parse(\n\t\t\tsentBuffer.data(), sentBuffer.size()) };\n\n\t\tREQUIRE(sentPacket);\n\t\tREQUIRE(sentPacket->GetChunksCount() == 1);\n\n\t\tconst auto* sentHeartbeatRequestChunk =\n\t\t  sentPacket->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>();\n\n\t\tREQUIRE(sentHeartbeatRequestChunk);\n\n\t\tconst auto* sentHeartbeatInfoParameter =\n\t\t  sentHeartbeatRequestChunk->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\tREQUIRE(sentHeartbeatInfoParameter);\n\t\tREQUIRE(sentHeartbeatInfoParameter->HasInfo());\n\n\t\tstd::unique_ptr<RTC::SCTP::HeartbeatAckChunk> receivedHeartbeatAckChunk{\n\t\t\tRTC::SCTP::HeartbeatAckChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu)\n\t\t};\n\n\t\tauto* receivedHeartbeatInfoParameter =\n\t\t  receivedHeartbeatAckChunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\treceivedHeartbeatInfoParameter->SetInfo(\n\t\t  sentHeartbeatInfoParameter->GetInfo(), sentHeartbeatInfoParameter->GetInfoLength());\n\t\treceivedHeartbeatInfoParameter->Consolidate();\n\n\t\t// Respond a while later.\n\t\tconst uint64_t rttMs{ 313 };\n\n\t\ttest.tcbContext.ExpectObserveRttMsCalledTimes(1);\n\n\t\ttest.AdvanceTimeMs(rttMs);\n\t\ttest.heartbeatHandler.HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk.get());\n\n\t\tREQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations());\n\t}\n\n\tSECTION(\"doesn't observe RTT on invalid hearbeats receipt\")\n\t{\n\t\tTestHeartbeatHandler test(HeartbeatIntervalMs);\n\n\t\ttest.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs);\n\n\t\tauto* heartbeatIntervalTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-interval\");\n\n\t\tREQUIRE(heartbeatIntervalTimer);\n\t\tREQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true);\n\t\tREQUIRE(test.associationListener.HasSentPackets() == true);\n\n\t\tconst std::vector<uint8_t> sentBuffer = test.associationListener.ConsumeFirstSentPacket();\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> sentPacket{ RTC::SCTP::Packet::Parse(\n\t\t\tsentBuffer.data(), sentBuffer.size()) };\n\n\t\tREQUIRE(sentPacket);\n\t\tREQUIRE(sentPacket->GetChunksCount() == 1);\n\n\t\tconst auto* sentHeartbeatRequestChunk =\n\t\t  sentPacket->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>();\n\n\t\tREQUIRE(sentHeartbeatRequestChunk);\n\n\t\tconst auto* sentHeartbeatInfoParameter =\n\t\t  sentHeartbeatRequestChunk->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\tREQUIRE(sentHeartbeatInfoParameter);\n\t\tREQUIRE(sentHeartbeatInfoParameter->HasInfo());\n\n\t\tstd::unique_ptr<RTC::SCTP::HeartbeatAckChunk> receivedHeartbeatAckChunk{\n\t\t\tRTC::SCTP::HeartbeatAckChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu)\n\t\t};\n\n\t\tauto* receivedHeartbeatInfoParameter =\n\t\t  receivedHeartbeatAckChunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\treceivedHeartbeatInfoParameter->SetInfo(\n\t\t  sentHeartbeatInfoParameter->GetInfo(), sentHeartbeatInfoParameter->GetInfoLength());\n\t\treceivedHeartbeatInfoParameter->Consolidate();\n\n\t\ttest.tcbContext.ExpectObserveRttMsCalledTimes(0);\n\n\t\t// Go backwards in time to make the HEARTBEAT-ACK have an invalid timestamp\n\t\t// in it, as it will be in the future.\n\t\ttest.AdvanceTimeMs(-100);\n\t\ttest.heartbeatHandler.HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk.get());\n\n\t\tREQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations());\n\t}\n\n\tSECTION(\"increases error if heartbeat request is not acked in time\")\n\t{\n\t\tTestHeartbeatHandler test(HeartbeatIntervalMs);\n\n\t\tconst uint64_t rtoMs{ 105 };\n\n\t\ttest.tcbContext.WillGetCurrentRtoMsOnce(\n\t\t  []()\n\t\t  {\n\t\t\t  return rtoMs;\n\t\t  });\n\n\t\ttest.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs);\n\n\t\tauto* heartbeatIntervalTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-interval\");\n\n\t\tREQUIRE(heartbeatIntervalTimer);\n\t\tREQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true);\n\n\t\t// Validate that a request was sent.\n\t\tREQUIRE(test.associationListener.HasSentPackets() == true);\n\n\t\ttest.tcbContext.ExpectIncrementTxErrorCounterCalledTimes(1);\n\n\t\ttest.AdvanceTimeMs(rtoMs);\n\n\t\tauto* heartbeatTimeoutTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-timeout\");\n\n\t\tREQUIRE(heartbeatTimeoutTimer);\n\t\tREQUIRE(heartbeatTimeoutTimer->EvaluateHasExpired() == true);\n\t\tREQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations());\n\t}\n\n\tSECTION(\"doesn't send heartbeat requests when disabled\")\n\t{\n\t\tTestHeartbeatHandler test(0);\n\n\t\ttest.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs);\n\n\t\tauto* heartbeatIntervalTimer = test.shared.GetBackoffTimer(\"sctp-heartbeat-interval\");\n\n\t\tREQUIRE(heartbeatIntervalTimer);\n\t\tREQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == false);\n\t\tREQUIRE(test.associationListener.HasSentPackets() == false);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/association/TestNegotiatedCapabilities.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP Negotiated Capabilities\", \"[sctp][negotiatedcapabilities]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"NegotiatedCapabilities::Factory() succeeds (1)\")\n\t{\n\t\tRTC::SCTP::SctpOptions sctpOptions{};\n\n\t\tsctpOptions.announcedMaxOutboundStreams = 8192;\n\t\tsctpOptions.announcedMaxInboundStreams  = 2048;\n\t\tsctpOptions.enablePartialReliability    = true;\n\t\tsctpOptions.enableMessageInterleaving   = true;\n\t\tsctpOptions.zeroChecksumAlternateErrorDetectionMethod =\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;\n\n\t\tauto* remoteChunk =\n\t\t  RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tremoteChunk->SetNumberOfOutboundStreams(4096);\n\t\tremoteChunk->SetNumberOfInboundStreams(1024);\n\n\t\tauto* remoteSupportedExtensionsParameter =\n\t\t  remoteChunk->BuildParameterInPlace<RTC::SCTP::SupportedExtensionsParameter>();\n\n\t\tremoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::FORWARD_TSN);\n\t\tremoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tremoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA);\n\t\tremoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN);\n\t\tremoteSupportedExtensionsParameter->Consolidate();\n\n\t\tauto* remoteZeroChecksumAcceptableParameter =\n\t\t  remoteChunk->BuildParameterInPlace<RTC::SCTP::ZeroChecksumAcceptableParameter>();\n\n\t\tremoteZeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod(\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\t\tremoteZeroChecksumAcceptableParameter->Consolidate();\n\n\t\tauto negotiatedCapabilities =\n\t\t  RTC::SCTP::NegotiatedCapabilities::Factory(sctpOptions, remoteChunk);\n\n\t\tdelete remoteChunk;\n\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 1024);\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2048);\n\t\tREQUIRE(negotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(negotiatedCapabilities.messageInterleaving == true);\n\t\tREQUIRE(negotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(negotiatedCapabilities.zeroChecksum == true);\n\t}\n\n\tSECTION(\"NegotiatedCapabilities::Factory() succeeds (2)\")\n\t{\n\t\tRTC::SCTP::SctpOptions sctpOptions{};\n\n\t\tsctpOptions.announcedMaxOutboundStreams = 1000;\n\t\tsctpOptions.announcedMaxInboundStreams  = 2000;\n\t\tsctpOptions.enablePartialReliability    = true;\n\t\tsctpOptions.enableMessageInterleaving   = true;\n\t\tsctpOptions.zeroChecksumAlternateErrorDetectionMethod =\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS;\n\n\t\tauto* remoteChunk =\n\t\t  RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tremoteChunk->SetNumberOfOutboundStreams(4000);\n\t\tremoteChunk->SetNumberOfInboundStreams(3000);\n\n\t\tauto* remoteSupportedExtensionsParameter =\n\t\t  remoteChunk->BuildParameterInPlace<RTC::SCTP::SupportedExtensionsParameter>();\n\n\t\t// NOTE: Missing FORWARD_TSN, but peer announced support for it via\n\t\t// Forward-TSN-Supported Parameter negotiation).\n\t\t// NOTE: Missing RE_CONFIG (needed for Partial Reliability Extension\n\t\t// negotiation).\n\t\t// NOTE: Missing I_FORWARD_TSN (needed for Message Interleaving negotiation).\n\t\tremoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA);\n\t\tremoteSupportedExtensionsParameter->Consolidate();\n\n\t\tauto* remoteForwardTsnSupportedParameter =\n\t\t  remoteChunk->BuildParameterInPlace<RTC::SCTP::ForwardTsnSupportedParameter>();\n\n\t\tremoteForwardTsnSupportedParameter->Consolidate();\n\n\t\tauto* remoteZeroChecksumAcceptableParameter =\n\t\t  remoteChunk->BuildParameterInPlace<RTC::SCTP::ZeroChecksumAcceptableParameter>();\n\n\t\tremoteZeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod(\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  static_cast<RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod>(666));\n\t\tremoteZeroChecksumAcceptableParameter->Consolidate();\n\n\t\tauto negotiatedCapabilities =\n\t\t  RTC::SCTP::NegotiatedCapabilities::Factory(sctpOptions, remoteChunk);\n\n\t\tdelete remoteChunk;\n\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 1000);\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2000);\n\t\tREQUIRE(negotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(negotiatedCapabilities.messageInterleaving == false);\n\t\tREQUIRE(negotiatedCapabilities.reConfig == false);\n\t\tREQUIRE(negotiatedCapabilities.zeroChecksum == false);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/association/TestStateCookie.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\" // in worker/test/include/\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP State Cookie\", \"[sctp][statecookie]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"StateCookie::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\tuint8_t buffer[] =\n\t\t{\n\t\t\t// Magic 1: 0x6D73776F726B6572\n\t\t\t0x6D, 0x73, 0x77, 0x6F,\n\t\t\t0x72, 0x6B, 0x65, 0x72,\n\t\t\t// Local Verification Tag: 11223344\n\t\t\t0x00, 0xAB, 0x41, 0x30,\n\t\t\t// Remote Verification Tag: 55667788\n\t\t\t0x03, 0x51, 0x6C, 0x4C,\n\t\t\t// Local Initial TSN: 12345678\n\t\t\t0x00, 0xBC, 0x61, 0x4E,\n\t\t\t// Remote Initial TSN: 87654321\n\t\t\t0x05, 0x39, 0x7F, 0xB1,\n\t\t\t// Remote Advertised Receiver Window Credit (a_rwnd): 66666666\n\t\t\t0x03, 0xF9, 0x40, 0xAA,\n\t\t\t// Tie-Tag: 0xABCDEF0011223344\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Negotiated Capabilities\n\t\t\t// - partialReliability: 1\n\t\t\t// - messageInterleaving: 0\n\t\t\t// - re-config: 1\n\t\t\t// - zeroChecksum: 1\n\t\t\t// Magic 2: 0xAD81\n\t\t\t0x00, 0b00001101, 0xAD, 0x81,\n\t\t\t// Max Outbound Streams: 15000, Max Inbound Streams: 2500\n\t\t\t0x3A, 0x98, 0x09, 0xC4\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer, sizeof(buffer)) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer, sizeof(buffer)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tauto* stateCookie = RTC::SCTP::StateCookie::Parse(buffer, sizeof(buffer));\n\n\t\tREQUIRE(stateCookie);\n\t\tREQUIRE(stateCookie->GetBuffer() == buffer);\n\t\tREQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetLocalVerificationTag() == 11223344);\n\t\tREQUIRE(stateCookie->GetRemoteVerificationTag() == 55667788);\n\t\tREQUIRE(stateCookie->GetLocalInitialTsn() == 12345678);\n\t\tREQUIRE(stateCookie->GetRemoteInitialTsn() == 87654321);\n\t\tREQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666);\n\t\tREQUIRE(stateCookie->GetTieTag() == 0xABCDEF0011223344);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tauto negotiatedCapabilities = stateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000);\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500);\n\t\tREQUIRE(negotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(negotiatedCapabilities.messageInterleaving == false);\n\t\tREQUIRE(negotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(negotiatedCapabilities.zeroChecksum == true);\n\n\t\t/* Serialize it. */\n\n\t\tstateCookie->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tREQUIRE(stateCookie);\n\t\tREQUIRE(stateCookie->GetBuffer() == sctpCommon::SerializeBuffer);\n\t\tREQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetBufferLength() == sizeof(sctpCommon::SerializeBuffer));\n\t\tREQUIRE(stateCookie->GetLocalVerificationTag() == 11223344);\n\t\tREQUIRE(stateCookie->GetRemoteVerificationTag() == 55667788);\n\t\tREQUIRE(stateCookie->GetLocalInitialTsn() == 12345678);\n\t\tREQUIRE(stateCookie->GetRemoteInitialTsn() == 87654321);\n\t\tREQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666);\n\t\tREQUIRE(stateCookie->GetTieTag() == 0xABCDEF0011223344);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tnegotiatedCapabilities = stateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000);\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500);\n\t\tREQUIRE(negotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(negotiatedCapabilities.messageInterleaving == false);\n\t\tREQUIRE(negotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(negotiatedCapabilities.zeroChecksum == true);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedStateCookie =\n\t\t  stateCookie->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete stateCookie;\n\n\t\tREQUIRE(clonedStateCookie);\n\t\tREQUIRE(clonedStateCookie->GetBuffer() == sctpCommon::CloneBuffer);\n\t\tREQUIRE(clonedStateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(clonedStateCookie->GetBufferLength() == sizeof(sctpCommon::CloneBuffer));\n\t\tREQUIRE(clonedStateCookie->GetLocalVerificationTag() == 11223344);\n\t\tREQUIRE(clonedStateCookie->GetRemoteVerificationTag() == 55667788);\n\t\tREQUIRE(clonedStateCookie->GetLocalInitialTsn() == 12345678);\n\t\tREQUIRE(clonedStateCookie->GetRemoteInitialTsn() == 87654321);\n\t\tREQUIRE(clonedStateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666);\n\t\tREQUIRE(clonedStateCookie->GetTieTag() == 0xABCDEF0011223344);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    clonedStateCookie->GetBuffer(), clonedStateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    clonedStateCookie->GetBuffer(), clonedStateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tnegotiatedCapabilities = clonedStateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000);\n\t\tREQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500);\n\t\tREQUIRE(negotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(negotiatedCapabilities.messageInterleaving == false);\n\t\tREQUIRE(negotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(negotiatedCapabilities.zeroChecksum == true);\n\n\t\tdelete clonedStateCookie;\n\t}\n\n\tSECTION(\"StateCookie::Parse() fails\")\n\t{\n\t\t// Wrong Magic 1.\n\t\t// clang-format off\n\t\tuint8_t buffer1[] =\n\t\t{\n\t\t\t// Magic 1: 0x6D73776F726B6573 (wrong)\n\t\t\t0x6D, 0x73, 0x77, 0x6F,\n\t\t\t0x72, 0x6B, 0x65, 0x73,\n\t\t\t// Local Verification Tag: 11223344\n\t\t\t0x00, 0xAB, 0x41, 0x30,\n\t\t\t// Remote Verification Tag: 55667788\n\t\t\t0x03, 0x51, 0x6C, 0x4C,\n\t\t\t// Local Initial TSN: 12345678\n\t\t\t0x00, 0xBC, 0x61, 0x4E,\n\t\t\t// Remote Initial TSN: 87654321\n\t\t\t0x05, 0x39, 0x7F, 0xB1,\n\t\t\t// Remote Advertised Receiver Window Credit (a_rwnd): 66666666\n\t\t\t0x03, 0xF9, 0x40, 0xAA,\n\t\t\t// Tie-Tag: 0xABCDEF0011223344\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Negotiated Capabilities\n\t\t\t// - partialReliability: 1\n\t\t\t// - messageInterleaving: 0\n\t\t\t// - re-config: 1\n\t\t\t// - zeroChecksum: 1\n\t\t\t// Magic 2: 0xAD81\n\t\t\t0x00, 0b00001101, 0xAD, 0x81,\n\t\t\t// Max Outbound Streams: 15000, Max Inbound Streams: 2500\n\t\t\t0x3A, 0x98, 0x09, 0xC4\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer1, sizeof(buffer1)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer1, sizeof(buffer1)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::UNKNOWN);\n\t\tREQUIRE(!RTC::SCTP::StateCookie::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Magic 2.\n\t\t// clang-format off\n\t\tuint8_t buffer2[] =\n\t\t{\n\t\t\t// Magic 1: 0x6D73776F726B6572\n\t\t\t0x6D, 0x73, 0x77, 0x6F,\n\t\t\t0x72, 0x6B, 0x65, 0x72,\n\t\t\t// Local Verification Tag: 11223344\n\t\t\t0x00, 0xAB, 0x41, 0x30,\n\t\t\t// Remote Verification Tag: 55667788\n\t\t\t0x03, 0x51, 0x6C, 0x4C,\n\t\t\t// Local Initial TSN: 12345678\n\t\t\t0x00, 0xBC, 0x61, 0x4E,\n\t\t\t// Remote Initial TSN: 87654321\n\t\t\t0x05, 0x39, 0x7F, 0xB1,\n\t\t\t// Remote Advertised Receiver Window Credit (a_rwnd): 66666666\n\t\t\t0x03, 0xF9, 0x40, 0xAA,\n\t\t\t// Tie-Tag: 0xABCDEF0011223344\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Negotiated Capabilities\n\t\t\t// - partialReliability: 1\n\t\t\t// - messageInterleaving: 0\n\t\t\t// - re-config: 1\n\t\t\t// - zeroChecksum: 1\n\t\t\t// Magic 2: 0xAD82 (instead of 0xAD81)\n\t\t\t0x00, 0b00001101, 0xAD, 0x82,\n\t\t\t// Max Outbound Streams: 15000, Max Inbound Streams: 2500\n\t\t\t0x3A, 0x98, 0x09, 0xC4\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer2, sizeof(buffer2)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer2, sizeof(buffer2)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\t\tREQUIRE(!RTC::SCTP::StateCookie::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Buffer too big.\n\t\t// clang-format off\n\t\tuint8_t buffer3[] =\n\t\t{\n\t\t\t// Magic 1: 0x6D73776F726B6572\n\t\t\t0x6D, 0x73, 0x77, 0x6F,\n\t\t\t0x72, 0x6B, 0x65, 0x72,\n\t\t\t// Local Verification Tag: 11223344\n\t\t\t0x00, 0xAB, 0x41, 0x30,\n\t\t\t// Remote Verification Tag: 55667788\n\t\t\t0x03, 0x51, 0x6C, 0x4C,\n\t\t\t// Local Initial TSN: 12345678\n\t\t\t0x00, 0xBC, 0x61, 0x4E,\n\t\t\t// Remote Initial TSN: 87654321\n\t\t\t0x05, 0x39, 0x7F, 0xB1,\n\t\t\t// Remote Advertised Receiver Window Credit (a_rwnd): 66666666\n\t\t\t0x03, 0xF9, 0x40, 0xAA,\n\t\t\t// Tie-Tag: 0xABCDEF0011223344\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Negotiated Capabilities\n\t\t\t// - partialReliability: 1\n\t\t\t// - messageInterleaving: 0\n\t\t\t// - re-config: 1\n\t\t\t// - zeroChecksum: 1\n\t\t\t// Magic 2: 0xAD81\n\t\t\t0x00, 0b00001101, 0xAD, 0x81,\n\t\t\t// Max Outbound Streams: 15000, Max Inbound Streams: 2500\n\t\t\t0x3A, 0x98, 0x09, 0xC4,\n\t\t\t// Extra bytes that shouldn't be here.\n\t\t\t0x11, 0x22, 0x33, 0x44\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer3, sizeof(buffer3)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer3, sizeof(buffer3)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\t\tREQUIRE(!RTC::SCTP::StateCookie::Parse(buffer3, sizeof(buffer3)));\n\t}\n\n\tSECTION(\"StateCookie::Factory() succeeds\")\n\t{\n\t\tRTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = { .negotiatedMaxOutboundStreams = 62000,\n\t\t\t                                                           .negotiatedMaxInboundStreams = 55555,\n\t\t\t                                                           .partialReliability  = true,\n\t\t\t                                                           .messageInterleaving = true,\n\t\t\t                                                           .reConfig            = true,\n\t\t\t                                                           .zeroChecksum        = false };\n\n\t\tauto* stateCookie = RTC::SCTP::StateCookie::Factory(\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*localVerificationTag*/ 6660666,\n\t\t  /*remoteVerificationTag*/ 9990999,\n\t\t  /*localInitialTsn*/ 1110111,\n\t\t  /*remoteInitialTsn*/ 2220222,\n\t\t  /*remoteAdvertisedReceiverWindowCredit*/ 999909999,\n\t\t  /*tieTag*/ 1111222233334444,\n\t\t  negotiatedCapabilities);\n\n\t\t// Change values of the original NegotiatedCapabilities to assert that it\n\t\t// doesn't affect the internals of StateCookie.\n\t\tnegotiatedCapabilities.partialReliability           = false;\n\t\tnegotiatedCapabilities.negotiatedMaxOutboundStreams = 1024;\n\n\t\tREQUIRE(stateCookie);\n\t\tREQUIRE(stateCookie->GetBuffer() == sctpCommon::FactoryBuffer);\n\t\tREQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetLocalVerificationTag() == 6660666);\n\t\tREQUIRE(stateCookie->GetRemoteVerificationTag() == 9990999);\n\t\tREQUIRE(stateCookie->GetLocalInitialTsn() == 1110111);\n\t\tREQUIRE(stateCookie->GetRemoteInitialTsn() == 2220222);\n\t\tREQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999);\n\t\tREQUIRE(stateCookie->GetTieTag() == 1111222233334444);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tconst auto retrievedNegotiatedCapabilities = stateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.messageInterleaving == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.zeroChecksum == false);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedStateCookie =\n\t\t  RTC::SCTP::StateCookie::Parse(stateCookie->GetBuffer(), stateCookie->GetLength());\n\n\t\tdelete stateCookie;\n\n\t\tREQUIRE(parsedStateCookie);\n\t\tREQUIRE(parsedStateCookie->GetBuffer() == sctpCommon::FactoryBuffer);\n\t\tREQUIRE(parsedStateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(parsedStateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(parsedStateCookie->GetLocalVerificationTag() == 6660666);\n\t\tREQUIRE(parsedStateCookie->GetRemoteVerificationTag() == 9990999);\n\t\tREQUIRE(parsedStateCookie->GetLocalInitialTsn() == 1110111);\n\t\tREQUIRE(parsedStateCookie->GetRemoteInitialTsn() == 2220222);\n\t\tREQUIRE(parsedStateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999);\n\t\tREQUIRE(parsedStateCookie->GetTieTag() == 1111222233334444);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    parsedStateCookie->GetBuffer(), parsedStateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    parsedStateCookie->GetBuffer(), parsedStateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tconst auto retrievedParsedNegotiatedCapabilities = parsedStateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000);\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555);\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.messageInterleaving == true);\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(retrievedParsedNegotiatedCapabilities.zeroChecksum == false);\n\n\t\tdelete parsedStateCookie;\n\t}\n\n\tSECTION(\"StateCookie::Write() succeeds\")\n\t{\n\t\tRTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = { .negotiatedMaxOutboundStreams = 62000,\n\t\t\t                                                           .negotiatedMaxInboundStreams = 55555,\n\t\t\t                                                           .partialReliability  = true,\n\t\t\t                                                           .messageInterleaving = true,\n\t\t\t                                                           .reConfig            = true,\n\t\t\t                                                           .zeroChecksum        = false };\n\n\t\tauto* buffer = sctpCommon::FactoryBuffer;\n\n\t\tRTC::SCTP::StateCookie::Write(\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ RTC::SCTP::StateCookie::StateCookieLength,\n\t\t  /*localVerificationTag*/ 6660666,\n\t\t  /*remoteVerificationTag*/ 9990999,\n\t\t  /*localInitialTsn*/ 1110111,\n\t\t  /*remoteInitialTsn*/ 2220222,\n\t\t  /*remoteAdvertisedReceiverWindowCredit*/ 999909999,\n\t\t  /*tieTag*/ 1111222233334444,\n\t\t  negotiatedCapabilities);\n\n\t\t// Change values of the original NegotiatedCapabilities to assert that it\n\t\t// doesn't affect the internals of StateCookie.\n\t\tnegotiatedCapabilities.partialReliability           = false;\n\t\tnegotiatedCapabilities.negotiatedMaxOutboundStreams = 1024;\n\n\t\t/* Parse the buffer. */\n\n\t\tauto* stateCookie =\n\t\t  RTC::SCTP::StateCookie::Parse(buffer, RTC::SCTP::StateCookie::StateCookieLength);\n\n\t\tREQUIRE(stateCookie);\n\t\tREQUIRE(stateCookie->GetBuffer() == buffer);\n\t\tREQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\t\tREQUIRE(stateCookie->GetLocalVerificationTag() == 6660666);\n\t\tREQUIRE(stateCookie->GetRemoteVerificationTag() == 9990999);\n\t\tREQUIRE(stateCookie->GetLocalInitialTsn() == 1110111);\n\t\tREQUIRE(stateCookie->GetRemoteInitialTsn() == 2220222);\n\t\tREQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999);\n\t\tREQUIRE(stateCookie->GetTieTag() == 1111222233334444);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::IsMediasoupStateCookie(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) == true);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(\n\t\t    stateCookie->GetBuffer(), stateCookie->GetLength()) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::MEDIASOUP);\n\n\t\tconst auto retrievedNegotiatedCapabilities = stateCookie->GetNegotiatedCapabilities();\n\n\t\tREQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.partialReliability == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.messageInterleaving == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.reConfig == true);\n\t\tREQUIRE(retrievedNegotiatedCapabilities.zeroChecksum == false);\n\n\t\tdelete stateCookie;\n\t}\n\n\tSECTION(\"StateCookie::DetermineSctpImplementation() succeeds\")\n\t{\n\t\t// usrsctp generated State Cookie.\n\t\t// clang-format off\n\t\tuint8_t buffer1[] =\n\t\t{\n\t\t\t// Magic 1: 0x4B414D452D425344\n\t\t\t0x4B, 0x41, 0x4D, 0x45,\n\t\t\t0x2D, 0x42, 0x53, 0x44,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x6D, 0x73, 0x77, 0x6F,\n\t\t\t0x72, 0x6B, 0x65, 0x72,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x00, 0xAB, 0x41, 0x30,\n\t\t\t0x03, 0x51, 0x6C, 0x4C,\n\t\t\t0x00, 0xBC, 0x61, 0x4E,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x05, 0x39, 0x7F, 0xB1,\n\t\t\t0x03, 0xF9, 0x40, 0xAA,\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// etc\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer1, sizeof(buffer1)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer1, sizeof(buffer1)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::USRSCTP);\n\n\t\t// dcSCTP generated State Cookie.\n\t\t// clang-format off\n\t\tuint8_t buffer2[] =\n\t\t{\n\t\t\t// Magic 1: 0x6463534354503030\n\t\t\t0x64, 0x63, 0x53, 0x43,\n\t\t\t0x54, 0x50, 0x30, 0x30,\n\t\t\t0x5D, 0x0E, 0x21, 0xE4,\n\t\t\t0x0F, 0xA8, 0x44, 0x3F,\n\t\t\t0x11, 0x80, 0x89, 0x5D,\n\t\t\t0x2F, 0x4E, 0x17, 0x1F,\n\t\t\t0x00, 0x02, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x01, 0x00, 0x01, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer2, sizeof(buffer2)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer2, sizeof(buffer2)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::DCSCTP);\n\n\t\t// State Cookie generated by unknown implementation.\n\t\t// clang-format off\n\t\tuint8_t buffer3[] =\n\t\t{\n\t\t\t// Magic 1: 0x1122334455667788\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t0x11, 0x80, 0x89, 0x5D,\n\t\t\t0x2F, 0x4E, 0x17, 0x1F,\n\t\t\t0x00, 0x02, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00,\n\t\t\t0x01, 0x00, 0x01, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer3, sizeof(buffer3)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer3, sizeof(buffer3)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::UNKNOWN);\n\n\t\t// Too short State Cookie so we don't know.\n\t\t// clang-format off\n\t\tuint8_t buffer4[] =\n\t\t{\n\t\t\t// Magic 1: 0xAABBCCDD\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer4, sizeof(buffer4)) == false);\n\t\tREQUIRE(\n\t\t  RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer4, sizeof(buffer4)) ==\n\t\t  RTC::SCTP::Types::SctpImplementation::UNKNOWN);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/TestChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP Chunk\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"alignof() SCTP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::SCTP::Chunk::ChunkHeader) == 2);\n\t\tREQUIRE(alignof(RTC::SCTP::Chunk::ChunkFlags) == 1);\n\t}\n\n\tSECTION(\"BuildParameterInPlace() and AddParameter() throw if the Chunk needs consolidation\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::InitChunk> chunk{ RTC::SCTP::InitChunk::Factory(\n\t\t\tsctpCommon::FactoryBuffer, 1000) };\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tconst auto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::ForwardTsnSupportedParameter>();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == true);\n\n\t\t// We didn't call parameter1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(\n\t\t  chunk->BuildParameterInPlace<RTC::SCTP::ZeroChecksumAcceptableParameter>(), MediaSoupError);\n\n\t\tconst auto* parameter2 = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// We didn't call parameter1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(chunk->AddParameter(parameter2), MediaSoupError);\n\n\t\tdelete parameter2;\n\n\t\tparameter1->Consolidate();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\t// This shouldn't throw now.\n\t\tconst auto* parameter3 =\n\t\t  chunk->BuildParameterInPlace<RTC::SCTP::ZeroChecksumAcceptableParameter>();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == true);\n\n\t\tparameter3->Consolidate();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tconst auto* parameter4 = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// This shouldn't throw now.\n\t\tchunk->AddParameter(parameter4);\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tdelete parameter4;\n\t}\n\n\tSECTION(\"BuildErrorCauseInPlace() and AddErrorCause() throw if the Chunk needs consolidation\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::AbortAssociationChunk> chunk{\n\t\t\tRTC::SCTP::AbortAssociationChunk::Factory(sctpCommon::FactoryBuffer, 1000)\n\t\t};\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tconst auto* errorCause1 = chunk->BuildErrorCauseInPlace<RTC::SCTP::OutOfResourceErrorCause>();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == true);\n\n\t\t// We didn't call errorCause1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(\n\t\t  chunk->BuildErrorCauseInPlace<RTC::SCTP::ProtocolViolationErrorCause>(), MediaSoupError);\n\n\t\tconst auto* errorCause2 = RTC::SCTP::ProtocolViolationErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// We didn't call errorCause1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(chunk->AddErrorCause(errorCause2), MediaSoupError);\n\n\t\tdelete errorCause2;\n\n\t\terrorCause1->Consolidate();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\t// This shouldn't throw now.\n\t\tconst auto* errorCause3 = chunk->BuildErrorCauseInPlace<RTC::SCTP::ProtocolViolationErrorCause>();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == true);\n\n\t\terrorCause3->Consolidate();\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tconst auto* errorCause4 = RTC::SCTP::ProtocolViolationErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// This shouldn't throw now.\n\t\tchunk->AddErrorCause(errorCause4);\n\n\t\tREQUIRE(chunk->NeedsConsolidation() == false);\n\n\t\tdelete errorCause4;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/TestErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP Error Cause\", \"[serializable][sctp][errorcause]\")\n{\n\tSECTION(\"alignof() SCTP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::SCTP::ErrorCause::ErrorCauseHeader) == 2);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/TestPacket.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/UnknownChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Packet\", \"[serializable][sctp][packet]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"alignof() SCTP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::SCTP::Packet::CommonHeader) == 4);\n\t}\n\n\tSECTION(\"Parse() without Chunks succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Source Port: 10000, Destination Port: 15999\n\t\t\t0x27, 0x10, 0x3E, 0x7F,\n\t\t\t// Verification Tag: 4294967285\n\t\t\t0xFF, 0xFF, 0xFF, 0xF5,\n\t\t\t// Checksum: 5\n\t\t\t0x00, 0x00, 0x00, 0x05\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\t// NOTE: Obviously the Checksum CRC32C validation fails since Checksum is\n\t\t// totally random.\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == nullptr);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == nullptr);\n\n\t\t/* Insert CRC32C checksum. */\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == nullptr);\n\t}\n\n\tSECTION(\"Parse() with Chunks succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Source Port: 10000, Destination Port: 15999\n\t\t\t0x27, 0x10, 0x3E, 0x7F,\n\t\t\t// Verification Tag: 4294967285\n\t\t\t0xFF, 0xFF, 0xFF, 0xF5,\n\t\t\t// Checksum: 5\n\t\t\t0x00, 0x00, 0x00, 0x05,\n\t\t\t// Chunk 1: Type:0 (DATA), I:1, U:0, B:1, E:1, Length: 18\n\t\t\t0x00, 0b00001011, 0x00, 0x12,\n\t\t\t// TSN: 0x11223344,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream Identifier S: 0xFF00, Stream Sequence Number n: 0x6677\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Payload Protocol Identifier: 0x12341234\n\t\t\t0x12, 0x34, 0x12, 0x34,\n\t\t\t// User Data (2 bytes): 0xABCD, 2 bytes of padding\n\t\t\t0xAB, 0xCD, 0x00, 0x00,\n\t\t\t// Chunk 2: Type:0xEE (UNKNOWN), Flags: 0b00001100, Length: 7\n\t\t\t0xEE, 0b00001100, 0x00, 0x07,\n\t\t\t// Unknown data: 0xAABBCC, 1 byte of padding\n\t\t\t0xAA, 0xBB, 0xCC, 0x00,\n\t\t\t// Chunk 3: Type:5 (HEARTBEAT_ACK), Flags:0b00000000, Length: 10\n\t\t\t// NOTE: Chunk Length field must exclude padding of the last Parameter.\n\t\t\t0x05, 0b00000000, 0x00, 0x0A,\n\t\t\t// Parameter 1: Type:1 (HEARBEAT_INFO), Length: 6\n\t\t\t0x00, 0x01, 0x00, 0x06,\n\t\t\t// Heartbeat Information (2 bytes): 0x1122, 2 bytes of padding\n\t\t\t0x11, 0x22, 0x00, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 52,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 3);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::UnknownChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() == nullptr);\n\n\t\tconst auto* chunk1 = reinterpret_cast<const RTC::SCTP::DataChunk*>(packet->GetChunkAt(0));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == chunk1);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk1->GetI() == true);\n\t\tREQUIRE(chunk1->GetU() == false);\n\t\tREQUIRE(chunk1->GetB() == true);\n\t\tREQUIRE(chunk1->GetE() == true);\n\t\tREQUIRE(chunk1->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk1->GetStreamId() == 0xFF00);\n\t\tREQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(chunk1->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk1->GetUserDataPayloadLength() == 2);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD);\n\n\t\tconst auto* chunk2 = reinterpret_cast<const RTC::SCTP::UnknownChunk*>(packet->GetChunkAt(1));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::UnknownChunk>() == chunk2);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk2->HasUnknownValue() == true);\n\t\tREQUIRE(chunk2->GetUnknownValueLength() == 3);\n\t\tREQUIRE(chunk2->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(chunk2->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(chunk2->GetUnknownValue()[2] == 0xCC);\n\t\t// Padding.\n\t\tREQUIRE(chunk2->GetUnknownValue()[3] == 0x00);\n\n\t\tconst auto* chunk3 = reinterpret_cast<const RTC::SCTP::HeartbeatAckChunk*>(packet->GetChunkAt(2));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>() == chunk3);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tconst auto* parameter3_1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk3->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3_1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3_1->HasInfo() == true);\n\t\tREQUIRE(parameter3_1->GetInfoLength() == 2);\n\t\tREQUIRE(parameter3_1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter3_1->GetInfo()[1] == 0x22);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter3_1->GetInfo()[2] == 0x00);\n\t\tREQUIRE(parameter3_1->GetInfo()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tpacket->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 52,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 3);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == chunk1);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::UnknownChunk>() == chunk2);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>() == chunk3);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() == nullptr);\n\n\t\tchunk1 = reinterpret_cast<const RTC::SCTP::DataChunk*>(packet->GetChunkAt(0));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk1->GetI() == true);\n\t\tREQUIRE(chunk1->GetU() == false);\n\t\tREQUIRE(chunk1->GetB() == true);\n\t\tREQUIRE(chunk1->GetE() == true);\n\t\tREQUIRE(chunk1->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk1->GetStreamId() == 0xFF00);\n\t\tREQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(chunk1->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk1->GetUserDataPayloadLength() == 2);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD);\n\n\t\tchunk2 = reinterpret_cast<const RTC::SCTP::UnknownChunk*>(packet->GetChunkAt(1));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk2->HasUnknownValue() == true);\n\t\tREQUIRE(chunk2->GetUnknownValueLength() == 3);\n\t\tREQUIRE(chunk2->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(chunk2->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(chunk2->GetUnknownValue()[2] == 0xCC);\n\t\t// Padding.\n\t\tREQUIRE(chunk2->GetUnknownValue()[3] == 0x00);\n\n\t\tchunk3 = reinterpret_cast<const RTC::SCTP::HeartbeatAckChunk*>(packet->GetChunkAt(2));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter3_1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk3->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3_1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3_1->HasInfo() == true);\n\t\tREQUIRE(parameter3_1->GetInfoLength() == 2);\n\t\tREQUIRE(parameter3_1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter3_1->GetInfo()[1] == 0x22);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter3_1->GetInfo()[2] == 0x00);\n\t\tREQUIRE(parameter3_1->GetInfo()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tpacket.reset(packet->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 52,\n\t\t  /*sourcePort*/ 10000,\n\t\t  /*destinationPort*/ 15999,\n\t\t  /*verificationTag*/ 4294967285,\n\t\t  /*checksum*/ 5,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 3);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::UnknownChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>() != nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == nullptr);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() == nullptr);\n\n\t\tchunk1 = reinterpret_cast<const RTC::SCTP::DataChunk*>(packet->GetChunkAt(0));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::DataChunk>() == chunk1);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk1->GetI() == true);\n\t\tREQUIRE(chunk1->GetU() == false);\n\t\tREQUIRE(chunk1->GetB() == true);\n\t\tREQUIRE(chunk1->GetE() == true);\n\t\tREQUIRE(chunk1->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk1->GetStreamId() == 0xFF00);\n\t\tREQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(chunk1->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk1->GetUserDataPayloadLength() == 2);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD);\n\n\t\tchunk2 = reinterpret_cast<const RTC::SCTP::UnknownChunk*>(packet->GetChunkAt(1));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::UnknownChunk>() == chunk2);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk2->HasUnknownValue() == true);\n\t\tREQUIRE(chunk2->GetUnknownValueLength() == 3);\n\t\tREQUIRE(chunk2->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(chunk2->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(chunk2->GetUnknownValue()[2] == 0xCC);\n\t\t// Padding.\n\t\tREQUIRE(chunk2->GetUnknownValue()[3] == 0x00);\n\n\t\tchunk3 = reinterpret_cast<const RTC::SCTP::HeartbeatAckChunk*>(packet->GetChunkAt(2));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatAckChunk>() == chunk3);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter3_1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk3->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3_1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3_1->HasInfo() == true);\n\t\tREQUIRE(parameter3_1->GetInfoLength() == 2);\n\t\tREQUIRE(parameter3_1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter3_1->GetInfo()[1] == 0x22);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter3_1->GetInfo()[2] == 0x00);\n\t\tREQUIRE(parameter3_1->GetInfo()[3] == 0x00);\n\t}\n\n\tSECTION(\"Factory() with Chunks succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 0,\n\t\t  /*destinationPort*/ 0,\n\t\t  /*verificationTag*/ 0,\n\t\t  /*checksum*/ 0,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == nullptr);\n\n\t\t/* Modify the Packet and add Chunks. */\n\n\t\tpacket->SetSourcePort(1000);\n\t\tpacket->SetDestinationPort(6000);\n\t\tpacket->SetVerificationTag(12345678);\n\t\tpacket->SetChecksum(0);\n\n\t\t// Chunk 1: INIT, length: 20 bytes.\n\t\tauto* chunk1 = packet->BuildChunkInPlace<RTC::SCTP::InitChunk>();\n\n\t\tchunk1->SetInitiateTag(87654321);\n\t\tchunk1->SetAdvertisedReceiverWindowCredit(12345678);\n\t\tchunk1->SetNumberOfOutboundStreams(11100);\n\t\tchunk1->SetNumberOfInboundStreams(22200);\n\t\tchunk1->SetInitialTsn(14141414);\n\n\t\t// Parameter 1.1: IPV4_ADDRESS, length: 8 bytes.\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tauto* parameter1_1 = chunk1->BuildParameterInPlace<RTC::SCTP::IPv4AddressParameter>();\n\n\t\t// 192.168.0.3 IPv4 in network order.\n\t\tuint8_t ipBuffer[] = { 0xC0, 0xA8, 0x00, 0x03 };\n\n\t\tparameter1_1->SetIPv4Address(ipBuffer);\n\t\tparameter1_1->Consolidate();\n\n\t\tREQUIRE(chunk1->GetFirstParameterOfType<RTC::SCTP::IPv4AddressParameter>() == parameter1_1);\n\n\t\t// Parameter 1.2: COOKIE_PRESERVATIVE, length: 8 bytes.\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tauto* parameter1_2 = chunk1->BuildParameterInPlace<RTC::SCTP::CookiePreservativeParameter>();\n\n\t\tparameter1_2->SetLifeSpanIncrement(987654321);\n\t\tparameter1_2->Consolidate();\n\n\t\tREQUIRE(chunk1->GetFirstParameterOfType<RTC::SCTP::CookiePreservativeParameter>() == parameter1_2);\n\n\t\t// Consolidate Chunk 1 after consolidating its Parameters 1.1 and 1.2.\n\t\tchunk1->Consolidate();\n\n\t\tREQUIRE(chunk1->GetFirstParameterOfType<RTC::SCTP::IPv4AddressParameter>() == parameter1_1);\n\t\tREQUIRE(chunk1->GetFirstParameterOfType<RTC::SCTP::CookiePreservativeParameter>() == parameter1_2);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == chunk1);\n\n\t\t// Chunk 2: HEARTBEAT_REQUEST, length: 4 bytes.\n\t\tauto* chunk2 = packet->BuildChunkInPlace<RTC::SCTP::HeartbeatRequestChunk>();\n\n\t\t// Parameter 2.1: HEARTBEAT_INFO, length: 4 bytes.\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tauto* parameter2_1 = chunk2->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\t// Parameter 2.1: Add 3 bytes of info + 1 byte of padding.\n\t\tparameter2_1->SetInfo(sctpCommon::DataBuffer, 3);\n\t\tparameter2_1->Consolidate();\n\n\t\tREQUIRE(chunk2->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>() == parameter2_1);\n\n\t\tstd::memset(sctpCommon::DataBuffer, 0xFF, 3);\n\n\t\t// Consolidate the Chunk after consolidating its Parameters.\n\t\tchunk2->Consolidate();\n\n\t\tREQUIRE(chunk2->GetFirstParameterOfType<RTC::SCTP::HeartbeatInfoParameter>() == parameter2_1);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == chunk2);\n\n\t\t// Insert CRC32C checksum.\n\t\tpacket->WriteCRC32cChecksum();\n\n\t\tauto crc32cChecksum = packet->GetChecksum();\n\n\t\t// Packet length must be:\n\t\t// - Packet header: 12\n\t\t// - Chunk 1: 20\n\t\t// - Parameter 1.1: 8\n\t\t// - Parameter 1.2: 8\n\t\t// - Chunk 2: 4\n\t\t// - Parameter 2.1: 4 + 3 + 1 = 8\n\t\t// - Total: 60\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 60,\n\t\t  /*sourcePort*/ 1000,\n\t\t  /*destinationPort*/ 6000,\n\t\t  /*verificationTag*/ 12345678,\n\t\t  /*checksum*/ crc32cChecksum,\n\t\t  /*hasValidCrc32cChecksum*/ true,\n\t\t  /*chunksCount*/ 2);\n\n\t\t/* Serialize the Packet. */\n\n\t\tpacket->Serialize(sctpCommon::SerializeBuffer, packet->GetLength());\n\n\t\tstd::memset(sctpCommon::FactoryBuffer, 0xAA, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ 60,\n\t\t  /*length*/ 60,\n\t\t  /*sourcePort*/ 1000,\n\t\t  /*destinationPort*/ 6000,\n\t\t  /*verificationTag*/ 12345678,\n\t\t  /*checksum*/ crc32cChecksum,\n\t\t  /*hasValidCrc32cChecksum*/ true,\n\t\t  /*chunksCount*/ 2);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == chunk1);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == chunk2);\n\n\t\t/* Clone the Packet. */\n\n\t\tpacket.reset(packet->Clone(sctpCommon::CloneBuffer, packet->GetLength()));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tconst auto* obtainedChunk1 = reinterpret_cast<const RTC::SCTP::InitChunk*>(packet->GetChunkAt(0));\n\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tconst auto* obtainedParameter1_1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(obtainedChunk1->GetParameterAt(0));\n\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tconst auto* obtainedParameter1_2 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(\n\t\t    obtainedChunk1->GetParameterAt(1));\n\n\t\tconst auto* obtainedChunk2 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatRequestChunk*>(packet->GetChunkAt(1));\n\n\t\t// NOLINTNEXTLINE (readability-identifier-naming)\n\t\tconst auto* obtainedParameter2_1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(obtainedChunk2->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ 60,\n\t\t  /*length*/ 60,\n\t\t  /*sourcePort*/ 1000,\n\t\t  /*destinationPort*/ 6000,\n\t\t  /*verificationTag*/ 12345678,\n\t\t  /*checksum*/ crc32cChecksum,\n\t\t  /*hasValidCrc32cChecksum*/ true,\n\t\t  /*chunksCount*/ 2);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::InitChunk>() == obtainedChunk1);\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::HeartbeatRequestChunk>() == obtainedChunk2);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ obtainedChunk1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20 + 8 + 8,\n\t\t  /*length*/ 20 + 8 + 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(obtainedChunk1->GetInitiateTag() == 87654321);\n\t\tREQUIRE(obtainedChunk1->GetAdvertisedReceiverWindowCredit() == 12345678);\n\t\tREQUIRE(obtainedChunk1->GetNumberOfOutboundStreams() == 11100);\n\t\tREQUIRE(obtainedChunk1->GetNumberOfInboundStreams() == 22200);\n\t\tREQUIRE(obtainedChunk1->GetInitialTsn() == 14141414);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter1_1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter1_1->GetIPv4Address()[0] == 0xC0);\n\t\tREQUIRE(obtainedParameter1_1->GetIPv4Address()[1] == 0xA8);\n\t\tREQUIRE(obtainedParameter1_1->GetIPv4Address()[2] == 0x00);\n\t\tREQUIRE(obtainedParameter1_1->GetIPv4Address()[3] == 0x03);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter1_2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter1_2->GetLifeSpanIncrement() == 987654321);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ obtainedChunk2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 4 + 8,\n\t\t  /*length*/ 4 + 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter2_1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter2_1->HasInfo() == true);\n\t\tREQUIRE(obtainedParameter2_1->GetInfoLength() == 3);\n\t\tREQUIRE(obtainedParameter2_1->GetInfo()[0] == 0x00);\n\t\tREQUIRE(obtainedParameter2_1->GetInfo()[1] == 0x01);\n\t\tREQUIRE(obtainedParameter2_1->GetInfo()[2] == 0x02);\n\t}\n\n\tSECTION(\"Factory() using AddChunk() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, 1000) };\n\n\t\tpacket->SetSourcePort(1);\n\t\tpacket->SetDestinationPort(2);\n\t\tpacket->SetVerificationTag(3);\n\t\tpacket->SetChecksum(4);\n\n\t\t// 4 bytes Chunk.\n\t\tauto* chunk1 = RTC::SCTP::ShutdownCompleteChunk::Factory(sctpCommon::FactoryBuffer + 1000, 1000);\n\n\t\tchunk1->SetT(true);\n\n\t\tpacket->AddChunk(chunk1);\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() != nullptr);\n\t\t// NOTE: The stored Chunk is not the same than the given one since it's\n\t\t// internally cloned.\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() != chunk1);\n\n\t\t// Once added, we can delete the Chunk.\n\t\tdelete chunk1;\n\n\t\t// Packet length must be:\n\t\t// - Packet header: 12\n\t\t// - Chunk 1: 4\n\t\t// - Total: 16\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 1000,\n\t\t  /*length*/ 16,\n\t\t  /*sourcePort*/ 1,\n\t\t  /*destinationPort*/ 2,\n\t\t  /*verificationTag*/ 3,\n\t\t  /*checksum*/ 4,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 1);\n\n\t\tconst auto* obtainedChunk1 =\n\t\t  reinterpret_cast<const RTC::SCTP::ShutdownCompleteChunk*>(packet->GetChunkAt(0));\n\n\t\tREQUIRE(packet->GetFirstChunkOfType<RTC::SCTP::ShutdownCompleteChunk>() == obtainedChunk1);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ obtainedChunk1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(obtainedChunk1->GetT() == true);\n\t}\n\n\tSECTION(\"BuildChunkInPlace() throws if given Chunk exceeds Packet buffer length\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, 28) };\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 28,\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 0,\n\t\t  /*destinationPort*/ 0,\n\t\t  /*verificationTag*/ 0,\n\t\t  /*checksum*/ 0,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\n\t\t// Chunk 1: DATA, length: 16 bytes.\n\t\tauto* chunk1 = packet->BuildChunkInPlace<RTC::SCTP::DataChunk>();\n\n\t\t// Adding user data 10 bytes, must throw.\n\t\tREQUIRE_THROWS_AS(chunk1->SetUserDataPayload(sctpCommon::DataBuffer, 10), MediaSoupError);\n\n\t\tdelete chunk1;\n\n\t\t// Chunk 2: INIT, length: 20 bytes. Must throw.\n\t\tREQUIRE_THROWS_AS(packet->BuildChunkInPlace<RTC::SCTP::InitChunk>(), MediaSoupError);\n\n\t\tCHECK_SCTP_PACKET(\n\t\t  /*packet*/ packet.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 28,\n\t\t  /*length*/ 12,\n\t\t  /*sourcePort*/ 0,\n\t\t  /*destinationPort*/ 0,\n\t\t  /*verificationTag*/ 0,\n\t\t  /*checksum*/ 0,\n\t\t  /*hasValidCrc32cChecksum*/ false,\n\t\t  /*chunksCount*/ 0);\n\t}\n\n\tSECTION(\"BuildChunkInPlace() and AddChunk() throw if the Packet needs consolidation\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, 1000) };\n\n\t\tREQUIRE(packet->NeedsConsolidation() == false);\n\n\t\tconst auto* chunk1 = packet->BuildChunkInPlace<RTC::SCTP::InitChunk>();\n\n\t\tREQUIRE(packet->NeedsConsolidation() == true);\n\n\t\t// We didn't call chunk1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(packet->BuildChunkInPlace<RTC::SCTP::ShutdownCompleteChunk>(), MediaSoupError);\n\n\t\tconst auto* chunk2 = RTC::SCTP::ShutdownCompleteChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// We didn't call chunk1->Consolidate() yet so this must throw.\n\t\tREQUIRE_THROWS_AS(packet->AddChunk(chunk2), MediaSoupError);\n\n\t\tdelete chunk2;\n\n\t\tchunk1->Consolidate();\n\n\t\tREQUIRE(packet->NeedsConsolidation() == false);\n\n\t\t// This shouldn't throw now.\n\t\tconst auto* chunk3 = packet->BuildChunkInPlace<RTC::SCTP::ShutdownCompleteChunk>();\n\n\t\tREQUIRE(packet->NeedsConsolidation() == true);\n\n\t\tchunk3->Consolidate();\n\n\t\tREQUIRE(packet->NeedsConsolidation() == false);\n\n\t\tconst auto* chunk4 = RTC::SCTP::ShutdownCompleteChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// This shouldn't throw now.\n\t\tpacket->AddChunk(chunk4);\n\n\t\tREQUIRE(packet->NeedsConsolidation() == false);\n\n\t\tdelete chunk4;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/TestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP Parameter\", \"[serializable][sctp][parameter]\")\n{\n\tSECTION(\"alignof() SCTP structs\")\n\t{\n\t\tREQUIRE(alignof(RTC::SCTP::Parameter::ParameterHeader) == 2);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestAbortAssociationChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Abort Association Chunk (6)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"AbortAssociationChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:6 (ABORT), Flags:0b00000000, Length: 12\n\t\t\t0x06, 0b00000001, 0x00, 0x0C,\n\t\t\t// Error Cause 3: Code:1 (STALE_COOKIE), Length: 8\n\t\t\t0x00, 0x03, 0x00, 0x08,\n\t\t\t// Measure of Staleness: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::AbortAssociationChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\tconst auto* errorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(chunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\terrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(chunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\terrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(clonedChunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"AbortAssociationChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::AbortAssociationChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetT() == false);\n\n\t\t/* Modify it and add Error Causes. */\n\n\t\tchunk->SetT(true);\n\n\t\tauto* errorCause1 = chunk->BuildErrorCauseInPlace<RTC::SCTP::StaleCookieErrorCause>();\n\n\t\terrorCause1->SetMeasureOfStaleness(666);\n\t\terrorCause1->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4 + (4 + 4),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\tconst auto* addedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(chunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ addedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause1->GetMeasureOfStaleness() == 666);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk =\n\t\t  RTC::SCTP::AbortAssociationChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + (4 + 4),\n\t\t  /*length*/ 4 + (4 + 4),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\tREQUIRE(parsedChunk->GetT() == true);\n\n\t\tconst auto* parsedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(parsedChunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause1->GetMeasureOfStaleness() == 666);\n\n\t\tdelete parsedChunk;\n\t}\n\n\tSECTION(\"AbortAssociationChunk::Factory() with AddErrorCause() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::AbortAssociationChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tchunk->SetT(true);\n\n\t\t// 8 bytes Error Cause.\n\t\tauto* errorCause1 = RTC::SCTP::StaleCookieErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\terrorCause1->SetMeasureOfStaleness(666666);\n\n\t\tchunk->AddErrorCause(errorCause1);\n\n\t\t// Once added, we can delete the Error Cause.\n\t\tdelete errorCause1;\n\n\t\t// Chunk length must be:\n\t\t// - Chunk header: 4\n\t\t// - Error Cause 1: 8\n\t\t// - Total: 12\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\tconst auto* obtainedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(chunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ obtainedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(obtainedErrorCause1->GetMeasureOfStaleness() == 666666);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk =\n\t\t  RTC::SCTP::AbortAssociationChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 1);\n\n\t\tREQUIRE(parsedChunk->GetT() == true);\n\n\t\tobtainedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::StaleCookieErrorCause*>(parsedChunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ obtainedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(obtainedErrorCause1->GetMeasureOfStaleness() == 666666);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestCookieAckChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieAckChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Cookie Acknowledgement Chunk (11)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"CookieAckChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:11 (COOKIE_ACK), Flags:0x00000001, T: 1, Length: 4\n\t\t\t0x0B, 0b00000101, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::CookieAckChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000101,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000101,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000101,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"RTC::SCTP::CookieAckChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::CookieAckChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::CookieAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestCookieEchoChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/CookieEchoChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Cookie Echo Chunk (10)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"CookieEchoChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:A (COOKIE_ECHO), Flags: 0b00000000, Length: 9\n\t\t\t0x0A, 0b00000000, 0x00, 0x09,\n\t\t\t// Cookie: 0x1122334455,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 3 bytes of padding\n\t\t\t0x55, 0x00, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::CookieEchoChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasCookie() == true);\n\t\tREQUIRE(chunk->GetCookieLength() == 5);\n\t\tREQUIRE(chunk->GetCookie()[0] == 0x11);\n\t\tREQUIRE(chunk->GetCookie()[1] == 0x22);\n\t\tREQUIRE(chunk->GetCookie()[2] == 0x33);\n\t\tREQUIRE(chunk->GetCookie()[3] == 0x44);\n\t\tREQUIRE(chunk->GetCookie()[4] == 0x55);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetCookie()[5] == 0x00);\n\t\tREQUIRE(chunk->GetCookie()[6] == 0x00);\n\t\tREQUIRE(chunk->GetCookie()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasCookie() == true);\n\t\tREQUIRE(chunk->GetCookieLength() == 5);\n\t\tREQUIRE(chunk->GetCookie()[0] == 0x11);\n\t\tREQUIRE(chunk->GetCookie()[1] == 0x22);\n\t\tREQUIRE(chunk->GetCookie()[2] == 0x33);\n\t\tREQUIRE(chunk->GetCookie()[3] == 0x44);\n\t\tREQUIRE(chunk->GetCookie()[4] == 0x55);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetCookie()[5] == 0x00);\n\t\tREQUIRE(chunk->GetCookie()[6] == 0x00);\n\t\tREQUIRE(chunk->GetCookie()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->HasCookie() == true);\n\t\tREQUIRE(clonedChunk->GetCookieLength() == 5);\n\t\tREQUIRE(clonedChunk->GetCookie()[0] == 0x11);\n\t\tREQUIRE(clonedChunk->GetCookie()[1] == 0x22);\n\t\tREQUIRE(clonedChunk->GetCookie()[2] == 0x33);\n\t\tREQUIRE(clonedChunk->GetCookie()[3] == 0x44);\n\t\tREQUIRE(clonedChunk->GetCookie()[4] == 0x55);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedChunk->GetCookie()[5] == 0x00);\n\t\tREQUIRE(clonedChunk->GetCookie()[6] == 0x00);\n\t\tREQUIRE(clonedChunk->GetCookie()[7] == 0x00);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"CookieEchoChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::CookieEchoChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasCookie() == false);\n\t\tREQUIRE(chunk->GetCookieLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\tchunk->SetCookie(sctpCommon::DataBuffer + 1000, 2999);\n\n\t\tREQUIRE(chunk->GetLength() == 3004);\n\t\tREQUIRE(chunk->HasCookie() == true);\n\t\tREQUIRE(chunk->GetCookieLength() == 2999);\n\n\t\tchunk->SetCookie(nullptr, 0);\n\n\t\tREQUIRE(chunk->GetLength() == 4);\n\t\tREQUIRE(chunk->HasCookie() == false);\n\t\tREQUIRE(chunk->GetCookieLength() == 0);\n\n\t\t// 3 bytes + 1 byte of padding.\n\t\tchunk->SetCookie(sctpCommon::DataBuffer, 3);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasCookie() == true);\n\t\tREQUIRE(chunk->GetCookieLength() == 3);\n\t\tREQUIRE(chunk->GetCookie()[0] == 0x00);\n\t\tREQUIRE(chunk->GetCookie()[1] == 0x01);\n\t\tREQUIRE(chunk->GetCookie()[2] == 0x02);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetCookie()[3] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::CookieEchoChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->HasCookie() == true);\n\t\tREQUIRE(parsedChunk->GetCookieLength() == 3);\n\t\tREQUIRE(parsedChunk->GetCookie()[0] == 0x00);\n\t\tREQUIRE(parsedChunk->GetCookie()[1] == 0x01);\n\t\tREQUIRE(parsedChunk->GetCookie()[2] == 0x02);\n\t\t// This should be padding.\n\t\tREQUIRE(parsedChunk->GetCookie()[3] == 0x00);\n\n\t\tdelete parsedChunk;\n\t}\n\n\tSECTION(\"CookieEchoChunk::SetCookie() throws if userDataLength is too big\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::CookieEchoChunk::Factory(sctpCommon::ThrowBuffer, sizeof(sctpCommon::ThrowBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::ThrowBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE_THROWS_AS(chunk->SetCookie(sctpCommon::ThrowBuffer, 65535), MediaSoupError);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::ThrowBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete chunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestDataChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"SCTP Payload Data Chunk (0)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"DataChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:0 (DATA), I:1, U:0, B:1, E:1, Length: 19\n\t\t\t0x00, 0b00001011, 0x00, 0x13,\n\t\t\t// TSN: 0x11223344,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream Identifier S: 0xFF00, Stream Sequence Number n: 0x6677\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Payload Protocol Identifier: 0x12341234\n\t\t\t0x12, 0x34, 0x12, 0x34,\n\t\t\t// User Data (3 bytes): 0xABCDEF, 1 byte of padding\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::DataChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == true);\n\t\tREQUIRE(chunk->GetE() == true);\n\t\tREQUIRE(chunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk->GetStreamId() == 0xFF00);\n\t\tREQUIRE(chunk->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tauto userData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = { 0xAB, 0xCD, 0xEF };\n\n\t\tREQUIRE(userData.GetStreamId() == 0xFF00);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0x12341234);\n\t\t// NOTE: clang-tidy doesn't understand that this is fine.\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == true);\n\t\tREQUIRE(chunk->GetE() == true);\n\t\tREQUIRE(chunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk->GetStreamId() == 0xFF00);\n\t\tREQUIRE(chunk->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = chunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 0xFF00);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0x12341234);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001011,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetI() == true);\n\t\tREQUIRE(clonedChunk->GetU() == false);\n\t\tREQUIRE(clonedChunk->GetB() == true);\n\t\tREQUIRE(clonedChunk->GetE() == true);\n\t\tREQUIRE(clonedChunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(clonedChunk->GetStreamId() == 0xFF00);\n\t\tREQUIRE(clonedChunk->GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(clonedChunk->GetPayloadProtocolId() == 0x12341234);\n\t\tREQUIRE(clonedChunk->HasUserDataPayload() == true);\n\t\tREQUIRE(clonedChunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(clonedChunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(clonedChunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(clonedChunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedChunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = clonedChunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 0xFF00);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0x6677);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0x12341234);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"DataChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == false);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == false);\n\t\tREQUIRE(chunk->GetE() == false);\n\t\tREQUIRE(chunk->GetTsn() == 0);\n\t\tREQUIRE(chunk->GetStreamId() == 0);\n\t\tREQUIRE(chunk->GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0);\n\t\tREQUIRE(chunk->HasUserDataPayload() == false);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 0);\n\n\t\tauto userData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = {};\n\n\t\tREQUIRE(userData.GetStreamId() == 0);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetI(true);\n\t\tchunk->SetE(true);\n\t\tchunk->SetTsn(12345678);\n\t\tchunk->SetStreamId(9988);\n\t\tchunk->SetStreamSequenceNumber(2211);\n\t\tchunk->SetPayloadProtocolId(987654321);\n\n\t\t// Verify that replacing the value works.\n\t\tchunk->SetUserDataPayload(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(chunk->GetLength() == 3016);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3000);\n\n\t\tchunk->SetUserDataPayload(nullptr, 0);\n\n\t\tREQUIRE(chunk->GetLength() == 16);\n\t\tREQUIRE(chunk->HasUserDataPayload() == false);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 0);\n\n\t\t// 3 bytes + 1 byte of padding.\n\t\tchunk->SetUserDataPayload(sctpCommon::DataBuffer, 3);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16 + 3 + 1,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == false);\n\t\tREQUIRE(chunk->GetE() == true);\n\t\tREQUIRE(chunk->GetTsn() == 12345678);\n\t\tREQUIRE(chunk->GetStreamId() == 9988);\n\t\tREQUIRE(chunk->GetStreamSequenceNumber() == 2211);\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 987654321);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0x00);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0x01);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0x02);\n\t\t// Last byte must be a zero byte padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData        = chunk->MakeUserData();\n\t\texpectedPayload = { 0x00, 0x01, 0x02 };\n\n\t\tREQUIRE(userData.GetStreamId() == 9988);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 2211);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 987654321);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::DataChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 16 + 3 + 1,\n\t\t  /*length*/ 16 + 3 + 1,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00001001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetI() == true);\n\t\tREQUIRE(parsedChunk->GetU() == false);\n\t\tREQUIRE(parsedChunk->GetB() == false);\n\t\tREQUIRE(parsedChunk->GetE() == true);\n\t\tREQUIRE(parsedChunk->GetTsn() == 12345678);\n\t\tREQUIRE(parsedChunk->GetStreamId() == 9988);\n\t\tREQUIRE(parsedChunk->GetStreamSequenceNumber() == 2211);\n\t\tREQUIRE(parsedChunk->GetPayloadProtocolId() == 987654321);\n\t\tREQUIRE(parsedChunk->HasUserDataPayload() == true);\n\t\tREQUIRE(parsedChunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(parsedChunk->GetUserDataPayload()[0] == 0x00);\n\t\tREQUIRE(parsedChunk->GetUserDataPayload()[1] == 0x01);\n\t\tREQUIRE(parsedChunk->GetUserDataPayload()[2] == 0x02);\n\t\t// Last byte must be a zero byte padding.\n\t\tREQUIRE(parsedChunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = parsedChunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 9988);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 2211);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 987654321);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\tdelete parsedChunk;\n\t}\n\n\tSECTION(\"DataChunk::SetUserDataPayload() throws if userDataLength is too big\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE_THROWS_AS(chunk->SetUserDataPayload(sctpCommon::DataBuffer, 65535), MediaSoupError);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete chunk;\n\t}\n\n\tSECTION(\"DataChunk::SetUserData() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tRTC::SCTP::UserData userData(\n\t\t  /*streamId*/ 123,\n\t\t  /*ssn*/ 4444,\n\t\t  /*mid*/ 0, // Not in DATA chunks.\n\t\t  /*fsn*/ 0, // Not in DATA chunks.\n\t\t  /*ppid*/ 56789,\n\t\t  /*payload*/ { 1, 2, 3, 4 },\n\t\t  /*isBeginning*/ true,\n\t\t  /*isEnd*/ true,\n\t\t  /*isUnordered*/ true);\n\n\t\tREQUIRE(userData.GetStreamId() == 123);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 4444);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 56789);\n\t\tREQUIRE(userData.GetPayloadLength() == 4);\n\t\tREQUIRE(userData.GetPayload()[0] == 1);\n\t\tREQUIRE(userData.GetPayload()[1] == 2);\n\t\tREQUIRE(userData.GetPayload()[2] == 3);\n\t\tREQUIRE(userData.GetPayload()[3] == 4);\n\t\tREQUIRE(userData.IsBeginning() == true);\n\t\tREQUIRE(userData.IsEnd() == true);\n\t\tREQUIRE(userData.IsUnordered() == true);\n\n\t\tchunk->SetUserData(std::move(userData));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16 + 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000111,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tauto gotUserData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = { 1, 2, 3, 4 };\n\n\t\tREQUIRE(gotUserData.GetStreamId() == 123);\n\t\tREQUIRE(gotUserData.GetStreamSequenceNumber() == 4444);\n\t\tREQUIRE(gotUserData.GetMessageId() == 0);\n\t\tREQUIRE(gotUserData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(gotUserData.GetPayloadProtocolId() == 56789);\n\t\tREQUIRE(gotUserData.GetPayloadLength() == 4);\n\t\tREQUIRE(gotUserData.GetPayload()[0] == 1);\n\t\tREQUIRE(gotUserData.GetPayload()[1] == 2);\n\t\tREQUIRE(gotUserData.GetPayload()[2] == 3);\n\t\tREQUIRE(gotUserData.GetPayload()[3] == 4);\n\t\tREQUIRE(gotUserData.IsBeginning() == true);\n\t\tREQUIRE(gotUserData.IsEnd() == true);\n\t\tREQUIRE(gotUserData.IsUnordered() == true);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(gotUserData).ReleasePayload() == expectedPayload);\n\n\t\tdelete chunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestForwardTsnChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"Forward Cumulative TSN Chunk (192)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ForwardTsnChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:192 (FORWARD_TSN), Flags: 0b00000000, Length: 16\n\t\t\t0xC0, 0b00000000, 0x00, 0x10,\n\t\t\t// New Cumulative TSN: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream 1: 4660, Stream Sequence 1: 17185\n\t\t\t0x12, 0x34, 0x43, 0x21,\n\t\t\t// Stream 2: 22136, Stream Sequence 2: 34661\n\t\t\t0x56, 0x78, 0x87, 0x65,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::ForwardTsnChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 2);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  { 4660,  17185 },\n                                      { 22136, 34661 }\n    });\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 2);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  { 4660,  17185 },\n                                      { 22136, 34661 }\n    });\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(clonedChunk->GetNumberOfSkippedStreams() == 2);\n\t\tREQUIRE(\n\t\t  clonedChunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                        { 4660,  17185 },\n                                            { 22136, 34661 }\n    });\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"ForwardTsnChunk::Parse() fails\")\n\t{\n\t\t// Length field is not even.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:192 (FORWARD_TSN), Flags: 0b00000000, Length: 14 (should be 16)\n\t\t\t0xC0, 0b00000000, 0x00, 0x0E,\n\t\t\t// New Cumulative TSN: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream 1: 4660, Stream Sequence 1: 17185\n\t\t\t0x12, 0x34, 0x43, 0x21,\n\t\t\t// Stream 2: 22136, Stream Sequence 2 (missing in Length field)\n\t\t\t0x56, 0x78, 0x87, 0x65,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::ForwardTsnChunk::Parse(buffer1, sizeof(buffer1)));\n\t}\n\n\tSECTION(\"ForwardTsnChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::ForwardTsnChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 0);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 0);\n\t\tREQUIRE(chunk->GetSkippedStreams().empty());\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetNewCumulativeTsn(1234);\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 1111, /*ssn*/ 11110 });\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 2222, /*ssn*/ 22220 });\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 3333, /*ssn*/ 33330 });\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 1234);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  { 1111, 11110 },\n\t\t                                  { 2222, 22220 },\n\t\t                                  { 3333, 33330 },\n    });\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::ForwardTsnChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetNewCumulativeTsn() == 1234);\n\t\tREQUIRE(parsedChunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  parsedChunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                        { 1111, 11110 },\n\t\t                                        { 2222, 22220 },\n\t\t                                        { 3333, 33330 },\n    });\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestHeartbeatAckChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnknownParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Hearbeat Acknowledgement Chunk (5)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"HeartbeatAckChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type: 5 (HEARTBEAT_ACK), Flags:0b00000000, Length: 22\n\t\t\t// NOTE: Chunk Length field must exclude padding of the last Parameter.\n\t\t\t0x05, 0b00000000, 0x00, 0x16,\n\t\t\t// Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11\n\t\t\t0x00, 0x01, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t\t// Parameter 2: Type:49159 (UNKNOWN), Length: 6\n\t\t\t0xC0, 0x07, 0x00, 0x06,\n\t\t\t// Unknown data: 0xABCD, 2 bytes of padding\n\t\t\t0xAB, 0xCD, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::HeartbeatAckChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tconst auto* parameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnknownParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 = reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::UnknownParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tconst auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(clonedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::UnknownParameter*>(clonedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"HeartbeatAckChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::HeartbeatAckChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Modify it by adding Parameters. */\n\n\t\tauto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\t// Info length is 5 so 3 bytes of padding will be added.\n\t\tparameter1->SetInfo(sctpCommon::DataBuffer, 5);\n\t\tparameter1->Consolidate();\n\n\t\t// Let's add another RTC::SCTP::HeartbeatInfoParameter (it doesn't make sense but\n\t\t// anyway).\n\t\tauto* parameter2 = chunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\t// Info length is 2 so 2 bytes of padding will be added.\n\t\tparameter2->SetInfo(sctpCommon::DataBuffer, 2);\n\t\tparameter2->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* addedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter1->HasInfo() == true);\n\t\tREQUIRE(addedParameter1->GetInfoLength() == 5);\n\t\tREQUIRE(addedParameter1->GetInfo()[0] == 0x00);\n\t\tREQUIRE(addedParameter1->GetInfo()[1] == 0x01);\n\t\tREQUIRE(addedParameter1->GetInfo()[2] == 0x02);\n\t\tREQUIRE(addedParameter1->GetInfo()[3] == 0x03);\n\t\tREQUIRE(addedParameter1->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(addedParameter1->GetInfo()[5] == 0x00);\n\t\tREQUIRE(addedParameter1->GetInfo()[6] == 0x00);\n\n\t\tconst auto* addedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter2->HasInfo() == true);\n\t\tREQUIRE(addedParameter2->GetInfoLength() == 2);\n\t\tREQUIRE(addedParameter2->GetInfo()[0] == 0x00);\n\t\tREQUIRE(addedParameter2->GetInfo()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(addedParameter2->GetInfo()[2] == 0x00);\n\t\tREQUIRE(addedParameter2->GetInfo()[3] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tconst auto* parsedChunk =\n\t\t  RTC::SCTP::HeartbeatAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parsedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(parsedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter1->HasInfo() == true);\n\t\tREQUIRE(parsedParameter1->GetInfoLength() == 5);\n\t\tREQUIRE(parsedParameter1->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parsedParameter1->GetInfo()[1] == 0x01);\n\t\tREQUIRE(parsedParameter1->GetInfo()[2] == 0x02);\n\t\tREQUIRE(parsedParameter1->GetInfo()[3] == 0x03);\n\t\tREQUIRE(parsedParameter1->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter1->GetInfo()[5] == 0x00);\n\t\tREQUIRE(parsedParameter1->GetInfo()[6] == 0x00);\n\n\t\tconst auto* parsedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(parsedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter2->HasInfo() == true);\n\t\tREQUIRE(parsedParameter2->GetInfoLength() == 2);\n\t\tREQUIRE(parsedParameter2->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parsedParameter2->GetInfo()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter2->GetInfo()[2] == 0x00);\n\t\tREQUIRE(parsedParameter2->GetInfo()[3] == 0x00);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestHeartbeatRequestChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnknownParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Hearbeat Request Chunk (4)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"HeartbeatRequestChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:4 (HEARTBEAT_REQUEST), Flags:0b00000000, Length: 22\n\t\t\t// NOTE: Length field must exclude the padding of the last Parameter.\n\t\t\t0x04, 0b00000000, 0x00, 0x16,\n\t\t\t// Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11\n\t\t\t0x00, 0x01, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t\t// Parameter 2: Type:49159 (UNKNOWN), Length: 6\n\t\t\t0xC0, 0x07, 0x00, 0x06,\n\t\t\t// Unknown data: 0xABCD, 2 bytes of padding\n\t\t\t0xAB, 0xCD, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::HeartbeatRequestChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tconst auto* parameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnknownParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->HasUnknownValue() == true);\n\t\tREQUIRE(parameter2->GetUnknownValueLength() == 2);\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 = reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::UnknownParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->HasUnknownValue() == true);\n\t\tREQUIRE(parameter2->GetUnknownValueLength() == 2);\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(clonedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::UnknownParameter*>(clonedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->HasUnknownValue() == true);\n\t\tREQUIRE(parameter2->GetUnknownValueLength() == 2);\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"HeartbeatRequestChunk::Parse() with incorrect but valid Chunk Length field succeeds\")\n\t{\n\t\t// Here the chunk has incorrect Chunk Length field with value 24 instead of\n\t\t// 22. It's incorrect because, as per RFC 9260:\n\t\t//\n\t\t// > The Chunk Length field does not count any chunk padding. However, it\n\t\t// > does include any padding of variable-length parameters other than the\n\t\t// > last parameter in the chunk. A robust implementation is expected to\n\t\t// > accept the chunk whether or not the final padding has been included in\n\t\t// > the Chunk Length.\n\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:4 (HEARTBEAT_REQUEST), Flags:0b00000000, Length: 24\n\t\t\t// NOTE: Length field must exclude the padding of the last Parameter so\n\t\t\t// Length field should be 22 rather than 24. But anyway it's ok.\n\t\t\t0x04, 0b00000000, 0x00, 0x18,\n\t\t\t// Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11\n\t\t\t0x00, 0x01, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t\t// Parameter 2: Type:49159 (UNKNOWN), Length: 6\n\t\t\t0xC0, 0x07, 0x00, 0x06,\n\t\t\t// Unknown data: 0xABCD, 2 bytes of padding\n\t\t\t0xAB, 0xCD, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::HeartbeatRequestChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tconst auto* parameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnknownParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->HasUnknownValue() == true);\n\t\tREQUIRE(parameter2->GetUnknownValueLength() == 2);\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(clonedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->HasInfo() == true);\n\t\tREQUIRE(parameter1->GetInfoLength() == 7);\n\t\tREQUIRE(parameter1->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter1->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter1->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter1->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter1->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter1->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter1->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter1->GetInfo()[7] == 0x00);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::UnknownParameter*>(clonedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter2->HasUnknownValue() == true);\n\t\tREQUIRE(parameter2->GetUnknownValueLength() == 2);\n\t\tREQUIRE(parameter2->GetUnknownValue()[0] == 0xAB);\n\t\tREQUIRE(parameter2->GetUnknownValue()[1] == 0xCD);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter2->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(parameter2->GetUnknownValue()[3] == 0x00);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"HeartbeatRequestChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::HeartbeatRequestChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Modify it by adding Parameters. */\n\n\t\tauto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\t// Info length is 5 so 3 bytes of padding will be added.\n\t\tparameter1->SetInfo(sctpCommon::DataBuffer, 5);\n\t\tparameter1->Consolidate();\n\n\t\t// Let's add another HeartbeatInfoParameter (it doesn't make sense but\n\t\t// anyway).\n\t\tauto* parameter2 = chunk->BuildParameterInPlace<RTC::SCTP::HeartbeatInfoParameter>();\n\n\t\t// Info length is 2 so 2 bytes of padding will be added.\n\t\tparameter2->SetInfo(sctpCommon::DataBuffer, 2);\n\t\tparameter2->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* addedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter1->HasInfo() == true);\n\t\tREQUIRE(addedParameter1->GetInfoLength() == 5);\n\t\tREQUIRE(addedParameter1->GetInfo()[0] == 0x00);\n\t\tREQUIRE(addedParameter1->GetInfo()[1] == 0x01);\n\t\tREQUIRE(addedParameter1->GetInfo()[2] == 0x02);\n\t\tREQUIRE(addedParameter1->GetInfo()[3] == 0x03);\n\t\tREQUIRE(addedParameter1->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(addedParameter1->GetInfo()[5] == 0x00);\n\t\tREQUIRE(addedParameter1->GetInfo()[6] == 0x00);\n\n\t\tconst auto* addedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter2->HasInfo() == true);\n\t\tREQUIRE(addedParameter2->GetInfoLength() == 2);\n\t\tREQUIRE(addedParameter2->GetInfo()[0] == 0x00);\n\t\tREQUIRE(addedParameter2->GetInfo()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(addedParameter2->GetInfo()[2] == 0x00);\n\t\tREQUIRE(addedParameter2->GetInfo()[3] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk =\n\t\t  RTC::SCTP::HeartbeatRequestChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parsedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(parsedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter1->HasInfo() == true);\n\t\tREQUIRE(parsedParameter1->GetInfoLength() == 5);\n\t\tREQUIRE(parsedParameter1->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parsedParameter1->GetInfo()[1] == 0x01);\n\t\tREQUIRE(parsedParameter1->GetInfo()[2] == 0x02);\n\t\tREQUIRE(parsedParameter1->GetInfo()[3] == 0x03);\n\t\tREQUIRE(parsedParameter1->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter1->GetInfo()[5] == 0x00);\n\t\tREQUIRE(parsedParameter1->GetInfo()[6] == 0x00);\n\n\t\tconst auto* parsedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::HeartbeatInfoParameter*>(parsedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter2->HasInfo() == true);\n\t\tREQUIRE(parsedParameter2->GetInfoLength() == 2);\n\t\tREQUIRE(parsedParameter2->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parsedParameter2->GetInfo()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter2->GetInfo()[2] == 0x00);\n\t\tREQUIRE(parsedParameter2->GetInfo()[3] == 0x00);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestIDataChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IDataChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"SCTP I-Data Chunk (64)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"IDataChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:64 (I_DATA), I:1, U:0, B:1, E:0, Length: 23\n\t\t\t0x40, 0b00001010, 0x00, 0x17,\n\t\t\t// TSN: 0x11223344,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream Identifier: 5001\n\t\t\t0x13, 0x89, 0x00, 0x00,\n\t\t\t// Message Identifier: 1234567890\n\t\t\t0x49, 0x96, 0x02, 0xD2,\n\t\t\t// Payload Protocol Identifier / Fragment Sequence Number: 99887766 (PPID)\n\t\t\t0x05, 0xF4, 0x2A, 0x96,\n\t\t\t// User Data (3 bytes): 0xABCDED, 1 byte of padding\n\t\t\t0xAB, 0xCD, 0xEF, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tstd::unique_ptr<RTC::SCTP::IDataChunk> chunk{ RTC::SCTP::IDataChunk::Parse(\n\t\t\tbuffer, sizeof(buffer)) };\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00001010,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == true);\n\t\tREQUIRE(chunk->GetE() == false);\n\t\tREQUIRE(chunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk->GetStreamId() == 5001);\n\t\tREQUIRE(chunk->GetMessageId() == 1234567890);\n\t\t// Bit B is set so we have PPID instead of FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 99887766);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tauto userData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = { 0xAB, 0xCD, 0xEF };\n\n\t\tREQUIRE(userData.GetStreamId() == 5001);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 1234567890);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 99887766);\n\t\t// NOTE: clang-tidy doesn't understand that this is fine.\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t// Bit B is not set so cannot set FSN.\n\t\tREQUIRE_THROWS_AS(chunk->SetFragmentSequenceNumber(1234), MediaSoupError);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00001010,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == true);\n\t\tREQUIRE(chunk->GetE() == false);\n\t\tREQUIRE(chunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk->GetStreamId() == 5001);\n\t\tREQUIRE(chunk->GetMessageId() == 1234567890);\n\t\t// Bit B is set so we have PPID instead of FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 99887766);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = chunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 5001);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 1234567890);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 99887766);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Clone it. */\n\n\t\tchunk.reset(chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00001010,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == true);\n\t\tREQUIRE(chunk->GetE() == false);\n\t\tREQUIRE(chunk->GetTsn() == 0x11223344);\n\t\tREQUIRE(chunk->GetStreamId() == 5001);\n\t\tREQUIRE(chunk->GetMessageId() == 1234567890);\n\t\t// Bit B is set so we have PPID instead of FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 99887766);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0xAB);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0xCD);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0xEF);\n\t\t// This should be padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = chunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 5001);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 1234567890);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 99887766);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\t}\n\n\tSECTION(\"IDataChunk::Factory() succeeds\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::IDataChunk> chunk{ RTC::SCTP::IDataChunk::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == false);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == false);\n\t\tREQUIRE(chunk->GetE() == false);\n\t\tREQUIRE(chunk->GetTsn() == 0);\n\t\tREQUIRE(chunk->GetStreamId() == 0);\n\t\tREQUIRE(chunk->GetMessageId() == 0);\n\t\t// Bit B is not set so we don't have PPID but FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(chunk->HasUserDataPayload() == false);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 0);\n\n\t\tauto userData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = {};\n\n\t\tREQUIRE(userData.GetStreamId() == 0);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 0);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetI(true);\n\t\tchunk->SetE(true);\n\t\tchunk->SetTsn(12345678);\n\t\tchunk->SetStreamId(9988);\n\t\tchunk->SetMessageId(1234);\n\t\tchunk->SetFragmentSequenceNumber(987654321);\n\n\t\t// Bit B is not set so cannot set PPID.\n\t\tREQUIRE_THROWS_AS(chunk->SetPayloadProtocolId(1234), MediaSoupError);\n\n\t\t// Verify that replacing the value works.\n\t\tchunk->SetUserDataPayload(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(chunk->GetLength() == 3020);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3000);\n\n\t\tchunk->SetUserDataPayload(nullptr, 0);\n\n\t\tREQUIRE(chunk->GetLength() == 20);\n\t\tREQUIRE(chunk->HasUserDataPayload() == false);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 0);\n\n\t\t// 3 bytes + 1 byte of padding.\n\t\tchunk->SetUserDataPayload(sctpCommon::DataBuffer, 3);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20 + 3 + 1,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00001001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == false);\n\t\tREQUIRE(chunk->GetE() == true);\n\t\tREQUIRE(chunk->GetTsn() == 12345678);\n\t\tREQUIRE(chunk->GetStreamId() == 9988);\n\t\tREQUIRE(chunk->GetMessageId() == 1234);\n\t\t// Bit B is not set so we don't have PPID but FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 987654321);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0x00);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0x01);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0x02);\n\t\t// Last byte must be a zero byte padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = chunk->MakeUserData();\n\n\t\texpectedPayload = { 0x00, 0x01, 0x02 };\n\n\t\tREQUIRE(userData.GetStreamId() == 9988);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 1234);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 987654321);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\n\t\t/* Parse itself and compare. */\n\n\t\tchunk.reset(RTC::SCTP::IDataChunk::Parse(chunk->GetBuffer(), chunk->GetLength()));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 20 + 3 + 1,\n\t\t  /*length*/ 20 + 3 + 1,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00001001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetI() == true);\n\t\tREQUIRE(chunk->GetU() == false);\n\t\tREQUIRE(chunk->GetB() == false);\n\t\tREQUIRE(chunk->GetE() == true);\n\t\tREQUIRE(chunk->GetTsn() == 12345678);\n\t\tREQUIRE(chunk->GetStreamId() == 9988);\n\t\tREQUIRE(chunk->GetMessageId() == 1234);\n\t\t// Bit B is not set so we don't have PPID but FSN.\n\t\tREQUIRE(chunk->GetPayloadProtocolId() == 0);\n\t\tREQUIRE(chunk->GetFragmentSequenceNumber() == 987654321);\n\t\tREQUIRE(chunk->HasUserDataPayload() == true);\n\t\tREQUIRE(chunk->GetUserDataPayloadLength() == 3);\n\t\tREQUIRE(chunk->GetUserDataPayload()[0] == 0x00);\n\t\tREQUIRE(chunk->GetUserDataPayload()[1] == 0x01);\n\t\tREQUIRE(chunk->GetUserDataPayload()[2] == 0x02);\n\t\t// Last byte must be a zero byte padding.\n\t\tREQUIRE(chunk->GetUserDataPayload()[3] == 0x00);\n\n\t\tuserData = chunk->MakeUserData();\n\n\t\tREQUIRE(userData.GetStreamId() == 9988);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 1234);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 987654321);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 0);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(userData).ReleasePayload() == expectedPayload);\n\t}\n\n\tSECTION(\"IDataChunk::SetUserDataPayload() throws if userDataPayloadLength is too big\")\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::IDataChunk> chunk{ RTC::SCTP::IDataChunk::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE_THROWS_AS(chunk->SetUserDataPayload(sctpCommon::DataBuffer, 65535), MediaSoupError);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk.get(),\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\t}\n\n\tSECTION(\"IDataChunk::SetUserData() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::IDataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tRTC::SCTP::UserData userData(\n\t\t  /*streamId*/ 123,\n\t\t  /*ssn*/ 0, // Not in I-DATA chunks.\n\t\t  /*mid*/ 5555,\n\t\t  /*fsn*/ 6666,\n\t\t  /*ppid*/ 56789,\n\t\t  /*payload*/ { 1, 2, 3, 4 },\n\t\t  /*isBeginning*/ true,\n\t\t  /*isEnd*/ true,\n\t\t  /*isUnordered*/ true);\n\n\t\tREQUIRE(userData.GetStreamId() == 123);\n\t\tREQUIRE(userData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(userData.GetMessageId() == 5555);\n\t\tREQUIRE(userData.GetFragmentSequenceNumber() == 6666);\n\t\tREQUIRE(userData.GetPayloadProtocolId() == 56789);\n\t\tREQUIRE(userData.GetPayloadLength() == 4);\n\t\tREQUIRE(userData.GetPayload()[0] == 1);\n\t\tREQUIRE(userData.GetPayload()[1] == 2);\n\t\tREQUIRE(userData.GetPayload()[2] == 3);\n\t\tREQUIRE(userData.GetPayload()[3] == 4);\n\t\tREQUIRE(userData.IsBeginning() == true);\n\t\tREQUIRE(userData.IsEnd() == true);\n\t\tREQUIRE(userData.IsUnordered() == true);\n\n\t\tchunk->SetUserData(std::move(userData));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20 + 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT,\n\t\t  /*flags*/ 0b00000111,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tauto gotUserData = chunk->MakeUserData();\n\n\t\tstd::vector<uint8_t> expectedPayload = { 1, 2, 3, 4 };\n\n\t\tREQUIRE(gotUserData.GetStreamId() == 123);\n\t\tREQUIRE(gotUserData.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(gotUserData.GetMessageId() == 5555);\n\t\t// Bit B is set in the I_DATA Chunk so this must be 0.\n\t\tREQUIRE(gotUserData.GetFragmentSequenceNumber() == 0);\n\t\tREQUIRE(gotUserData.GetPayloadProtocolId() == 56789);\n\t\tREQUIRE(gotUserData.GetPayloadLength() == 4);\n\t\tREQUIRE(gotUserData.GetPayload()[0] == 1);\n\t\tREQUIRE(gotUserData.GetPayload()[1] == 2);\n\t\tREQUIRE(gotUserData.GetPayload()[2] == 3);\n\t\tREQUIRE(gotUserData.GetPayload()[3] == 4);\n\t\tREQUIRE(gotUserData.IsBeginning() == true);\n\t\tREQUIRE(gotUserData.IsEnd() == true);\n\t\tREQUIRE(gotUserData.IsUnordered() == true);\n\t\t// NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(std::move(gotUserData).ReleasePayload() == expectedPayload);\n\n\t\tdelete chunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestIForwardTsnChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"I-Forward Cumulative TSN Chunk (194)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"IForwardTsnChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:194 (I_FORWARD_TSN), Flags: 0b00000000, Length: 32\n\t\t\t0xC2, 0b00000000, 0x00, 0x20,\n\t\t\t// New Cumulative TSN: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream 1: 4097, U: 1\n\t\t\t0x10, 0x01, 0x00, 0x01,\n\t\t\t// Message Identifier: 285212689\n\t\t\t0x11, 0x00, 0x00, 0x11,\n\t\t\t// Stream 2: 8194, U: 0\n\t\t\t0x20, 0x02, 0x00, 0x00,\n\t\t\t// Message Identifier: 570425378\n\t\t\t0x22, 0x00, 0x00, 0x22,\n\t\t\t// Stream 3: 12291, U: 1\n\t\t\t0x30, 0x03, 0x00, 0x01,\n\t\t\t// Message Identifier: 855638067\n\t\t\t0x33, 0x00, 0x00, 0x33,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::IForwardTsnChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 32,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  {\n                                       true,  4097,\n                                       285212689, },\n\t\t                                  {\n                                       false,    8194,\n                                       570425378, },\n\t\t                                  {\n                                       true,12291,\n                                       855638067, },\n    });\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 32,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  {\n                                       true,  4097,\n                                       285212689, },\n\t\t                                  {\n                                       false,    8194,\n                                       570425378, },\n\t\t                                  {\n                                       true,12291,\n                                       855638067, },\n    });\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 32,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetNewCumulativeTsn() == 287454020);\n\t\tREQUIRE(clonedChunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  clonedChunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                        {\n                                             true,  4097,\n                                             285212689, },\n\t\t                                        {\n                                             false,    8194,\n                                             570425378, },\n\t\t                                        {\n                                             true,12291,\n                                             855638067, },\n    });\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"IForwardTsnChunk::Parse() fails\")\n\t{\n\t\t// Length field is not multiple of 8.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:194 (I_FORWARD_TSN), Flags: 0b00000000, Length: 20 (should be 24)\n\t\t\t0xC2, 0b00000000, 0x00, 0x14,\n\t\t\t// New Cumulative TSN: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream 1: 4097, U: 1\n\t\t\t0x10, 0x01, 0x00, 0x01,\n\t\t\t// Message Identifier: 285212689\n\t\t\t0x11, 0x00, 0x00, 0x11,\n\t\t\t// Stream 2: 8194, U: 0\n\t\t\t0x20, 0x02, 0x00, 0x00,\n\t\t\t// Message Identifier (missing in Length field)\n\t\t\t0x22, 0x00, 0x00, 0x22,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IForwardTsnChunk::Parse(buffer1, sizeof(buffer1)));\n\t}\n\n\tSECTION(\"IForwardTsnChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::IForwardTsnChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 0);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 0);\n\t\tREQUIRE(chunk->GetSkippedStreams().empty());\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetNewCumulativeTsn(12345678);\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{\n\t\t    /*unordered*/ true, /*streamId*/ 1111, /*mid*/ 11110001 });\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{\n\t\t    /*unordered*/ false, /*streamId*/ 2222, /*mid*/ 22220002 });\n\t\tchunk->AddSkippedStream(\n\t\t  RTC::SCTP::AnyForwardTsnChunk::SkippedStream{\n\t\t    /*unordered*/ true, /*streamId*/ 3333, /*mid*/ 33330003 });\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 32,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetNewCumulativeTsn() == 12345678);\n\t\tREQUIRE(chunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  chunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                  { true,  1111, 11110001 },\n\t\t                                  { false, 2222, 22220002 },\n\t\t                                  { true,  3333, 33330003 },\n    });\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::IForwardTsnChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 32,\n\t\t  /*length*/ 32,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetNewCumulativeTsn() == 12345678);\n\t\tREQUIRE(parsedChunk->GetNumberOfSkippedStreams() == 3);\n\t\tREQUIRE(\n\t\t  parsedChunk->GetSkippedStreams() == std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t                                        { true,  1111, 11110001 },\n\t\t                                        { false, 2222, 22220002 },\n\t\t                                        { true,  3333, 33330003 },\n    });\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestInitAckChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitAckChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\n// NOTE: Simplified since it's similar to InitChunk.\nSCENARIO(\"SCTP Init Acknowledgement (2)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"InitAckChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:2 (INIT_ACK), Flags: 0b00000000, Length: 28\n\t\t\t0x02, 0b00000000, 0x00, 0x1C,\n\t\t\t// Initiate Tag: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Outbound Streams: 4660, Number of Inbound Streams: 22136\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// Initial TSN: 2882339074\n\t\t\t0xAB, 0xCD, 0x01, 0x02,\n\t\t\t// Parameter 1: Type:5 (IPV4_ADDRESS), Length: 8\n\t\t\t0x00, 0x05, 0x00, 0x08,\n\t\t\t// IPv4 Address: \"2.3.4.5\"\n\t\t\t0x02, 0x03, 0x04, 0x05,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::InitAckChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 28,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 287454020);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 4660);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 22136);\n\t\tREQUIRE(chunk->GetInitialTsn() == 2882339074);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetIPv4Address()[0] == 0x02);\n\t\tREQUIRE(parameter1->GetIPv4Address()[1] == 0x03);\n\t\tREQUIRE(parameter1->GetIPv4Address()[2] == 0x04);\n\t\tREQUIRE(parameter1->GetIPv4Address()[3] == 0x05);\n\n\t\tdelete chunk;\n\t}\n\n\tSECTION(\"InitAckChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::InitAckChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 0);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 0);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 0);\n\t\tREQUIRE(chunk->GetInitialTsn() == 0);\n\n\t\t/* Modify it and add Parameters. */\n\n\t\tchunk->SetInitiateTag(1111111110);\n\t\tchunk->SetAdvertisedReceiverWindowCredit(2222222220);\n\t\tchunk->SetNumberOfOutboundStreams(1234);\n\t\tchunk->SetNumberOfInboundStreams(5678);\n\t\tchunk->SetInitialTsn(3333333330);\n\n\t\tauto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::IPv4AddressParameter>();\n\n\t\t// 11.22.33.44 IPv4 in network order.\n\t\tuint8_t ipBuffer1[] = { 0x0B, 0x16, 0x21, 0x2C };\n\n\t\tparameter1->SetIPv4Address(ipBuffer1);\n\t\tparameter1->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 28,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 1111111110);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 2222222220);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 1234);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 5678);\n\t\tREQUIRE(chunk->GetInitialTsn() == 3333333330);\n\n\t\tconst auto* addedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[0] == 0x0B);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[1] == 0x16);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[2] == 0x21);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[3] == 0x2C);\n\n\t\tdelete chunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestInitChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/InitChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Init Chunk (1)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"InitChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:1 (INIT), Flags: 0b00000000, Length: 56\n\t\t\t0x01, 0b00000000, 0x00, 0x38,\n\t\t\t// Initiate Tag: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Outbound Streams: 4660, Number of Inbound Streams: 22136\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// Initial TSN: 2882339074\n\t\t\t0xAB, 0xCD, 0x01, 0x02,\n\t\t\t// Parameter 1: Type:5 (IPV4_ADDRESS), Length: 8\n\t\t\t0x00, 0x05, 0x00, 0x08,\n\t\t\t// IPv4 Address: \"2.3.4.5\"\n\t\t\t0x02, 0x03, 0x04, 0x05,\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 20\n\t\t\t0x00, 0x06, 0x00, 0x14,\n\t\t\t// Parameter 2: IPv6 Address: \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73, 0x34,\n\t\t\t// Parameter 3: Type:9 (COOKIE_PRESERVATIVE), Length: 8\n\t\t\t0x00, 0x09, 0x00, 0x08,\n\t\t\t// Suggested Cookie Life-Span Increment: 556942164\n\t\t\t0x21, 0x32, 0x43, 0x54,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::InitChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 56,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 3,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 287454020);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 4660);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 22136);\n\t\tREQUIRE(chunk->GetInitialTsn() == 2882339074);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetIPv4Address()[0] == 0x02);\n\t\tREQUIRE(parameter1->GetIPv4Address()[1] == 0x03);\n\t\tREQUIRE(parameter1->GetIPv4Address()[2] == 0x04);\n\t\tREQUIRE(parameter1->GetIPv4Address()[3] == 0x05);\n\n\t\tconst auto* parameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv6AddressParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(parameter2->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(parameter2->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(parameter2->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(parameter2->GetIPv6Address()[15] == 0x34);\n\n\t\tconst auto* parameter3 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(chunk->GetParameterAt(2));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3->GetLifeSpanIncrement() == 556942164);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 56,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 3,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 287454020);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 4660);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 22136);\n\t\tREQUIRE(chunk->GetInitialTsn() == 2882339074);\n\n\t\tparameter1 = reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetIPv4Address()[0] == 0x02);\n\t\tREQUIRE(parameter1->GetIPv4Address()[1] == 0x03);\n\t\tREQUIRE(parameter1->GetIPv4Address()[2] == 0x04);\n\t\tREQUIRE(parameter1->GetIPv4Address()[3] == 0x05);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::IPv6AddressParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(parameter2->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(parameter2->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(parameter2->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(parameter2->GetIPv6Address()[15] == 0x34);\n\n\t\tparameter3 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(chunk->GetParameterAt(2));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3->GetLifeSpanIncrement() == 556942164);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 56,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 3,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetInitiateTag() == 287454020);\n\t\tREQUIRE(clonedChunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(clonedChunk->GetNumberOfOutboundStreams() == 4660);\n\t\tREQUIRE(clonedChunk->GetNumberOfInboundStreams() == 22136);\n\t\tREQUIRE(clonedChunk->GetInitialTsn() == 2882339074);\n\n\t\tparameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(clonedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetIPv4Address()[0] == 0x02);\n\t\tREQUIRE(parameter1->GetIPv4Address()[1] == 0x03);\n\t\tREQUIRE(parameter1->GetIPv4Address()[2] == 0x04);\n\t\tREQUIRE(parameter1->GetIPv4Address()[3] == 0x05);\n\n\t\tparameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv6AddressParameter*>(clonedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(parameter2->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(parameter2->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(parameter2->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(parameter2->GetIPv6Address()[15] == 0x34);\n\n\t\tparameter3 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(clonedChunk->GetParameterAt(2));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter3->GetLifeSpanIncrement() == 556942164);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"InitChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 0);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 0);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 0);\n\t\tREQUIRE(chunk->GetInitialTsn() == 0);\n\n\t\t/* Modify it and add Parameters. */\n\n\t\tchunk->SetInitiateTag(1111111110);\n\t\tchunk->SetAdvertisedReceiverWindowCredit(2222222220);\n\t\tchunk->SetNumberOfOutboundStreams(1234);\n\t\tchunk->SetNumberOfInboundStreams(5678);\n\t\tchunk->SetInitialTsn(3333333330);\n\n\t\tauto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::IPv4AddressParameter>();\n\n\t\t// 11.22.33.44 IPv4 in network order.\n\t\tuint8_t ipBuffer1[] = { 0x0B, 0x16, 0x21, 0x2C };\n\n\t\tparameter1->SetIPv4Address(ipBuffer1);\n\t\tparameter1->Consolidate();\n\n\t\tauto* parameter2 = chunk->BuildParameterInPlace<RTC::SCTP::IPv6AddressParameter>();\n\n\t\t// 2345:0425:2CA1:0000:0000:0567:5673:23b5 IPv6 in network order.\n\t\tuint8_t ipBuffer2[] = { 0x23, 0x45, 0x04, 0x25, 0x2C, 0xA1, 0x00, 0x00,\n\t\t\t                      0x00, 0x00, 0x05, 0x67, 0x56, 0x73, 0x23, 0xB5 };\n\n\t\tparameter2->SetIPv6Address(ipBuffer2);\n\t\tparameter2->Consolidate();\n\n\t\tauto* parameter3 = chunk->BuildParameterInPlace<RTC::SCTP::CookiePreservativeParameter>();\n\n\t\tparameter3->SetLifeSpanIncrement(876543210);\n\t\tparameter3->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 56,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 3,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetInitiateTag() == 1111111110);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 2222222220);\n\t\tREQUIRE(chunk->GetNumberOfOutboundStreams() == 1234);\n\t\tREQUIRE(chunk->GetNumberOfInboundStreams() == 5678);\n\t\tREQUIRE(chunk->GetInitialTsn() == 3333333330);\n\n\t\tconst auto* addedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[0] == 0x0B);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[1] == 0x16);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[2] == 0x21);\n\t\tREQUIRE(addedParameter1->GetIPv4Address()[3] == 0x2C);\n\n\t\tconst auto* addedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv6AddressParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter2->GetIPv6Address()[0] == 0x23);\n\t\tREQUIRE(addedParameter2->GetIPv6Address()[1] == 0x45);\n\t\tREQUIRE(addedParameter2->GetIPv6Address()[2] == 0x04);\n\t\tREQUIRE(addedParameter2->GetIPv6Address()[3] == 0x25);\n\t\tREQUIRE(addedParameter2->GetIPv6Address()[15] == 0xB5);\n\n\t\tconst auto* addedParameter3 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(chunk->GetParameterAt(2));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter3->GetLifeSpanIncrement() == 876543210);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::InitChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 56,\n\t\t  /*length*/ 56,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 3,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetInitiateTag() == 1111111110);\n\t\tREQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 2222222220);\n\t\tREQUIRE(parsedChunk->GetNumberOfOutboundStreams() == 1234);\n\t\tREQUIRE(parsedChunk->GetNumberOfInboundStreams() == 5678);\n\t\tREQUIRE(parsedChunk->GetInitialTsn() == 3333333330);\n\n\t\tconst auto* parsedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv4AddressParameter*>(parsedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter1->GetIPv4Address()[0] == 0x0B);\n\t\tREQUIRE(parsedParameter1->GetIPv4Address()[1] == 0x16);\n\t\tREQUIRE(parsedParameter1->GetIPv4Address()[2] == 0x21);\n\t\tREQUIRE(parsedParameter1->GetIPv4Address()[3] == 0x2C);\n\n\t\tconst auto* parsedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IPv6AddressParameter*>(parsedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter2->GetIPv6Address()[0] == 0x23);\n\t\tREQUIRE(parsedParameter2->GetIPv6Address()[1] == 0x45);\n\t\tREQUIRE(parsedParameter2->GetIPv6Address()[2] == 0x04);\n\t\tREQUIRE(parsedParameter2->GetIPv6Address()[3] == 0x25);\n\t\tREQUIRE(parsedParameter2->GetIPv6Address()[15] == 0xB5);\n\n\t\tconst auto* parsedParameter3 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(parsedChunk->GetParameterAt(2));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter3->GetLifeSpanIncrement() == 876543210);\n\n\t\tdelete parsedChunk;\n\t}\n\n\tSECTION(\"InitChunk::Factory() using AddParameter() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tchunk->SetInitiateTag(1);\n\t\tchunk->SetAdvertisedReceiverWindowCredit(2);\n\t\tchunk->SetNumberOfOutboundStreams(3);\n\t\tchunk->SetNumberOfInboundStreams(4);\n\t\tchunk->SetInitialTsn(5);\n\n\t\tauto* parameter1 = RTC::SCTP::CookiePreservativeParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// 8 bytes Parameter.\n\t\tparameter1->SetLifeSpanIncrement(123456);\n\n\t\tchunk->AddParameter(parameter1);\n\n\t\t// Once added, we can delete the Parameter.\n\t\tdelete parameter1;\n\n\t\t// Chunk length must be:\n\t\t// - Chunk header: 20\n\t\t// - Parameter 1: 8\n\t\t// - Total: 28\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 28,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* obtainedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter1->GetLifeSpanIncrement() == 123456);\n\n\t\t// 4 bytes Parameter.\n\t\tauto* parameter2 = RTC::SCTP::SupportedAddressTypesParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer));\n\n\t\t// Add 6 bytes.\n\t\tparameter2->AddAddressType(1111);\n\t\tparameter2->AddAddressType(2222);\n\t\tparameter2->AddAddressType(3333);\n\n\t\tchunk->AddParameter(parameter2);\n\n\t\t// Once added, we can delete the Parameter.\n\t\tdelete parameter2;\n\n\t\t// Chunk length must be:\n\t\t// - Chunk header: 20\n\t\t// - Parameter 1: 8\n\t\t// - Parameter 2: 12\n\t\t// - Total: 40\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 40,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* obtainedParameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::SupportedAddressTypesParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter2->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(0) == 1111);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(1) == 2222);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(2) == 3333);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::InitChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 40,\n\t\t  /*length*/ 40,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetInitiateTag() == 1);\n\t\tREQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 2);\n\t\tREQUIRE(parsedChunk->GetNumberOfOutboundStreams() == 3);\n\t\tREQUIRE(parsedChunk->GetNumberOfInboundStreams() == 4);\n\t\tREQUIRE(parsedChunk->GetInitialTsn() == 5);\n\n\t\tobtainedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::CookiePreservativeParameter*>(parsedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter1->GetLifeSpanIncrement() == 123456);\n\n\t\tobtainedParameter2 = reinterpret_cast<const RTC::SCTP::SupportedAddressTypesParameter*>(\n\t\t  parsedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ obtainedParameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(obtainedParameter2->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(0) == 1111);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(1) == 2222);\n\t\tREQUIRE(obtainedParameter2->GetAddressTypeAt(2) == 3333);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestOperationErrorChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/chunks/OperationErrorChunk.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Operation Error Chunk (9)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"OperationErrorChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:9 (OPERATION_ERROR), Flags:0b00000000, Length: 21\n\t\t\t0x09, 0b00000000, 0x00, 0x15,\n\t\t\t// Error Cause 1: Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8\n\t\t\t0x00, 0x01, 0x00, 0x08,\n\t\t\t// Stream Identifier: 0x1234\n\t\t\t0x12, 0x34, 0x00, 0x00,\n\t\t\t// Error Cause 2: Code:4 (OUT_OF_RESOURCE), Length: 4\n\t\t\t0x00, 0x04, 0x00, 0x04,\n\t\t\t// Error Cause 3: Type:49159 (UNKNOWN), Length: 5\n\t\t\t0xC0, 0x07, 0x00, 0x05,\n\t\t\t// Unknown data: 0xAB, 3 bytes of padding\n\t\t\t0xAB, 0x00, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::OperationErrorChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 3);\n\n\t\tconst auto* errorCause1 = reinterpret_cast<const RTC::SCTP::InvalidStreamIdentifierErrorCause*>(\n\t\t  chunk->GetErrorCauseAt(0));\n\n\t\tREQUIRE(\n\t\t  chunk->GetFirstErrorCauseOfCode<RTC::SCTP::InvalidStreamIdentifierErrorCause>() == errorCause1);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetStreamIdentifier() == 0x1234);\n\n\t\tconst auto* errorCause2 =\n\t\t  reinterpret_cast<const RTC::SCTP::OutOfResourceErrorCause*>(chunk->GetErrorCauseAt(1));\n\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::OutOfResourceErrorCause>() == errorCause2);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\tconst auto* errorCause3 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnknownErrorCause*>(chunk->GetErrorCauseAt(2));\n\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnknownErrorCause>() == errorCause3);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(49159),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(errorCause3->HasUnknownValue() == true);\n\t\tREQUIRE(errorCause3->GetUnknownValueLength() == 1);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause3->GetUnknownValue()[1] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 3);\n\n\t\terrorCause1 = reinterpret_cast<const RTC::SCTP::InvalidStreamIdentifierErrorCause*>(\n\t\t  chunk->GetErrorCauseAt(0));\n\n\t\tREQUIRE(\n\t\t  chunk->GetFirstErrorCauseOfCode<RTC::SCTP::InvalidStreamIdentifierErrorCause>() == errorCause1);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetStreamIdentifier() == 0x1234);\n\n\t\terrorCause2 =\n\t\t  reinterpret_cast<const RTC::SCTP::OutOfResourceErrorCause*>(chunk->GetErrorCauseAt(1));\n\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::OutOfResourceErrorCause>() == errorCause2);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\terrorCause3 = reinterpret_cast<const RTC::SCTP::UnknownErrorCause*>(chunk->GetErrorCauseAt(2));\n\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnknownErrorCause>() == errorCause3);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(49159),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(errorCause3->HasUnknownValue() == true);\n\t\tREQUIRE(errorCause3->GetUnknownValueLength() == 1);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause3->GetUnknownValue()[1] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 3);\n\n\t\terrorCause1 = reinterpret_cast<const RTC::SCTP::InvalidStreamIdentifierErrorCause*>(\n\t\t  clonedChunk->GetErrorCauseAt(0));\n\n\t\tREQUIRE(\n\t\t  clonedChunk->GetFirstErrorCauseOfCode<RTC::SCTP::InvalidStreamIdentifierErrorCause>() ==\n\t\t  errorCause1);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownType*/ false);\n\n\t\tREQUIRE(errorCause1->GetStreamIdentifier() == 0x1234);\n\n\t\terrorCause2 =\n\t\t  reinterpret_cast<const RTC::SCTP::OutOfResourceErrorCause*>(clonedChunk->GetErrorCauseAt(1));\n\n\t\tREQUIRE(\n\t\t  clonedChunk->GetFirstErrorCauseOfCode<RTC::SCTP::OutOfResourceErrorCause>() == errorCause2);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\terrorCause3 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnknownErrorCause*>(clonedChunk->GetErrorCauseAt(2));\n\n\t\tREQUIRE(clonedChunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnknownErrorCause>() == errorCause3);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause3,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(49159),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(errorCause3->HasUnknownValue() == true);\n\t\tREQUIRE(errorCause3->GetUnknownValueLength() == 1);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause3->GetUnknownValue()[1] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[2] == 0x00);\n\t\tREQUIRE(errorCause3->GetUnknownValue()[3] == 0x00);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"OperationErrorChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::OperationErrorChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnrecognizedChunkTypeErrorCause>() == nullptr);\n\t\tREQUIRE(chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnknownErrorCause>() == nullptr);\n\n\t\t/* Modify it by adding Error Causes. */\n\n\t\tauto* errorCause1 = chunk->BuildErrorCauseInPlace<RTC::SCTP::UnrecognizedChunkTypeErrorCause>();\n\n\t\t// Unrecognized Chunk length is 5 so 3 bytes of padding will be added.\n\t\terrorCause1->SetUnrecognizedChunk(sctpCommon::DataBuffer, 5);\n\t\terrorCause1->Consolidate();\n\n\t\tREQUIRE(\n\t\t  chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnrecognizedChunkTypeErrorCause>() == errorCause1);\n\n\t\t// // Let's add another UnrecognizedChunkTypeErrorCause.\n\t\tauto* errorCause2 = chunk->BuildErrorCauseInPlace<RTC::SCTP::UnrecognizedChunkTypeErrorCause>();\n\n\t\t// Unrecognized Chunk is 2 so 2 bytes of padding will be added.\n\t\terrorCause2->SetUnrecognizedChunk(sctpCommon::DataBuffer, 2);\n\t\terrorCause2->Consolidate();\n\n\t\t// Still must return the first Error Cause.\n\t\tREQUIRE(\n\t\t  chunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnrecognizedChunkTypeErrorCause>() == errorCause1);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 2);\n\n\t\tconst auto* addedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnrecognizedChunkTypeErrorCause*>(chunk->GetErrorCauseAt(0));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ addedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(addedErrorCause1->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunkLength() == 5);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[1] == 0x01);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[2] == 0x02);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[3] == 0x03);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[5] == 0x00);\n\t\tREQUIRE(addedErrorCause1->GetUnrecognizedChunk()[6] == 0x00);\n\n\t\tconst auto* addedErrorCause2 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnrecognizedChunkTypeErrorCause*>(chunk->GetErrorCauseAt(1));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ addedErrorCause2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(addedErrorCause2->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(addedErrorCause2->GetUnrecognizedChunkLength() == 2);\n\t\tREQUIRE(addedErrorCause2->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(addedErrorCause2->GetUnrecognizedChunk()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(addedErrorCause2->GetUnrecognizedChunk()[2] == 0x00);\n\t\tREQUIRE(addedErrorCause2->GetUnrecognizedChunk()[3] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::OperationErrorChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2),\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ true,\n\t\t  /*errorCausesCount*/ 2);\n\n\t\tconst auto* parsedErrorCause1 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnrecognizedChunkTypeErrorCause*>(\n\t\t    parsedChunk->GetErrorCauseAt(0));\n\n\t\tREQUIRE(\n\t\t  parsedChunk->GetFirstErrorCauseOfCode<RTC::SCTP::UnrecognizedChunkTypeErrorCause>() ==\n\t\t  parsedErrorCause1);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause1->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunkLength() == 5);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[1] == 0x01);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[2] == 0x02);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[3] == 0x03);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[5] == 0x00);\n\t\tREQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[6] == 0x00);\n\n\t\tconst auto* parsedErrorCause2 =\n\t\t  reinterpret_cast<const RTC::SCTP::UnrecognizedChunkTypeErrorCause*>(\n\t\t    parsedChunk->GetErrorCauseAt(1));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause2->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(parsedErrorCause2->GetUnrecognizedChunkLength() == 2);\n\t\tREQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[1] == 0x01);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[2] == 0x00);\n\t\tREQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[3] == 0x00);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestReConfigChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/chunks/ReConfigChunk.hpp\"\n#include \"RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"SCTP Re-Config Chunk (130)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ReConfigChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:130 (RE_CONFIG), Flags:0b00000000, Length: 38\n\t\t\t// NOTE: Length field must exclude the padding of the last Parameter.\n\t\t\t0x82, 0b00000000, 0x00, 0x26,\n\t\t\t// Parameter 1: Type:13 (OUTGOING_SSN_RESET_REQUEST), Length: 22\n\t\t\t0x00, 0x0D, 0x00, 0x16,\n\t\t\t// Re-configuration Request Sequence Number: 0x11223344\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Re-configuration Response Sequence Number: 0x55667788\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t// Sender's Last Assigned TSN: 0xAABBCCDD\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t// Stream 1: 0x5001, Stream 2: 0x5002\n\t\t\t0x50, 0x01, 0x50, 0x02,\n\t\t\t// Stream 3: 0x5003, 2 bytes of padding\n\t\t\t0x50, 0x03, 0x00, 0x00,\n\t\t\t// Parameter 2: Type:14 (INCOMING_SSN_RESET_REQUEST), Length: 10\n\t\t\t0x00, 0x0E, 0x00, 0x0A,\n\t\t\t// Re-configuration Request Sequence Number: 0x44332211\n\t\t\t0x44, 0x33, 0x22, 0x11,\n\t\t\t// Stream 1: 0x6001, 2 bytes of padding\n\t\t\t0x60, 0x01, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::ReConfigChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 40,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::OutgoingSsnResetRequestParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 24,\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\n\t\tconst std::vector<uint16_t> expectedStreamIds1{\n\t\t\t{ 0x5001, 0x5002, 0x5003 },\n\t\t};\n\n\t\tREQUIRE(parameter1->GetStreamIds() == expectedStreamIds1);\n\n\t\tconst auto* parameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IncomingSsnResetRequestParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/\n\t\t  RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211);\n\n\t\tconst std::vector<uint16_t> expectedStreamIds2{\n\t\t\t0x6001,\n\t\t};\n\n\t\tREQUIRE(parameter2->GetStreamIds() == expectedStreamIds2);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 40,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::OutgoingSsnResetRequestParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 24,\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\t\tREQUIRE(parameter1->GetStreamIds() == expectedStreamIds1);\n\n\t\tparameter2 =\n\t\t  reinterpret_cast<const RTC::SCTP::IncomingSsnResetRequestParameter*>(chunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/\n\t\t  RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211);\n\t\tREQUIRE(parameter2->GetStreamIds() == expectedStreamIds2);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 40,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 2,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tparameter1 = reinterpret_cast<const RTC::SCTP::OutgoingSsnResetRequestParameter*>(\n\t\t  clonedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 24,\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\t\tREQUIRE(parameter1->GetStreamIds() == expectedStreamIds1);\n\n\t\tparameter2 = reinterpret_cast<const RTC::SCTP::IncomingSsnResetRequestParameter*>(\n\t\t  clonedChunk->GetParameterAt(1));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter2,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/\n\t\t  RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211);\n\t\tREQUIRE(parameter2->GetStreamIds() == expectedStreamIds2);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"ReConfigChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::ReConfigChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Modify it by adding Parameters. */\n\n\t\tauto* parameter1 = chunk->BuildParameterInPlace<RTC::SCTP::ReconfigurationResponseParameter>();\n\n\t\t// Parameter will have 20 bytes length.\n\t\tparameter1->SetReconfigurationResponseSequenceNumber(11112222);\n\t\tparameter1->SetResult(RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tparameter1->SetNextTsns(100000001, 200000002);\n\t\tparameter1->Consolidate();\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4 + 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* addedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::ReconfigurationResponseParameter*>(chunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ addedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(addedParameter1->GetReconfigurationResponseSequenceNumber() == 11112222);\n\t\tREQUIRE(\n\t\t  addedParameter1->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tREQUIRE(addedParameter1->HasNextTsns() == true);\n\t\tREQUIRE(addedParameter1->GetSenderNextTsn() == 100000001);\n\t\tREQUIRE(addedParameter1->GetReceiverNextTsn() == 200000002);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::ReConfigChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + 20,\n\t\t  /*length*/ 4 + 20,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ true,\n\t\t  /*parametersCount*/ 1,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tconst auto* parsedParameter1 =\n\t\t  reinterpret_cast<const RTC::SCTP::ReconfigurationResponseParameter*>(\n\t\t    parsedChunk->GetParameterAt(0));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter1,\n\t\t  /*buffer*/ nullptr,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter1->GetReconfigurationResponseSequenceNumber() == 11112222);\n\t\tREQUIRE(\n\t\t  parsedParameter1->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tREQUIRE(parsedParameter1->HasNextTsns() == true);\n\t\tREQUIRE(parsedParameter1->GetSenderNextTsn() == 100000001);\n\t\tREQUIRE(parsedParameter1->GetReceiverNextTsn() == 200000002);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestSackChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"Selective Acknowledgement Chunk (3)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"SackChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:3 (SACK), Flags: 0b00000000, Length: 36\n\t\t\t0x03, 0b00000000, 0x00, 0x24,\n\t\t\t// Cumulative TSN Ack: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Gap Ack Blocks: 2, Number of Duplicate TSNs: 3\n\t\t\t0x00, 0x02, 0x00, 0x03,\n\t\t\t// Gap Ack Block 1: Start: 1000, End: 1999\n\t\t\t0x03, 0xE8, 0x07, 0xCF,\n\t\t\t// Gap Ack Block 2: Start: 2000, End: 2999\n\t\t\t// Notice that this is wrong since it should be merged with the first\n\t\t\t// Gap Ack Block.\n\t\t\t0x07, 0xD0, 0x0B, 0xB7,\n\t\t\t// Duplicate TSN 1: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Duplicate TSN 2: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Duplicate TSN 3: 556942164\n\t\t\t0x21, 0x32, 0x43, 0x54,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::SackChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 36,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 287454020);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(\n\t\t  chunk->GetDuplicateTsns() == std::vector<uint32_t>{\n\t\t                                 { 287454020, 4278216311, 556942164 }\n    });\n\t\tREQUIRE(\n\t\t  chunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                { 1000, 1999 },\n                                    { 2000, 2999 }\n    });\n\t\tREQUIRE(\n\t\t  chunk->GetValidatedGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                         { 1000, 2999 },\n    });\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 36,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 287454020);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(\n\t\t  chunk->GetDuplicateTsns() == std::vector<uint32_t>{\n\t\t                                 { 287454020, 4278216311, 556942164 }\n    });\n\t\tREQUIRE(\n\t\t  chunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                { 1000, 1999 },\n                                    { 2000, 2999 }\n    });\n\t\tREQUIRE(\n\t\t  chunk->GetValidatedGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                         { 1000, 2999 }\n    });\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 36,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetCumulativeTsnAck() == 287454020);\n\t\tREQUIRE(clonedChunk->GetAdvertisedReceiverWindowCredit() == 4278216311);\n\t\tREQUIRE(\n\t\t  clonedChunk->GetDuplicateTsns() == std::vector<uint32_t>{\n\t\t                                       { 287454020, 4278216311, 556942164 }\n    });\n\t\tREQUIRE(\n\t\t  clonedChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                      { 1000, 1999 },\n                                          { 2000, 2999 }\n    });\n\t\tREQUIRE(\n\t\t  clonedChunk->GetValidatedGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                               { 1000, 2999 }\n    });\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"SackChunk::Parse() fails\")\n\t{\n\t\t// Length field doesn't match Number of Gap Ack Blocks + Number of\n\t\t// Duplicate TSNs.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:3 (SACK), Flags: 0b00000000, Length: 24 (should be 28)\n\t\t\t0x03, 0b00000000, 0x00, 0x18,\n\t\t\t// Cumulative TSN Ack: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 2\n\t\t\t0x00, 0x01, 0x00, 0x02,\n\t\t\t// Gap Ack Block 1: Start: 1000, End: 1999\n\t\t\t0x03, 0xE8, 0x07, 0xCF,\n\t\t\t// Duplicate TSN 1: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Duplicate TSN 2: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::SackChunk::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Length field doesn't match Number of Gap Ack Blocks + Number of\n\t\t// Duplicate TSNs.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:3 (SACK), Flags: 0b00000000, Length: 32 (should be 28)\n\t\t\t0x03, 0b00000000, 0x00, 0x20,\n\t\t\t// Cumulative TSN Ack: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 2\n\t\t\t0x00, 0x01, 0x00, 0x02,\n\t\t\t// Gap Ack Block 1: Start: 1000, End: 1999\n\t\t\t0x03, 0xE8, 0x07, 0xCF,\n\t\t\t// Duplicate TSN 1: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Duplicate TSN 2: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Duplicate TSN 3: 4278216312 (exceeds Number of Duplicate TSNs)\n\t\t\t0xFF, 0x00, 0x66, 0x78,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::SackChunk::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field (smaller than buffer).\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Type:3 (SACK), Flags: 0b00000000, Length: 24 (buffer is 20)\n\t\t\t0x03, 0b00000000, 0x00, 0x18,\n\t\t\t// Cumulative TSN Ack: 287454020,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Advertised Receiver Window Credit: 4278216311\n\t\t\t0xFF, 0x00, 0x66, 0x77,\n\t\t\t// Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 0\n\t\t\t0x00, 0x01, 0x00, 0x00,\n\t\t\t// Gap Ack Block 1: Start: 1000, End: 1999\n\t\t\t0x03, 0xE8, 0x07, 0xCF,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::SackChunk::Parse(buffer3, sizeof(buffer3)));\n\t}\n\n\tSECTION(\"SackChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk =\n\t\t  RTC::SCTP::SackChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 0);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0);\n\t\tREQUIRE(chunk->GetDuplicateTsns().empty());\n\t\tREQUIRE(chunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(chunk->GetValidatedGapAckBlocks().empty());\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetCumulativeTsnAck(1234);\n\t\tchunk->SetAdvertisedReceiverWindowCredit(5678);\n\t\tchunk->AddDuplicateTsn(10000000);\n\t\tchunk->AddAckBlock(10000, 19999);\n\t\t// Notice that here we are creating a semig-wrong SACK Chunk since these\n\t\t// two ranges shoyuld be merged into one.\n\t\tchunk->AddAckBlock(RTC::SCTP::SackChunk::GapAckBlock(20000, 20999));\n\t\tchunk->AddDuplicateTsn(20000000);\n\t\tchunk->AddAckBlock(60000, 60999);\n\t\tchunk->AddDuplicateTsn(30000000);\n\t\tchunk->AddDuplicateTsn(40000000);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 44,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 1234);\n\t\tREQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 5678);\n\t\tREQUIRE(\n\t\t  chunk->GetDuplicateTsns() == std::vector<uint32_t>{ 10000000, 20000000, 30000000, 40000000 });\n\t\tREQUIRE(\n\t\t  chunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                { 10000, 19999 },\n                                    { 20000, 20999 },\n                                    { 60000, 60999 }\n    });\n\t\tREQUIRE(\n\t\t  chunk->GetValidatedGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                         { 10000, 20999 },\n                                             { 60000, 60999 }\n    });\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::SackChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 44,\n\t\t  /*length*/ 44,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetCumulativeTsnAck() == 1234);\n\t\tREQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 5678);\n\t\tREQUIRE(\n\t\t  parsedChunk->GetDuplicateTsns() ==\n\t\t  std::vector<uint32_t>{ 10000000, 20000000, 30000000, 40000000 });\n\t\tREQUIRE(\n\t\t  parsedChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                      { 10000, 19999 },\n                                          { 20000, 20999 },\n                                          { 60000, 60999 }\n    });\n\t\tREQUIRE(\n\t\t  parsedChunk->GetValidatedGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                               { 10000, 20999 },\n                                                   { 60000, 60999 }\n    });\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestShutdownAckChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Shutdown Ack Chunk (8)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ShutdownAckChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:8 (SHUTDOWN_ACK), Flags:0x00000000, Length: 4\n\t\t\t0x08, 0b01000000, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::ShutdownAckChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b01000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b01000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b01000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"ShutdownAckChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::ShutdownAckChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::ShutdownAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestShutdownChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Shutdown Association Chunk (7)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ShutdownChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:7 (SHUTDOWN), Flags:0x00000000, Length: 8\n\t\t\t0x07, 0b00000000, 0x00, 0x08,\n\t\t\t// Cumulative TSN Ack: 0x11223344,\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::ShutdownChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 0x11223344);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 0x11223344);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetCumulativeTsnAck() == 0x11223344);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"ShutdownChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::ShutdownChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 0);\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetCumulativeTsnAck(99887766);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetCumulativeTsnAck() == 99887766);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk = RTC::SCTP::ShutdownChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetCumulativeTsnAck() == 99887766);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestShutdownCompleteChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Shutdown Complete Chunk (14)\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ShutdownCompleteChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:8 (SHUTDOWN_COMPLETE), Flags:0x00000001, T: 1, Length: 4\n\t\t\t0x0E, 0b00000001, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::ShutdownCompleteChunk::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->GetT() == true);\n\n\t\tdelete clonedChunk;\n\t}\n\n\tSECTION(\"ShutdownCompleteChunk::Factory() succeeds\")\n\t{\n\t\tauto* chunk = RTC::SCTP::ShutdownCompleteChunk::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000000,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetT() == false);\n\n\t\t/* Modify it. */\n\n\t\tchunk->SetT(true);\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->GetT() == true);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedChunk =\n\t\t  RTC::SCTP::ShutdownCompleteChunk::Parse(chunk->GetBuffer(), chunk->GetLength());\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ parsedChunk,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP,\n\t\t  /*flags*/ 0b00000001,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(parsedChunk->GetT() == true);\n\n\t\tdelete parsedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/chunks/TestUnknownChunk.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/UnknownChunk.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SCTP Unknown Chunk\", \"[serializable][sctp][chunk]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnknownChunk::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:0xEE (UNKNOWN), Flags: 0b1100, Length: 7\n\t\t\t0xEE, 0b10001100, 0x00, 0x07,\n\t\t\t// Unknown value: 0xAABBCC, 1 byte of padding\n\t\t\t0xAA, 0xBB, 0xCC, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* chunk = RTC::SCTP::UnknownChunk::Parse(buffer, sizeof(buffer));\n\n\t\t// NOTE: Chunk Type is 0xEE (0b11101110) so first 2 bits are 11, meaning\n\t\t// that the action to take if we receive this Chunk Type is SKIP_AND_REPORT.\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b10001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasUnknownValue() == true);\n\t\tREQUIRE(chunk->GetUnknownValueLength() == 3);\n\t\tREQUIRE(chunk->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(chunk->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(chunk->GetUnknownValue()[2] == 0xCC);\n\n\t\t/* Serialize it. */\n\n\t\tchunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ chunk,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b10001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(chunk->HasUnknownValue() == true);\n\t\tREQUIRE(chunk->GetUnknownValueLength() == 3);\n\t\tREQUIRE(chunk->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(chunk->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(chunk->GetUnknownValue()[2] == 0xCC);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete chunk;\n\n\t\tCHECK_SCTP_CHUNK(\n\t\t  /*chunk*/ clonedChunk,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*chunkType*/ static_cast<RTC::SCTP::Chunk::ChunkType>(0xEE),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT,\n\t\t  /*flags*/ 0b10001100,\n\t\t  /*canHaveParameters*/ false,\n\t\t  /*parametersCount*/ 0,\n\t\t  /*canHaveErrorCauses*/ false,\n\t\t  /*errorCausesCount*/ 0);\n\n\t\tREQUIRE(clonedChunk->HasUnknownValue() == true);\n\t\tREQUIRE(clonedChunk->GetUnknownValueLength() == 3);\n\t\tREQUIRE(clonedChunk->GetUnknownValue()[0] == 0xAA);\n\t\tREQUIRE(clonedChunk->GetUnknownValue()[1] == 0xBB);\n\t\tREQUIRE(clonedChunk->GetUnknownValue()[2] == 0xCC);\n\n\t\tdelete clonedChunk;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestCookieReceivedWhileShuttingDownErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Cookie Received While Shutting Down Error Cause (10)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"CookieReceivedWhileShuttingDownErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:10 (COOKIE_RECEIVED_WHILE_SHUTTING_DOWN), Length: 4\n\t\t\t0x00, 0x0A, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause =\n\t\t  RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"CookieReceivedWhileShuttingDownErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestInvalidMandatoryParameterErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Invalid Mandatory Parameter Error Cause (7)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"InvalidMandatoryParameterErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:7 (INVALID_MANDATORY_PARAMETER), Length: 4\n\t\t\t0x00, 0x07, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"InvalidMandatoryParameterErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestInvalidStreamIdentifierErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Invalid Stream Identifier Error Cause (1)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"InvalidStreamIdentifierErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8\n\t\t\t0x00, 0x01, 0x00, 0x08,\n\t\t\t// Stream Identifier: 12345\n\t\t\t0x30, 0x39, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetStreamIdentifier() == 12345);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(errorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[7] == 0);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetStreamIdentifier() == 12345);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(errorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[7] == 0);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->GetStreamIdentifier() == 12345);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(clonedErrorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(clonedErrorCause->GetBuffer()[7] == 0);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"InvalidStreamIdentifierErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Stream Identifier: 12345\n\t\t\t0x30, 0x39, 0x00, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:1 (INVALID_STREAM_IDENTIFIER), Length: 7\n\t\t\t0x00, 0x01, 0x00, 0x07,\n\t\t\t// Stream Identifier: 12345\n\t\t\t0x30, 0x39, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Code:1 (INVALID_STREAM_IDENTIFIER), Length: 9\n\t\t\t0x00, 0x01, 0x00, 0x09,\n\t\t\t// Stream Identifier: 12345\n\t\t\t0x30, 0x39, 0x00, 0x00,\n\t\t\t0xEE\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8\n\t\t\t0x00, 0x01, 0x00, 0x08,\n\t\t\t// Stream Identifier: 12345\n\t\t\t0x30, 0x39, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"InvalidStreamIdentifierErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetStreamIdentifier() == 0);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(errorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[7] == 0);\n\n\t\t/* Modify it. */\n\n\t\terrorCause->SetStreamIdentifier(6666);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetStreamIdentifier() == 6666);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(errorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[7] == 0);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->GetStreamIdentifier() == 6666);\n\t\t// Reserved bytes must be 0.\n\t\tREQUIRE(parsedErrorCause->GetBuffer()[6] == 0);\n\t\tREQUIRE(parsedErrorCause->GetBuffer()[7] == 0);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestMissingMandatoryParameterErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Invalid Stream Identifier Error Cause (2)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"MissingMandatoryParameterErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:2 (MISSING_MANDATORY_PARAMETER), Length: 14\n\t\t\t0x00, 0x02, 0x00, 0x0E,\n\t\t\t// Number of missing params: 3\n\t\t\t0x00, 0x00, 0x00, 0x03,\n\t\t\t// Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS)\n\t\t\t0x00, 0x05, 0x00, 0x06,\n\t\t\t// Missing Param 3: 9 (COOKIE_PRESERVATIVE), 2 bytes of padding\n\t\t\t0x00, 0x09, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 16,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetNumberOfMissingParameters() == 3);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(2) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetBuffer()[14] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[15] == 0);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetNumberOfMissingParameters() == 3);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(2) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetBuffer()[14] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[15] == 0);\n\n\t\t// /* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->GetNumberOfMissingParameters() == 3);\n\t\tREQUIRE(\n\t\t  clonedErrorCause->GetMissingParameterTypeAt(0) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\tREQUIRE(\n\t\t  clonedErrorCause->GetMissingParameterTypeAt(1) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\tREQUIRE(\n\t\t  clonedErrorCause->GetMissingParameterTypeAt(2) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\t\t// These should be padding.\n\t\tREQUIRE(clonedErrorCause->GetBuffer()[14] == 0);\n\t\tREQUIRE(clonedErrorCause->GetBuffer()[15] == 0);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"MissingMandatoryParameterErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 14\n\t\t\t0x03, 0xE7, 0x00, 0x0E,\n\t\t\t// Number of missing params: 3\n\t\t\t0x00, 0x00, 0x00, 0x03,\n\t\t\t// Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS)\n\t\t\t0x00, 0x05, 0x00, 0x06,\n\t\t\t// Missing Param 3: 9 (COOKIE_PRESERVATIVE), 2 bytes of padding\n\t\t\t0x00, 0x09, 0x00, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Length field doesn't match Number of missing parameters.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:2 (MISSING_MANDATORY_PARAMETER), Length: 14\n\t\t\t0x00, 0x02, 0x00, 0x0E,\n\t\t\t// Number of missing params: 2\n\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t// Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS)\n\t\t\t0x00, 0x05, 0x00, 0x06,\n\t\t\t// Missing Param 3: 9 (COOKIE_PRESERVATIVE) (exceeds number of missing\n\t\t\t// parameters), 2 bytes of padding\n\t\t\t0x00, 0x09, 0x00, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field (smaller than buffer).\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Code:2 (MISSING_MANDATORY_PARAMETER), Length: 8 (buffer is 12)\n\t\t\t0x00, 0x02, 0x00, 0x08,\n\t\t\t// Number of missing params: 4\n\t\t\t0x00, 0x00, 0x00, 0x02,\n\t\t\t// Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS)\n\t\t\t0x00, 0x05, 0x00, 0x06,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer3, sizeof(buffer3)));\n\t}\n\n\tSECTION(\"MissingMandatoryParameterErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetNumberOfMissingParameters() == 0);\n\n\t\t/* Modify it. */\n\n\t\terrorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\terrorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\terrorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetNumberOfMissingParameters() == 3);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\tREQUIRE(\n\t\t  errorCause->GetMissingParameterTypeAt(2) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetBuffer()[14] == 0);\n\t\tREQUIRE(errorCause->GetBuffer()[15] == 0);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 16,\n\t\t  /*length*/ 16,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->GetNumberOfMissingParameters() == 3);\n\t\tREQUIRE(\n\t\t  parsedErrorCause->GetMissingParameterTypeAt(0) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS);\n\t\tREQUIRE(\n\t\t  parsedErrorCause->GetMissingParameterTypeAt(1) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS);\n\t\tREQUIRE(\n\t\t  parsedErrorCause->GetMissingParameterTypeAt(2) ==\n\t\t  RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetBuffer()[14] == 0);\n\t\tREQUIRE(parsedErrorCause->GetBuffer()[15] == 0);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestNoUserDataErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"No User Data Error Cause (9)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"NoUserDataErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:9 (NO_USER_DATA), Length: 8\n\t\t\t0x00, 0x09, 0x00, 0x08,\n\t\t\t// TSN: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::NoUserDataErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetTsn() == 987654321);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetTsn() == 987654321);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->GetTsn() == 987654321);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"NoUserDataErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// TSN: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:9 (NO_USER_DATA), Length: 7\n\t\t\t0x00, 0x09, 0x00, 0x07,\n\t\t\t// TSN: 987654321\n\t\t\t0x3A, 0xDE, 0x68,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Code:9 (NO_USER_DATA), Length: 9\n\t\t\t0x00, 0x09, 0x00, 0x09,\n\t\t\t// TSN: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t\t0xEE\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Code:9 (NO_USER_DATA), Length: 8\n\t\t\t0x00, 0x09, 0x00, 0x08,\n\t\t\t// TSN (last byte missing)\n\t\t\t0x3A, 0xDE, 0x68\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"NoUserDataErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::NoUserDataErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetTsn() == 0);\n\n\t\t/* Modify it. */\n\n\t\terrorCause->SetTsn(666666);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetTsn() == 666666);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause =\n\t\t  RTC::SCTP::NoUserDataErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->GetTsn() == 666666);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestOutOfResourceErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Out of Resource Error Cause (4)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"OutOfResourceErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:4 (OUT_OF_RESOURCE), Length: 4\n\t\t\t0x00, 0x04, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::OutOfResourceErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"OutOfResourceErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 4\n\t\t\t0x03, 0xE7, 0x00, 0x04\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:4 (OUT_OF_RESOURCE), Length: 5\n\t\t\t0x00, 0x04, 0x00, 0x07,\n\t\t\t0x3A,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Code:4 (OUT_OF_RESOURCE), Length (broken)\n\t\t\t0x00, 0x04, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer3, sizeof(buffer3)));\n\t}\n\n\tSECTION(\"OutOfResourceErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::OutOfResourceErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause =\n\t\t  RTC::SCTP::OutOfResourceErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE,\n\t\t  /*unknownCode*/ false);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestProtocolViolationErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Protocol Violation Error Cause (13)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ProtocolViolationErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:13 (PROTOCOL_VIOLATION), Length: 10\n\t\t\t0x00, 0x0D, 0x00, 0x0A,\n\t\t\t// Additional Information: \"error1\"\n\t\t\t0x65, 0x72, 0x72, 0x6F,\n\t\t\t// 2 bytes of padding.\n\t\t\t0x72, 0x31, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 6);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[0] == 0x65);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[1] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[2] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[3] == 0x6F);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[4] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[5] == 0x31);\n\n\t\tstd::string additionalInfo(\n\t\t  reinterpret_cast<const char*>(errorCause->GetAdditionalInformation()),\n\t\t  errorCause->GetAdditionalInformationLength());\n\n\t\tREQUIRE(additionalInfo == \"error1\");\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 6);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[0] == 0x65);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[1] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[2] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[3] == 0x6F);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[4] == 0x72);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[5] == 0x31);\n\n\t\tadditionalInfo = std::string(\n\t\t  reinterpret_cast<const char*>(errorCause->GetAdditionalInformation()),\n\t\t  errorCause->GetAdditionalInformationLength());\n\n\t\tREQUIRE(additionalInfo == \"error1\");\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformationLength() == 6);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[0] == 0x65);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[1] == 0x72);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[2] == 0x72);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[3] == 0x6F);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[4] == 0x72);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[5] == 0x31);\n\n\t\tadditionalInfo = std::string(\n\t\t  reinterpret_cast<const char*>(clonedErrorCause->GetAdditionalInformation()),\n\t\t  clonedErrorCause->GetAdditionalInformationLength());\n\n\t\tREQUIRE(additionalInfo == \"error1\");\n\t\t// These should be padding.\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[6] == 0x00);\n\t\tREQUIRE(clonedErrorCause->GetAdditionalInformation()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"ProtocolViolationErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Additional Information: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:13 (PROTOCOL_VIOLATION), Length: 7\n\t\t\t0x00, 0x0D, 0x00, 0x07,\n\t\t\t// Additional Information: 0x123456 (missing padding byte)\n\t\t\t0x12, 0x34, 0x56,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"ProtocolViolationErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::ProtocolViolationErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == false);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\terrorCause->SetAdditionalInformation(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(errorCause->GetLength() == 3004);\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 3000);\n\n\t\terrorCause->SetAdditionalInformation(nullptr, 0);\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == false);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 0);\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetAdditionalInformation(\"iñaki\");\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(errorCause->GetAdditionalInformationLength() == 6);\n\n\t\tstd::string additionalInfo(\n\t\t  reinterpret_cast<const char*>(errorCause->GetAdditionalInformation()),\n\t\t  errorCause->GetAdditionalInformationLength());\n\n\t\tREQUIRE(additionalInfo == \"iñaki\");\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::ProtocolViolationErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasAdditionalInformation() == true);\n\t\tREQUIRE(parsedErrorCause->GetAdditionalInformationLength() == 6);\n\n\t\tadditionalInfo = std::string(\n\t\t  reinterpret_cast<const char*>(parsedErrorCause->GetAdditionalInformation()),\n\t\t  parsedErrorCause->GetAdditionalInformationLength());\n\n\t\tREQUIRE(additionalInfo == \"iñaki\");\n\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetAdditionalInformation()[6] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetAdditionalInformation()[7] == 0x00);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestRestartOfAnAssociationWithNewAddressesErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Restart of an Association with New Addresses Error Cause (11)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"RestartOfAnAssociationWithNewAddressesErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:11 (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES), Length: 11\n\t\t\t0x00, 0x0B, 0x00, 0x0B,\n\t\t\t// New Address TLVs: 0x1234567890AB\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// 1 byte of padding.\n\t\t\t0x90, 0xAB, 0xCD, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause =\n\t\t  RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 7);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 7);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvsLength() == 7);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[0] == 0x12);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[1] == 0x34);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[2] == 0x56);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[3] == 0x78);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[4] == 0x90);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[5] == 0xAB);\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedErrorCause->GetNewAddressTlvs()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"RestartOfAnAssociationWithNewAddressesErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t//  NewAddressTlvs: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse(\n\t\t  buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:11 (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES), Length: 7\n\t\t\t0x00, 0x0B, 0x00, 0x07,\n\t\t\t//  NewAddressTlvs: 0x123456 (missing padding byte)\n\t\t\t0x12, 0x34, 0x56,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse(\n\t\t  buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"RestartOfAnAssociationWithNewAddressesErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == false);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\terrorCause->SetNewAddressTlvs(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(errorCause->GetLength() == 3004);\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 3000);\n\n\t\terrorCause->SetNewAddressTlvs(nullptr, 0);\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == false);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 0);\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetNewAddressTlvs(sctpCommon::DataBuffer, 6);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(errorCause->GetNewAddressTlvsLength() == 6);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x00);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x01);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x02);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x03);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x04);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasNewAddressTlvs() == true);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvsLength() == 6);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[1] == 0x01);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[2] == 0x02);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[3] == 0x03);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[4] == 0x04);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[6] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetNewAddressTlvs()[7] == 0x00);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestStaleCookieErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Stale Cookie Error Cause (3)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"StaleCookieErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:3 (STALE_COOKIE), Length: 8\n\t\t\t0x00, 0x03, 0x00, 0x08,\n\t\t\t// Measure of Staleness: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::StaleCookieErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetMeasureOfStaleness() == 987654321);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetMeasureOfStaleness() == 987654321);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->GetMeasureOfStaleness() == 987654321);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"StaleCookieErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Measure of Staleness: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:3 (STALE_COOKIE), Length: 7\n\t\t\t0x00, 0x03, 0x00, 0x07,\n\t\t\t// Measure of Staleness: 987654321\n\t\t\t0x3A, 0xDE, 0x68,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Code:3 (STALE_COOKIE), Length: 9\n\t\t\t0x00, 0x03, 0x00, 0x09,\n\t\t\t// Measure of Staleness: 987654321\n\t\t\t0x3A, 0xDE, 0x68, 0xB1,\n\t\t\t0xEE\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Code:3 (STALE_COOKIE), Length: 8\n\t\t\t0x00, 0x03, 0x00, 0x08,\n\t\t\t// Measure of Staleness (last byte missing)\n\t\t\t0x3A, 0xDE, 0x68\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"StaleCookieErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::StaleCookieErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetMeasureOfStaleness() == 0);\n\n\t\t/* Modify it. */\n\n\t\terrorCause->SetMeasureOfStaleness(666666);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->GetMeasureOfStaleness() == 666666);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause =\n\t\t  RTC::SCTP::StaleCookieErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->GetMeasureOfStaleness() == 666666);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestUnknownErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unknown Error Cause\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnknownErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 11\n\t\t\t0x03, 0xE7, 0x00, 0x0B,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding\n\t\t\t0x89, 0xAB, 0xCD, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xEE\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::UnknownErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(999),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(errorCause->HasUnknownValue() == true);\n\t\tREQUIRE(errorCause->GetUnknownValueLength() == 7);\n\t\tREQUIRE(errorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(errorCause->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(errorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(errorCause->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(errorCause->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(errorCause->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(errorCause->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetUnknownValue()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(999),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(errorCause->HasUnknownValue() == true);\n\t\tREQUIRE(errorCause->GetUnknownValueLength() == 7);\n\t\tREQUIRE(errorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(errorCause->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(errorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(errorCause->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(errorCause->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(errorCause->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(errorCause->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetUnknownValue()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*causeCode*/ static_cast<RTC::SCTP::ErrorCause::ErrorCauseCode>(999),\n\t\t  /*unknownCode*/ true);\n\n\t\tREQUIRE(clonedErrorCause->HasUnknownValue() == true);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValueLength() == 7);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedErrorCause->GetUnknownValue()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"UnknownErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:49159 (UNKNOWN), Length: 3\n\t\t\t0xC0, 0x07, 0x00, 0x03,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding\n\t\t\t0x89, 0xAB, 0xCD, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnknownErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:49159 (UNKNOWN), Length: 11\n\t\t\t0xC0, 0x07, 0x00, 0x0B,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding (missing)\n\t\t\t0x89, 0xAB, 0xCD\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnknownErrorCause::Parse(buffer2, sizeof(buffer2)));\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedChunkTypeErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unrecognized Chunk Type Error Cause (6)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnrecognizedChunkTypeErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:6 (UNRECOGNIZED_CHUNK_TYPE), Length: 10\n\t\t\t0x00, 0x06, 0x00, 0x0A,\n\t\t\t// Unrecognized Chunk: 0x1234567890AB\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// 2 bytes of padding.\n\t\t\t0x90, 0xAB, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 6);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 6);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunkLength() == 6);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[0] == 0x12);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[1] == 0x34);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[2] == 0x56);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[3] == 0x78);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[4] == 0x90);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[5] == 0xAB);\n\t\t// These should be padding.\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[6] == 0x00);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedChunk()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"UnrecognizedChunkTypeErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Unrecognized Chunk: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:6 (UNRECOGNIZED_CHUNK_TYPE), Length: 7\n\t\t\t0x00, 0x06, 0x00, 0x07,\n\t\t\t// Unrecognized Chunk: 0x123456 (missing padding byte)\n\t\t\t0x12, 0x34, 0x56,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"UnrecognizedChunkTypeErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == false);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\terrorCause->SetUnrecognizedChunk(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(errorCause->GetLength() == 3004);\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 3000);\n\n\t\terrorCause->SetUnrecognizedChunk(nullptr, 0);\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == false);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 0);\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetUnrecognizedChunk(sctpCommon::DataBuffer, 6);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunkLength() == 6);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x01);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x02);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x03);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x04);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasUnrecognizedChunk() == true);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunkLength() == 6);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[1] == 0x01);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[2] == 0x02);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[3] == 0x03);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[4] == 0x04);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[6] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedChunk()[7] == 0x00);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedParametersErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unrecognized Parameters Error Cause (8)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnrecognizedParametersErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:8 (UNRECOGNIZED_PARAMETERS), Length: 11\n\t\t\t0x00, 0x08, 0x00, 0x0B,\n\t\t\t// Unrecognized Parameters: 0x1234567890ABCD\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// 1 byte of padding.\n\t\t\t0x90, 0xAB, 0xCD, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 7);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 7);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x90);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0xAB);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParametersLength() == 7);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[0] == 0x12);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[1] == 0x34);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[2] == 0x56);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[3] == 0x78);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[4] == 0x90);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[5] == 0xAB);\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedErrorCause->GetUnrecognizedParameters()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"UnrecognizedParametersErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == false);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\terrorCause->SetUnrecognizedParameters(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(errorCause->GetLength() == 3004);\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 3000);\n\n\t\terrorCause->SetUnrecognizedParameters(nullptr, 0);\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == false);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 0);\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetUnrecognizedParameters(sctpCommon::DataBuffer, 6);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(errorCause->GetUnrecognizedParametersLength() == 6);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x01);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x02);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x03);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x04);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasUnrecognizedParameters() == true);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParametersLength() == 6);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[1] == 0x01);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[2] == 0x02);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[3] == 0x03);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[4] == 0x04);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[6] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnrecognizedParameters()[7] == 0x00);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestUnresolvableAddressErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unresolvable Address Error Cause (5)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnresolvableAddressErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:5 (UNRESOLVABLE_ADDRESS), Length: 9\n\t\t\t0x00, 0x05, 0x00, 0x09,\n\t\t\t// Unresolvable Address: 0x1234567890\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t\t// 3 bytes of padding.\n\t\t\t0x90, 0x00, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 5);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x90);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 5);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x12);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x34);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x56);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x78);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x90);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddressLength() == 5);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[0] == 0x12);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[1] == 0x34);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[2] == 0x56);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[3] == 0x78);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[4] == 0x90);\n\t\t// These should be padding.\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[5] == 0x00);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[6] == 0x00);\n\t\tREQUIRE(clonedErrorCause->GetUnresolvableAddress()[7] == 0x00);\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"UnresolvableAddressErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Unresolvable Address: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:5 (UNRESOLVABLE_ADDRESS), Length: 7\n\t\t\t0x00, 0x05, 0x00, 0x07,\n\t\t\t// Unresolvable Address: 0x123456 (missing padding byte)\n\t\t\t0x12, 0x34, 0x56,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"UnresolvableAddressErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::UnresolvableAddressErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == false);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\terrorCause->SetUnresolvableAddress(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(errorCause->GetLength() == 3004);\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 3000);\n\n\t\terrorCause->SetUnresolvableAddress(nullptr, 0);\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == false);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 0);\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetUnresolvableAddress(sctpCommon::DataBuffer, 6);\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(errorCause->GetUnresolvableAddressLength() == 6);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x01);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x02);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x03);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x04);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00);\n\t\tREQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::UnresolvableAddressErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasUnresolvableAddress() == true);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddressLength() == 6);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[0] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[1] == 0x01);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[2] == 0x02);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[3] == 0x03);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[4] == 0x04);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[5] == 0x05);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[6] == 0x00);\n\t\tREQUIRE(parsedErrorCause->GetUnresolvableAddress()[7] == 0x00);\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/errorCauses/TestUserInitiatedAbortErrorCause.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/ErrorCause.hpp\"\n#include \"RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"User-Initiated Abort Error Cause (12)\", \"[serializable][sctp][errorcause]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UserInitiatedAbortErrorCause::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Code:12 (USER_INITIATED_ABORT), Length: 10\n\t\t\t0x00, 0x0C, 0x00, 0x0A,\n\t\t\t// Upper Layer Abort Reason: \"I DIE!\"\n\t\t\t0x49, 0x20, 0x44, 0x49,\n\t\t\t// 2 bytes of padding.\n\t\t\t0x45, 0x21, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* errorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason() == \"I DIE!\");\n\n\t\t/* Serialize it. */\n\n\t\terrorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason() == \"I DIE!\");\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedErrorCause =\n\t\t  errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ clonedErrorCause,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(clonedErrorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(clonedErrorCause->GetUpperLayerAbortReason() == \"I DIE!\");\n\n\t\tdelete clonedErrorCause;\n\t}\n\n\tSECTION(\"UserInitiatedAbortErrorCause::Parse() fails\")\n\t{\n\t\t// Wrong code.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Code:999 (UNKNOWN), Length: 8\n\t\t\t0x03, 0xE7, 0x00, 0x08,\n\t\t\t// Upper Layer Abort Reason: 0x12345678\n\t\t\t0x12, 0x34, 0x56, 0x78,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Code:12 (USER_INITIATED_ABORT), Length: 7\n\t\t\t0x00, 0x0C, 0x00, 0x07,\n\t\t\t// Upper Layer Abort Reason: 0x123456 (missing padding byte)\n\t\t\t0x12, 0x34, 0x56,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"UserInitiatedAbortErrorCause::Factory() succeeds\")\n\t{\n\t\tauto* errorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == false);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason().empty());\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works. This is 17 bytes long.\n\t\terrorCause->SetUpperLayerAbortReason(\"I'm dying! ☺️\");\n\n\t\tREQUIRE(errorCause->GetLength() == 24);\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason() == \"I'm dying! ☺️\");\n\n\t\terrorCause->SetUpperLayerAbortReason(\"\");\n\n\t\tREQUIRE(errorCause->GetLength() == 4);\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == false);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason().empty());\n\n\t\t// 6 bytes + 2 bytes of padding.\n\t\terrorCause->SetUpperLayerAbortReason(\"go go go\");\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ errorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(errorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(errorCause->GetUpperLayerAbortReason() == \"go go go\");\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedErrorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Parse(\n\t\t  errorCause->GetBuffer(), errorCause->GetLength());\n\n\t\tdelete errorCause;\n\n\t\tCHECK_SCTP_ERROR_CAUSE(\n\t\t  /*errorCause*/ parsedErrorCause,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT,\n\t\t  /*unknownCode*/ false);\n\n\t\tREQUIRE(parsedErrorCause->HasUpperLayerAbortReason() == true);\n\t\tREQUIRE(parsedErrorCause->GetUpperLayerAbortReason() == \"go go go\");\n\n\t\tdelete parsedErrorCause;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestAddIncomingStreamsRequestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Add Incoming Streams Request Parameter (18)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"AddIncomingStreamsRequestParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:18 (ADD_INCOMING_STREAMS_REQUEST), Length: 12\n\t\t\t0x00, 0x12, 0x00, 0x0C,\n\t\t\t// Re-configuration Request Sequence Number: 666777888\n\t\t\t0x27, 0xBE, 0x39, 0x20,\n\t\t\t// Number of new streams: 1024\n\t\t\t0x04, 0x00, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 1024);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 1024);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(clonedParameter->GetNumberOfNewStreams() == 1024);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"AddIncomingStreamsRequestParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 0);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationRequestSequenceNumber(12345678);\n\t\tparameter->SetNumberOfNewStreams(2048);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 2048);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\t\tREQUIRE(parsedParameter->GetNumberOfNewStreams() == 2048);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestAddOutgoingStreamsRequestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Add Outgoing Streams Request Parameter (17)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"AddOutgoingStreamsRequestParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:17 (ADD_OUTGOING_STREAMS_REQUEST), Length: 12\n\t\t\t0x00, 0x11, 0x00, 0x0C,\n\t\t\t// Re-configuration Request Sequence Number: 666777888\n\t\t\t0x27, 0xBE, 0x39, 0x20,\n\t\t\t// Number of new streams: 1024\n\t\t\t0x04, 0x00, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 1024);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 1024);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\t\tREQUIRE(clonedParameter->GetNumberOfNewStreams() == 1024);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"AddOutgoingStreamsRequestParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 0);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationRequestSequenceNumber(12345678);\n\t\tparameter->SetNumberOfNewStreams(2048);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\t\tREQUIRE(parameter->GetNumberOfNewStreams() == 2048);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\t\tREQUIRE(parsedParameter->GetNumberOfNewStreams() == 2048);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestCookiePreservativeParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Cookie Preservative Parameter (9)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"CookiePreservativeParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:9 (COOKIE_PRESERVATIVE), Length: 8\n\t\t\t0x00, 0x09, 0x00, 0x08,\n\t\t\t// Suggested Cookie Life-Span Increment: 4278194466\n\t\t\t0xFF, 0x00, 0x11, 0x22,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::CookiePreservativeParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetLifeSpanIncrement() == 4278194466);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetLifeSpanIncrement() == 4278194466);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetLifeSpanIncrement() == 4278194466);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"CookiePreservativeParameter::Parse() fails\")\n\t{\n\t\t// Wrong type.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:9 (IPV6_ADDRESS), Length: 8\n\t\t\t0x00, 0x06, 0x00, 0x08,\n\t\t\t// Suggested Cookie Life-Span Increment: 4278194466\n\t\t\t0xFF, 0x00, 0x11, 0x22,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:9 (COOKIE_PRESERVATIVE), Length: 7\n\t\t\t0x00, 0x09, 0x00, 0x07,\n\t\t\t// Suggested Cookie Life-Span Increment: 4278194466\n\t\t\t0xFF, 0x00, 0x11, 0x22,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Type:9 (COOKIE_PRESERVATIVE), Length: 9\n\t\t\t0x00, 0x09, 0x00, 0x09,\n\t\t\t// Suggested Cookie Life-Span Increment: 4278194466\n\t\t\t0xFF, 0x00, 0x11, 0x22,\n\t\t\t0x69\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 8\n\t\t\t0x00, 0x05, 0x00, 0x08,\n\t\t\t// Suggested Cookie Life-Span Increment (wrong length)\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"CookiePreservativeParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::CookiePreservativeParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetLifeSpanIncrement() == 0);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetLifeSpanIncrement(88776655);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetLifeSpanIncrement() == 88776655);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::CookiePreservativeParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetLifeSpanIncrement() == 88776655);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestForwardTsnSupportedParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Forward-TSN-Supported Parameter (32769)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ForwardTsnSupportedParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:49152 (FORWARD_TSN_SUPPORTED), Length: 4\n\t\t\t0xC0, 0x00, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::ForwardTsnSupportedParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"ForwardTsnSupportedParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::ForwardTsnSupportedParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::ForwardTsnSupportedParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4,\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestHeartbeatInfoParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Heartbeat Info Parameter (1)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"HeartbeatInfoParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:1 (HEARBEAT_INFO), Length: 11\n\t\t\t0x00, 0x01, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::HeartbeatInfoParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 7);\n\t\tREQUIRE(parameter->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetInfo()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 7);\n\t\tREQUIRE(parameter->GetInfo()[0] == 0x11);\n\t\tREQUIRE(parameter->GetInfo()[1] == 0x22);\n\t\tREQUIRE(parameter->GetInfo()[2] == 0x33);\n\t\tREQUIRE(parameter->GetInfo()[3] == 0x44);\n\t\tREQUIRE(parameter->GetInfo()[4] == 0x55);\n\t\tREQUIRE(parameter->GetInfo()[5] == 0x66);\n\t\tREQUIRE(parameter->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetInfo()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->HasInfo() == true);\n\t\tREQUIRE(clonedParameter->GetInfoLength() == 7);\n\t\tREQUIRE(clonedParameter->GetInfo()[0] == 0x11);\n\t\tREQUIRE(clonedParameter->GetInfo()[1] == 0x22);\n\t\tREQUIRE(clonedParameter->GetInfo()[2] == 0x33);\n\t\tREQUIRE(clonedParameter->GetInfo()[3] == 0x44);\n\t\tREQUIRE(clonedParameter->GetInfo()[4] == 0x55);\n\t\tREQUIRE(clonedParameter->GetInfo()[5] == 0x66);\n\t\tREQUIRE(clonedParameter->GetInfo()[6] == 0x77);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedParameter->GetInfo()[7] == 0x00);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"HeartbeatInfoParameter::Parse() fails\")\n\t{\n\t\t// Wrong type.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 8\n\t\t\t0x00, 0x06, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:1 (HEARBEAT_INFO), Length: 3\n\t\t\t0x00, 0x01, 0x00, 0x03,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding\n\t\t\t0x55, 0x66, 0x77, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Type:1 (HEARBEAT_INFO), Length: 11\n\t\t\t0x00, 0x01, 0x00, 0x0B,\n\t\t\t// Heartbeat Information (7 bytes): 0x11223344556677\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// 1 byte of padding (missing)\n\t\t\t0x55, 0x66, 0x77\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"HeartbeatInfoParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::HeartbeatInfoParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasInfo() == false);\n\t\tREQUIRE(parameter->GetInfoLength() == 0);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the value works.\n\t\tparameter->SetInfo(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(parameter->GetLength() == 3004);\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 3000);\n\n\t\tparameter->SetInfo(nullptr, 0);\n\n\t\tREQUIRE(parameter->GetLength() == 4);\n\t\tREQUIRE(parameter->HasInfo() == false);\n\t\tREQUIRE(parameter->GetInfoLength() == 0);\n\n\t\tparameter->SetInfo(sctpCommon::DataBuffer, 2);\n\n\t\tREQUIRE(parameter->GetLength() == 8);\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 2);\n\n\t\tparameter->SetInfo(sctpCommon::DataBuffer + 2000, 2000);\n\n\t\tREQUIRE(parameter->GetLength() == 2004);\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 2000);\n\n\t\t// Info length is 5 so 3 bytes of padding will be added.\n\t\tparameter->SetInfo(sctpCommon::DataBuffer, 5);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasInfo() == true);\n\t\tREQUIRE(parameter->GetInfoLength() == 5);\n\t\tREQUIRE(parameter->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parameter->GetInfo()[1] == 0x01);\n\t\tREQUIRE(parameter->GetInfo()[2] == 0x02);\n\t\tREQUIRE(parameter->GetInfo()[3] == 0x03);\n\t\tREQUIRE(parameter->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter->GetInfo()[5] == 0x00);\n\t\tREQUIRE(parameter->GetInfo()[6] == 0x00);\n\t\tREQUIRE(parameter->GetInfo()[7] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::HeartbeatInfoParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->HasInfo() == true);\n\t\tREQUIRE(parsedParameter->GetInfoLength() == 5);\n\t\tREQUIRE(parsedParameter->GetInfo()[0] == 0x00);\n\t\tREQUIRE(parsedParameter->GetInfo()[1] == 0x01);\n\t\tREQUIRE(parsedParameter->GetInfo()[2] == 0x02);\n\t\tREQUIRE(parsedParameter->GetInfo()[3] == 0x03);\n\t\tREQUIRE(parsedParameter->GetInfo()[4] == 0x04);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter->GetInfo()[5] == 0x00);\n\t\tREQUIRE(parsedParameter->GetInfo()[6] == 0x00);\n\t\tREQUIRE(parsedParameter->GetInfo()[7] == 0x00);\n\n\t\tdelete parsedParameter;\n\t}\n\n\tSECTION(\"HeartbeatInfoParameter::SetInfo() throws if infoLength is too big\")\n\t{\n\t\tauto* parameter = RTC::SCTP::HeartbeatInfoParameter::Factory(\n\t\t  sctpCommon::ThrowBuffer, sizeof(sctpCommon::ThrowBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::ThrowBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE_THROWS_AS(parameter->SetInfo(sctpCommon::ThrowBuffer, 65535), MediaSoupError);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::ThrowBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tdelete parameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestIPv4AddressParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"IPv4 Adress Parameter (5)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"IPv4AddressParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 8\n\t\t\t0x00, 0x05, 0x00, 0x08,\n\t\t\t// IPv4 Address: \"1.2.3.4\"\n\t\t\t0x01, 0x02, 0x03, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::IPv4AddressParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv4Address()[0] == 0x01);\n\t\tREQUIRE(parameter->GetIPv4Address()[1] == 0x02);\n\t\tREQUIRE(parameter->GetIPv4Address()[2] == 0x03);\n\t\tREQUIRE(parameter->GetIPv4Address()[3] == 0x04);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv4Address()[0] == 0x01);\n\t\tREQUIRE(parameter->GetIPv4Address()[1] == 0x02);\n\t\tREQUIRE(parameter->GetIPv4Address()[2] == 0x03);\n\t\tREQUIRE(parameter->GetIPv4Address()[3] == 0x04);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetIPv4Address()[0] == 0x01);\n\t\tREQUIRE(clonedParameter->GetIPv4Address()[1] == 0x02);\n\t\tREQUIRE(clonedParameter->GetIPv4Address()[2] == 0x03);\n\t\tREQUIRE(clonedParameter->GetIPv4Address()[3] == 0x04);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"IPv4AddressParameter::Parse() fails\")\n\t{\n\t\t// Wrong type.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 8\n\t\t\t0x00, 0x06, 0x00, 0x08,\n\t\t\t// IPv4 Address: 0xAABBCCDD\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 7\n\t\t\t0x00, 0x05, 0x00, 0x07,\n\t\t\t// IPv4 Address: 0xAABBCC\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 9\n\t\t\t0x00, 0x05, 0x00, 0x09,\n\t\t\t// IPv4 Address: 0xAABBCCDD\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xEE\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 8\n\t\t\t0x00, 0x05, 0x00, 0x08,\n\t\t\t// IPv4 Address (wrong length)\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"IPv4AddressParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::IPv4AddressParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv4Address()[0] == 0x00);\n\t\tREQUIRE(parameter->GetIPv4Address()[1] == 0x00);\n\t\tREQUIRE(parameter->GetIPv4Address()[2] == 0x00);\n\t\tREQUIRE(parameter->GetIPv4Address()[3] == 0x00);\n\n\t\t/* Modify it. */\n\n\t\t// 11.22.33.44 IPv4 in network order.\n\t\tuint8_t ipBuffer[] = { 0x0B, 0x16, 0x21, 0x2C };\n\n\t\tparameter->SetIPv4Address(ipBuffer);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv4Address()[0] == 0x0B);\n\t\tREQUIRE(parameter->GetIPv4Address()[1] == 0x16);\n\t\tREQUIRE(parameter->GetIPv4Address()[2] == 0x21);\n\t\tREQUIRE(parameter->GetIPv4Address()[3] == 0x2C);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::IPv4AddressParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetIPv4Address()[0] == 0x0B);\n\t\tREQUIRE(parsedParameter->GetIPv4Address()[1] == 0x16);\n\t\tREQUIRE(parsedParameter->GetIPv4Address()[2] == 0x21);\n\t\tREQUIRE(parsedParameter->GetIPv4Address()[3] == 0x2C);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestIPv6AddressParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"IPv6 Adress Parameter (6)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"IPv6AddressParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 20\n\t\t\t0x00, 0x06, 0x00, 0x14,\n\t\t\t// IPv6 Address: \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73, 0x34,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::IPv6AddressParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(parameter->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(parameter->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(parameter->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(parameter->GetIPv6Address()[15] == 0x34);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(parameter->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(parameter->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(parameter->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(parameter->GetIPv6Address()[15] == 0x34);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetIPv6Address()[0] == 0x20);\n\t\tREQUIRE(clonedParameter->GetIPv6Address()[1] == 0x01);\n\t\tREQUIRE(clonedParameter->GetIPv6Address()[2] == 0x0D);\n\t\tREQUIRE(clonedParameter->GetIPv6Address()[3] == 0xB8);\n\t\tREQUIRE(clonedParameter->GetIPv6Address()[15] == 0x34);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"IPv6AddressParameter::Parse() fails\")\n\t{\n\t\t// Wrong type.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:5 (IPV4_ADDRESS), Length: 20\n\t\t\t0x00, 0x05, 0x00, 0x14,\n\t\t\t// IPv6 Address: \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73, 0x34,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 19\n\t\t\t0x00, 0x06, 0x00, 0x14,\n\t\t\t// IPv6 Address: \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer2, sizeof(buffer2)));\n\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer3[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 21\n\t\t\t0x00, 0x06, 0x00, 0x15,\n\t\t\t// IPv6 Address: \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73, 0x34,\n\t\t\t0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer3, sizeof(buffer3)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer4[] =\n\t\t{\n\t\t\t// Type:6 (IPV6_ADDRESS), Length: 20\n\t\t\t0x00, 0x06, 0x00, 0x14,\n\t\t\t// IPv6 Address (wrong length)\n\t\t\t0x20, 0x01, 0x0D, 0xB8,\n\t\t\t0x85, 0xA3, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x8A, 0x2E,\n\t\t\t0x03, 0x70, 0x73\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer4, sizeof(buffer4)));\n\t}\n\n\tSECTION(\"IPv6AddressParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::IPv6AddressParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv6Address()[0] == 0x00);\n\t\tREQUIRE(parameter->GetIPv6Address()[1] == 0x00);\n\t\tREQUIRE(parameter->GetIPv6Address()[2] == 0x00);\n\t\tREQUIRE(parameter->GetIPv6Address()[3] == 0x00);\n\t\tREQUIRE(parameter->GetIPv6Address()[15] == 0x00);\n\n\t\t/* Modify it. */\n\n\t\t// 2345:0425:2CA1:0000:0000:0567:5673:23b5 IPv6 in network order.\n\t\tuint8_t ipBuffer[] = { 0x23, 0x45, 0x04, 0x25, 0x2C, 0xA1, 0x00, 0x00,\n\t\t\t                     0x00, 0x00, 0x05, 0x67, 0x56, 0x73, 0x23, 0xB5 };\n\n\t\tparameter->SetIPv6Address(ipBuffer);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetIPv6Address()[0] == 0x23);\n\t\tREQUIRE(parameter->GetIPv6Address()[1] == 0x45);\n\t\tREQUIRE(parameter->GetIPv6Address()[2] == 0x04);\n\t\tREQUIRE(parameter->GetIPv6Address()[3] == 0x25);\n\t\tREQUIRE(parameter->GetIPv6Address()[15] == 0xB5);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::IPv6AddressParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetIPv6Address()[0] == 0x23);\n\t\tREQUIRE(parsedParameter->GetIPv6Address()[1] == 0x45);\n\t\tREQUIRE(parsedParameter->GetIPv6Address()[2] == 0x04);\n\t\tREQUIRE(parsedParameter->GetIPv6Address()[3] == 0x25);\n\t\tREQUIRE(parsedParameter->GetIPv6Address()[15] == 0xB5);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestIncomingSsnResetRequestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"Incoming SSN Reset Request Parameter (14)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"IncomingSsnResetRequestParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:14 (INCOMING_SSN_RESET_REQUEST), Length: 14\n\t\t\t0x00, 0x0E, 0x00, 0x0E,\n\t\t\t// Re-configuration Request Sequence Number: 0x11223344\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Stream 1: 0x5001, Stream 2: 0x5002\n\t\t\t0x50, 0x01, 0x50, 0x02,\n\t\t\t// Stream 3: 0x5003, 2 bytes of padding\n\t\t\t0x50, 0x03, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::IncomingSsnResetRequestParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\n\t\tconst std::vector<uint16_t> expectedStreamIds{\n\t\t\t{ 0x5001, 0x5002, 0x5003 },\n\t\t};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(clonedParameter->GetStreamIds() == expectedStreamIds);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"IncomingSsnResetRequestParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::IncomingSsnResetRequestParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0);\n\n\t\tstd::vector<uint16_t> expectedStreamIds{};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationRequestSequenceNumber(111000);\n\t\tparameter->AddStreamId(4444);\n\t\tparameter->AddStreamId(4445);\n\t\tparameter->AddStreamId(4446);\n\t\tparameter->AddStreamId(4447);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 111000);\n\n\t\texpectedStreamIds = {\n\t\t\t{ 4444, 4445, 4446, 4447 },\n\t\t};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::IncomingSsnResetRequestParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 16,\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 111000);\n\t\tREQUIRE(parsedParameter->GetStreamIds() == expectedStreamIds);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestOutgoingSsnResetRequestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n#include <vector>\n\nSCENARIO(\"Outgoing SSN Reset Request Parameter (13)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"OutgoingSsnResetRequestParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:13 (OUTGOING_SSN_RESET_REQUEST), Length: 22\n\t\t\t0x00, 0x0D, 0x00, 0x16,\n\t\t\t// Re-configuration Request Sequence Number: 0x11223344\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Re-configuration Response Sequence Number: 0x55667788\n\t\t\t0x55, 0x66, 0x77, 0x88,\n\t\t\t// Sender's Last Assigned TSN: 0xAABBCCDD\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t// Stream 1: 0x5001, Stream 2: 0x5002\n\t\t\t0x50, 0x01, 0x50, 0x02,\n\t\t\t// Stream 3: 0x5003, 2 bytes of padding\n\t\t\t0x50, 0x03, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(parameter->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\n\t\tconst std::vector<uint16_t> expectedStreamIds{\n\t\t\t{ 0x5001, 0x5002, 0x5003 },\n\t\t};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(parameter->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 24,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 0x11223344);\n\t\tREQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 0x55667788);\n\t\tREQUIRE(clonedParameter->GetSenderLastAssignedTsn() == 0xAABBCCDD);\n\t\tREQUIRE(clonedParameter->GetStreamIds() == expectedStreamIds);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"OutgoingSsnResetRequestParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0);\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0);\n\t\tREQUIRE(parameter->GetSenderLastAssignedTsn() == 0);\n\n\t\tstd::vector<uint16_t> expectedStreamIds{};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationRequestSequenceNumber(111000);\n\t\tparameter->SetReconfigurationResponseSequenceNumber(222000);\n\t\tparameter->SetSenderLastAssignedTsn(333000);\n\t\tparameter->AddStreamId(4444);\n\t\tparameter->AddStreamId(4445);\n\t\tparameter->AddStreamId(4446);\n\t\tparameter->AddStreamId(4447);\n\t\tparameter->AddStreamId(4448);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 28,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 111000);\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 222000);\n\t\tREQUIRE(parameter->GetSenderLastAssignedTsn() == 333000);\n\n\t\texpectedStreamIds = {\n\t\t\t{ 4444, 4445, 4446, 4447, 4448 },\n\t\t};\n\n\t\tREQUIRE(parameter->GetStreamIds() == expectedStreamIds);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 28,\n\t\t  /*length*/ 28,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 111000);\n\t\tREQUIRE(parsedParameter->GetReconfigurationResponseSequenceNumber() == 222000);\n\t\tREQUIRE(parsedParameter->GetSenderLastAssignedTsn() == 333000);\n\t\tREQUIRE(parsedParameter->GetStreamIds() == expectedStreamIds);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestReconfigurationResponseParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Re-configuration Response Parameter (16)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ReconfigurationResponseParameter::Parse() with Sender's and Receiver's Next TSN succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:16 (RECONFIGURATION_RESPONSE), Length: 20\n\t\t\t0x00, 0x10, 0x00, 0x14,\n\t\t\t// Re-configuration Request Sequence Number: 287454020\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Result: SUCCESS_PERFORMED\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t// Sender's Next TSN: 1111111111\n\t\t\t0x42, 0x3A, 0x35, 0xC7,\n\t\t\t// Receiver's Next TSN: 1111111111\n\t\t\t0x84, 0x74, 0x6B, 0x8E,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 287454020);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED);\n\t\tREQUIRE(parameter->HasNextTsns() == true);\n\t\tREQUIRE(parameter->GetSenderNextTsn() == 1111111111);\n\t\tREQUIRE(parameter->GetReceiverNextTsn() == 2222222222);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 287454020);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED);\n\t\tREQUIRE(parameter->HasNextTsns() == true);\n\t\tREQUIRE(parameter->GetSenderNextTsn() == 1111111111);\n\t\tREQUIRE(parameter->GetReceiverNextTsn() == 2222222222);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 287454020);\n\t\tREQUIRE(\n\t\t  clonedParameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED);\n\t\tREQUIRE(clonedParameter->HasNextTsns() == true);\n\t\tREQUIRE(clonedParameter->GetSenderNextTsn() == 1111111111);\n\t\tREQUIRE(clonedParameter->GetReceiverNextTsn() == 2222222222);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"ReconfigurationResponseParameter::Parse() without Sender's and Receiver's Next TSN succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:16 (RECONFIGURATION_RESPONSE), Length: 12\n\t\t\t0x00, 0x10, 0x00, 0x0C,\n\t\t\t// Re-configuration Request Sequence Number: 3333333333\n\t\t\t0xC6, 0xAE, 0xA1, 0x55,\n\t\t\t// Result: ERROR_REQUEST_ALREADY_IN_PROGRESS\n\t\t\t0x00, 0x00, 0x00, 0x04,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 3333333333);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS);\n\t\tREQUIRE(parameter->HasNextTsns() == false);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 3333333333);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS);\n\t\tREQUIRE(parameter->HasNextTsns() == false);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 3333333333);\n\t\tREQUIRE(\n\t\t  clonedParameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS);\n\t\tREQUIRE(clonedParameter->HasNextTsns() == false);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"ReconfigurationResponseParameter::Parse() fails\")\n\t{\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:16 (RECONFIGURATION_RESPONSE), Length: 16 (should be 12 or 20)\n\t\t\t0x00, 0x10, 0x00, 0x10,\n\t\t\t// Re-configuration Request Sequence Number: 287454020\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Result: SUCCESS_PERFORMED\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t// Sender's Next TSN: 1111111111\n\t\t\t0x42, 0x3A, 0x35, 0xC7,\n\t\t\t// Receiver's Next TSN: 1111111111\n\t\t\t0x84, 0x74, 0x6B, 0x8E,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:16 (RECONFIGURATION_RESPONSE), Length: 20\n\t\t\t0x00, 0x10, 0x00, 0x14,\n\t\t\t// Re-configuration Request Sequence Number: 287454020\n\t\t\t0x11, 0x22, 0x33, 0x44,\n\t\t\t// Result: SUCCESS_PERFORMED\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t// Sender's Next TSN: 1111111111\n\t\t\t0x42, 0x3A, 0x35, 0xC7,\n\t\t\t// Receiver's Next TSN (missing)\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer2, sizeof(buffer2)));\n\t}\n\n\tSECTION(\"ReconfigurationResponseParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() == static_cast<RTC::SCTP::ReconfigurationResponseParameter::Result>(0));\n\t\tREQUIRE(parameter->HasNextTsns() == false);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationResponseSequenceNumber(111000);\n\t\tparameter->SetResult(RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tparameter->SetNextTsns(100000000, 200000000);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 111000);\n\t\tREQUIRE(\n\t\t  parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tREQUIRE(parameter->HasNextTsns() == true);\n\t\tREQUIRE(parameter->GetSenderNextTsn() == 100000000);\n\t\tREQUIRE(parameter->GetReceiverNextTsn() == 200000000);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::ReconfigurationResponseParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 20,\n\t\t  /*length*/ 20,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationResponseSequenceNumber() == 111000);\n\t\tREQUIRE(\n\t\t  parsedParameter->GetResult() ==\n\t\t  RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS);\n\t\tREQUIRE(parsedParameter->HasNextTsns() == true);\n\t\tREQUIRE(parsedParameter->GetSenderNextTsn() == 100000000);\n\t\tREQUIRE(parsedParameter->GetReceiverNextTsn() == 200000000);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestSsnTsnResetRequestParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"SSN/TSN Reset Request Parameter (15)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"SsnTsnResetRequestParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:15 (SSN_TSN_RESET_REQUEST), Length: 8\n\t\t\t0x00, 0x0F, 0x00, 0x08,\n\t\t\t// Re-configuration Request Sequence Number: 666777888\n\t\t\t0x27, 0xBE, 0x39, 0x20,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::SsnTsnResetRequestParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"SsnTsnResetRequestParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::SsnTsnResetRequestParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetReconfigurationRequestSequenceNumber(12345678);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::SsnTsnResetRequestParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestStateCookieParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/association/NegotiatedCapabilities.hpp\"\n#include \"RTC/SCTP/association/StateCookie.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/StateCookieParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"State Cookie Parameter (7)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"StateCookieParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:7 (STATE_COOKIE), Length: 7\n\t\t\t0x00, 0x07, 0x00, 0x07,\n\t\t\t// Cookie: 0xDDCCEE, 1 byte of padding\n\t\t\t0xDD, 0xCC, 0xEE, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::StateCookieParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasCookie() == true);\n\t\tREQUIRE(parameter->GetCookieLength() == 3);\n\t\tREQUIRE(parameter->GetCookie()[0] == 0xDD);\n\t\tREQUIRE(parameter->GetCookie()[1] == 0xCC);\n\t\tREQUIRE(parameter->GetCookie()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetCookie()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasCookie() == true);\n\t\tREQUIRE(parameter->GetCookieLength() == 3);\n\t\tREQUIRE(parameter->GetCookie()[0] == 0xDD);\n\t\tREQUIRE(parameter->GetCookie()[1] == 0xCC);\n\t\tREQUIRE(parameter->GetCookie()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetCookie()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->HasCookie() == true);\n\t\tREQUIRE(clonedParameter->GetCookieLength() == 3);\n\t\tREQUIRE(clonedParameter->GetCookie()[0] == 0xDD);\n\t\tREQUIRE(clonedParameter->GetCookie()[1] == 0xCC);\n\t\tREQUIRE(clonedParameter->GetCookie()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedParameter->GetCookie()[3] == 0x00);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"StateCookieParameter::Factory() succeeds (1)\")\n\t{\n\t\tauto* parameter = RTC::SCTP::StateCookieParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the cookie works.\n\t\tparameter->SetCookie(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(parameter->GetLength() == 3004);\n\t\tREQUIRE(parameter->HasCookie() == true);\n\t\tREQUIRE(parameter->GetCookieLength() == 3000);\n\n\t\tparameter->SetCookie(nullptr, 0);\n\n\t\tREQUIRE(parameter->GetLength() == 4);\n\t\tREQUIRE(parameter->HasCookie() == false);\n\t\tREQUIRE(parameter->GetCookieLength() == 0);\n\n\t\t// 1 bytes + 3 bytes of padding. Note that first (and unique byte) is\n\t\t// sctpCommon::DataBuffer + 1 which is initialized to 0x0A.\n\t\tparameter->SetCookie(sctpCommon::DataBuffer + 10, 1);\n\n\t\tREQUIRE(parameter->HasCookie() == true);\n\t\tREQUIRE(parameter->GetCookieLength() == 1);\n\t\tREQUIRE(parameter->GetCookie()[0] == 0x0A);\n\t\t// These should be padding.\n\t\tREQUIRE(parameter->GetCookie()[1] == 0x00);\n\t\tREQUIRE(parameter->GetCookie()[2] == 0x00);\n\t\tREQUIRE(parameter->GetCookie()[3] == 0x00);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::StateCookieParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->HasCookie() == true);\n\t\tREQUIRE(parsedParameter->GetCookieLength() == 1);\n\t\tREQUIRE(parsedParameter->GetCookie()[0] == 0x0A);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter->GetCookie()[1] == 0x00);\n\t\tREQUIRE(parsedParameter->GetCookie()[2] == 0x00);\n\t\tREQUIRE(parsedParameter->GetCookie()[3] == 0x00);\n\n\t\tdelete parsedParameter;\n\t}\n\n\tSECTION(\"StateCookieParameter::Factory() succeeds (2)\")\n\t{\n\t\tauto* parameter = RTC::SCTP::StateCookieParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\t/* Modify it. */\n\n\t\t// Create a StateCookie.\n\t\tconst RTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = {\n\t\t\t.negotiatedMaxOutboundStreams = 62000,\n\t\t\t.negotiatedMaxInboundStreams  = 55555,\n\t\t\t.partialReliability           = true,\n\t\t\t.messageInterleaving          = true,\n\t\t\t.reConfig                     = true,\n\t\t\t.zeroChecksum                 = false\n\t\t};\n\n\t\t// Build the StateCookie in place within the StateCookieParameter.\n\t\tparameter->WriteStateCookieInPlace(\n\t\t  /*localVerificationTag*/ 6660666,\n\t\t  /*remoteVerificationTag*/ 9990999,\n\t\t  /*localInitialTsn*/ 1110111,\n\t\t  /*remoteInitialTsn*/ 2220222,\n\t\t  /*remoteAdvertisedReceiverWindowCredit*/ 999909999,\n\t\t  /*tieTag*/ 1111222233334444,\n\t\t  negotiatedCapabilities);\n\n\t\tREQUIRE(parameter->HasCookie() == true);\n\t\tREQUIRE(parameter->GetCookieLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::StateCookieParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 4 + RTC::SCTP::StateCookie::StateCookieLength,\n\t\t  /*length*/ 4 + RTC::SCTP::StateCookie::StateCookieLength,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->HasCookie() == true);\n\t\tREQUIRE(parsedParameter->GetCookieLength() == RTC::SCTP::StateCookie::StateCookieLength);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestSupportedAddressTypesParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Supported Address Types Parameter (12)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"SupportedAddressTypesParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:12 (SUPPORTED_ADDRESS_TYPES), Length: 10\n\t\t\t0x00, 0x0C, 0x00, 0x0A,\n\t\t\t// Address Type 1: 0x1001, Address Type 2: 0x2002\n\t\t\t0x10, 0x01, 0x20, 0x02,\n\t\t\t// Address Type 3: 0x3003, 2 bytes of padding\n\t\t\t0x30, 0x03, 0x00, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::SupportedAddressTypesParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(parameter->GetAddressTypeAt(0) == 0x1001);\n\t\tREQUIRE(parameter->GetAddressTypeAt(1) == 0x2002);\n\t\tREQUIRE(parameter->GetAddressTypeAt(2) == 0x3003);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(parameter->GetAddressTypeAt(0) == 0x1001);\n\t\tREQUIRE(parameter->GetAddressTypeAt(1) == 0x2002);\n\t\tREQUIRE(parameter->GetAddressTypeAt(2) == 0x3003);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(clonedParameter->GetAddressTypeAt(0) == 0x1001);\n\t\tREQUIRE(clonedParameter->GetAddressTypeAt(1) == 0x2002);\n\t\tREQUIRE(clonedParameter->GetAddressTypeAt(2) == 0x3003);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"SupportedAddressTypesParameter::Parse() fails\")\n\t{\n\t\t// Wrong Length field (not even).\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:12 (SUPPORTED_ADDRESS_TYPES), Length: 7\n\t\t\t0x00, 0x0C, 0x00, 0x0A,\n\t\t\t// Address Type 1: 0x1001, Address Type 2: 0x2002\n\t\t\t0x10, 0x01, 0x20, 0x02,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::SupportedAddressTypesParameter::Parse(buffer1, sizeof(buffer1)));\n\t}\n\n\tSECTION(\"SupportedAddressTypesParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::SupportedAddressTypesParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetNumberOfAddressTypes() == 0);\n\n\t\t/* Modify it. */\n\n\t\tparameter->AddAddressType(11111);\n\t\tparameter->AddAddressType(22222);\n\t\tparameter->AddAddressType(33333);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetNumberOfAddressTypes() == 3);\n\t\tREQUIRE(parameter->GetAddressTypeAt(0) == 11111);\n\t\tREQUIRE(parameter->GetAddressTypeAt(1) == 22222);\n\t\tREQUIRE(parameter->GetAddressTypeAt(2) == 33333);\n\n\t\tparameter->AddAddressType(44444);\n\t\tparameter->AddAddressType(55555);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->GetNumberOfAddressTypes() == 5);\n\t\tREQUIRE(parameter->GetAddressTypeAt(0) == 11111);\n\t\tREQUIRE(parameter->GetAddressTypeAt(1) == 22222);\n\t\tREQUIRE(parameter->GetAddressTypeAt(2) == 33333);\n\t\tREQUIRE(parameter->GetAddressTypeAt(3) == 44444);\n\t\tREQUIRE(parameter->GetAddressTypeAt(4) == 55555);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::SupportedAddressTypesParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 16,\n\t\t  /*length*/ 16,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->GetNumberOfAddressTypes() == 5);\n\t\tREQUIRE(parsedParameter->GetAddressTypeAt(0) == 11111);\n\t\tREQUIRE(parsedParameter->GetAddressTypeAt(1) == 22222);\n\t\tREQUIRE(parsedParameter->GetAddressTypeAt(2) == 33333);\n\t\tREQUIRE(parsedParameter->GetAddressTypeAt(3) == 44444);\n\t\tREQUIRE(parsedParameter->GetAddressTypeAt(4) == 55555);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestSupportedExtensionsParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\" // in worker/test/include/\n#include \"RTC/SCTP/packet/Chunk.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Supported Extensions Parameter (32776)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"SupportedExtensionsParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:32776 (SUPPORTED_EXTENSIONS), Length: 7\n\t\t\t0x80, 0x08, 0x00, 0x07,\n\t\t\t// Chunk Type 1: RE_CONFIG (0x82), Chunk Type 2: ECNE (0x0C),\n\t\t\t// Chunk Type 3: UNKNOWN (0x42), 1 byte of padding\n\t\t\t0x82, 0x0C, 0x42, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::SupportedExtensionsParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parameter->GetNumberOfChunkTypes() == 3);\n\t\tREQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->GetChunkTypeAt(2) == static_cast<RTC::SCTP::Chunk::ChunkType>(0x42));\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(0x42)) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parameter->GetNumberOfChunkTypes() == 3);\n\t\tREQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->GetChunkTypeAt(2) == static_cast<RTC::SCTP::Chunk::ChunkType>(0x42));\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(0x42)) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(clonedParameter->GetNumberOfChunkTypes() == 3);\n\t\tREQUIRE(clonedParameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(clonedParameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(clonedParameter->GetChunkTypeAt(2) == static_cast<RTC::SCTP::Chunk::ChunkType>(0x42));\n\t\tREQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true);\n\t\tREQUIRE(\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  clonedParameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(0x42)) == true);\n\t\tREQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"SupportedExtensionsParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::SupportedExtensionsParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parameter->GetNumberOfChunkTypes() == 0);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == false);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == false);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(0x42)) == false);\n\n\t\t/* Modify it. */\n\n\t\tparameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tparameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::CWR);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parameter->GetNumberOfChunkTypes() == 2);\n\t\tREQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true);\n\n\t\tparameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR);\n\t\tparameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tparameter->AddChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(99));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parameter->GetNumberOfChunkTypes() == 5);\n\t\tREQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR);\n\t\tREQUIRE(parameter->GetChunkTypeAt(2) == RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR);\n\t\tREQUIRE(parameter->GetChunkTypeAt(3) == RTC::SCTP::Chunk::ChunkType::COOKIE_ACK);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->GetChunkTypeAt(4) == static_cast<RTC::SCTP::Chunk::ChunkType>(99));\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR) == true);\n\t\tREQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK) == true);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(99)) == true);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter =\n\t\t  RTC::SCTP::SupportedExtensionsParameter::Parse(parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 12,\n\t\t  /*length*/ 12,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(parsedParameter->GetNumberOfChunkTypes() == 5);\n\t\tREQUIRE(parsedParameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG);\n\t\tREQUIRE(parsedParameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR);\n\t\tREQUIRE(parsedParameter->GetChunkTypeAt(2) == RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR);\n\t\tREQUIRE(parsedParameter->GetChunkTypeAt(3) == RTC::SCTP::Chunk::ChunkType::COOKIE_ACK);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parsedParameter->GetChunkTypeAt(4) == static_cast<RTC::SCTP::Chunk::ChunkType>(99));\n\t\tREQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true);\n\t\tREQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true);\n\t\tREQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR) == true);\n\t\tREQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK) == true);\n\t\t// NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\tREQUIRE(parsedParameter->IncludesChunkType(static_cast<RTC::SCTP::Chunk::ChunkType>(99)) == true);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestUnknownParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnknownParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unknown Parameter\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnknownParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:49159 (UNKNOWN), Length: 11\n\t\t\t0xC0, 0x07, 0x00, 0x0B,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding\n\t\t\t0x89, 0xAB, 0xCD, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::UnknownParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter->HasUnknownValue() == true);\n\t\tREQUIRE(parameter->GetUnknownValueLength() == 7);\n\t\tREQUIRE(parameter->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(parameter->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(parameter->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(parameter->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(parameter->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(parameter->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(parameter->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(parameter->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetUnknownValue()[7] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(parameter->HasUnknownValue() == true);\n\t\tREQUIRE(parameter->GetUnknownValueLength() == 7);\n\t\tREQUIRE(parameter->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(parameter->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(parameter->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(parameter->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(parameter->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(parameter->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(parameter->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetUnknownValue()[7] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 12,\n\t\t  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)\n\t\t  /*parameterType*/ static_cast<RTC::SCTP::Parameter::ParameterType>(49159),\n\t\t  /*unknownType*/ true,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT);\n\n\t\tREQUIRE(clonedParameter->HasUnknownValue() == true);\n\t\tREQUIRE(clonedParameter->GetUnknownValueLength() == 7);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[0] == 0x01);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[1] == 0x23);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[2] == 0x45);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[3] == 0x67);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[4] == 0x89);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[5] == 0xAB);\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[6] == 0xCD);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedParameter->GetUnknownValue()[7] == 0x00);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"UnknownParameter::Parse() fails\")\n\t{\n\t\t// Wrong Length field.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer1[] =\n\t\t{\n\t\t\t// Type:49159 (UNKNOWN), Length: 3\n\t\t\t0xC0, 0x07, 0x00, 0x03,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding\n\t\t\t0x89, 0xAB, 0xCD, 0x00,\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnknownParameter::Parse(buffer1, sizeof(buffer1)));\n\n\t\t// Wrong buffer length.\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer2[] =\n\t\t{\n\t\t\t// Type:49159 (UNKNOWN), Length: 11\n\t\t\t0xC0, 0x07, 0x00, 0x0B,\n\t\t\t// Unknown value: 0x0123456789ABCD\n\t\t\t0x01, 0x23, 0x45, 0x67,\n\t\t\t// 1 byte of padding (missing)\n\t\t\t0x89, 0xAB, 0xCD\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(!RTC::SCTP::UnknownParameter::Parse(buffer2, sizeof(buffer2)));\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestUnrecognizedParameterParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Unrecognized Parameter Parameter (7)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"UnrecognizedParameterParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:8 (UNRECOGNIZED_PARAMETER), Length: 7\n\t\t\t0x00, 0x08, 0x00, 0x07,\n\t\t\t// Unrecognized Parameter: 0xDDCCEE, 1 byte of padding\n\t\t\t0xDD, 0xCC, 0xEE, 0x00,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::UnrecognizedParameterParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasUnrecognizedParameter() == true);\n\t\tREQUIRE(parameter->GetUnrecognizedParameterLength() == 3);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[0] == 0xDD);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[1] == 0xCC);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[3] == 0x00);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parameter->HasUnrecognizedParameter() == true);\n\t\tREQUIRE(parameter->GetUnrecognizedParameterLength() == 3);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[0] == 0xDD);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[1] == 0xCC);\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(parameter->GetUnrecognizedParameter()[3] == 0x00);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(clonedParameter->HasUnrecognizedParameter() == true);\n\t\tREQUIRE(clonedParameter->GetUnrecognizedParameterLength() == 3);\n\t\tREQUIRE(clonedParameter->GetUnrecognizedParameter()[0] == 0xDD);\n\t\tREQUIRE(clonedParameter->GetUnrecognizedParameter()[1] == 0xCC);\n\t\tREQUIRE(clonedParameter->GetUnrecognizedParameter()[2] == 0xEE);\n\t\t// This should be padding.\n\t\tREQUIRE(clonedParameter->GetUnrecognizedParameter()[3] == 0x00);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"UnrecognizedParameterParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::UnrecognizedParameterParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 4,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\t/* Modify it. */\n\n\t\t// Verify that replacing the unrecognized parameter works.\n\t\tparameter->SetUnrecognizedParameter(sctpCommon::DataBuffer + 1000, 3000);\n\n\t\tREQUIRE(parameter->GetLength() == 3004);\n\t\tREQUIRE(parameter->HasUnrecognizedParameter() == true);\n\t\tREQUIRE(parameter->GetUnrecognizedParameterLength() == 3000);\n\n\t\tparameter->SetUnrecognizedParameter(nullptr, 0);\n\n\t\tREQUIRE(parameter->GetLength() == 4);\n\t\tREQUIRE(parameter->HasUnrecognizedParameter() == false);\n\t\tREQUIRE(parameter->GetUnrecognizedParameterLength() == 0);\n\n\t\t// 1 bytes + 3 bytes of padding. Note that first (and unique byte) is\n\t\t// sctpCommon::DataBuffer + 1 which is initialized to 0x0A.\n\t\tparameter->SetUnrecognizedParameter(sctpCommon::DataBuffer + 10, 1);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::UnrecognizedParameterParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP);\n\n\t\tREQUIRE(parsedParameter->HasUnrecognizedParameter() == true);\n\t\tREQUIRE(parsedParameter->GetUnrecognizedParameterLength() == 1);\n\t\tREQUIRE(parsedParameter->GetUnrecognizedParameter()[0] == 0x0A);\n\t\t// These should be padding.\n\t\tREQUIRE(parsedParameter->GetUnrecognizedParameter()[1] == 0x00);\n\t\tREQUIRE(parsedParameter->GetUnrecognizedParameter()[2] == 0x00);\n\t\tREQUIRE(parsedParameter->GetUnrecognizedParameter()[3] == 0x00);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/packet/parameters/TestZeroChecksumAcceptableParameter.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Parameter.hpp\"\n#include \"RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Zero Checksum Acceptable Parameter (32769)\", \"[serializable][sctp][parameter]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tSECTION(\"ZeroChecksumAcceptableParameter::Parse() succeeds\")\n\t{\n\t\t// clang-format off\n\t\talignas(4) uint8_t buffer[] =\n\t\t{\n\t\t\t// Type:32769 (ZERO_CHECKSUM_ACCEPTABLE), Length: 8\n\t\t\t0x80, 0x01, 0x00, 0x08,\n\t\t\t// Alternate Error Detection Method (EDMID) : 0x0001\n\t\t\t0x00, 0x00, 0x00, 0x01,\n\t\t\t// Extra bytes that should be ignored\n\t\t\t0xAA, 0xBB, 0xCC, 0xDD,\n\t\t\t0xAA, 0xBB, 0xCC\n\t\t};\n\t\t// clang-format on\n\n\t\tauto* parameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Parse(buffer, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ buffer,\n\t\t  /*bufferLength*/ sizeof(buffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  parameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\t/* Serialize it. */\n\n\t\tparameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tstd::memset(buffer, 0x00, sizeof(buffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::SerializeBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  parameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\t/* Clone it. */\n\n\t\tauto* clonedParameter =\n\t\t  parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer));\n\n\t\tstd::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer));\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ clonedParameter,\n\t\t  /*buffer*/ sctpCommon::CloneBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::CloneBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  clonedParameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\tdelete clonedParameter;\n\t}\n\n\tSECTION(\"ZeroChecksumAcceptableParameter::Factory() succeeds\")\n\t{\n\t\tauto* parameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory(\n\t\t  sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer));\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  parameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE);\n\n\t\t/* Modify it. */\n\n\t\tparameter->SetAlternateErrorDetectionMethod(\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer),\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  parameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\t/* Parse itself and compare. */\n\n\t\tauto* parsedParameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Parse(\n\t\t  parameter->GetBuffer(), parameter->GetLength());\n\n\t\tdelete parameter;\n\n\t\tCHECK_SCTP_PARAMETER(\n\t\t  /*parameter*/ parsedParameter,\n\t\t  /*buffer*/ sctpCommon::FactoryBuffer,\n\t\t  /*bufferLength*/ 8,\n\t\t  /*length*/ 8,\n\t\t  /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE,\n\t\t  /*unknownType*/ false,\n\t\t  /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP);\n\n\t\tREQUIRE(\n\t\t  parsedParameter->GetAlternateErrorDetectionMethod() ==\n\t\t  RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS);\n\n\t\tdelete parsedParameter;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/rx/TestDataTracker.cpp",
    "content": "#include \"mocks/include/MockShared.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/rx/DataTracker.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <initializer_list>\n#include <vector>\n\nSCENARIO(\"SCTP DataTracker\", \"[sctp][datatracker]\")\n{\n\tclass MockBackoffTimerHandleListener : public BackoffTimerHandleInterface::Listener\n\t{\n\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* /*backoffTimer*/, uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) override\n\t\t{\n\t\t}\n\t};\n\n\tconstexpr uint32_t Arwnd{ 10000 };\n\tconstexpr uint64_t Mtu{ 1191 };\n\tconstexpr uint32_t InitialTsn{ 11 };\n\n\tMockBackoffTimerHandleListener backoffTimerHandleListener;\n\tuint64_t nowMs{ 10000 };\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         [&nowMs]()\n\t                         {\n\t\t                         return nowMs;\n\t                         });\n\n\tconst std::unique_ptr<BackoffTimerHandleInterface> delayedAckTimerUniquePtr{ shared.CreateBackoffTimer(\n\t\tBackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t  .listener            = std::addressof(backoffTimerHandleListener),\n\t\t  .label               = \"mock-sctp-delayed-ack\",\n\t\t  .baseTimeoutMs       = 0,\n\t\t  .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t  .maxBackoffTimeoutMs = std::nullopt,\n\t\t  .maxRestarts         = 0 }) };\n\n\tauto* delayedAckTimer = delayedAckTimerUniquePtr.get();\n\n\tRTC::SCTP::DataTracker dataTracker(delayedAckTimer, InitialTsn);\n\n\tauto createPacket = []()\n\t{\n\t\treturn std::unique_ptr<RTC::SCTP::Packet>{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, Mtu) };\n\t};\n\n\tauto addSackChunk = [&dataTracker](RTC::SCTP::Packet* packet, size_t aRwnd)\n\t{\n\t\tdataTracker.AddSackSelectiveAck(packet, aRwnd);\n\n\t\tconst auto* sackChunk = packet->GetFirstChunkOfType<RTC::SCTP::SackChunk>();\n\n\t\treturn sackChunk;\n\t};\n\n\tauto observe = [&dataTracker](std::initializer_list<uint32_t> tsns, bool expectAsDuplicate = false)\n\t{\n\t\tfor (const uint32_t tsn : tsns)\n\t\t{\n\t\t\tif (expectAsDuplicate)\n\t\t\t{\n\t\t\t\tREQUIRE(dataTracker.Observe(tsn, /*immediateAck*/ false) == false);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tREQUIRE(dataTracker.Observe(tsn, /*immediateAck*/ false) == true);\n\t\t\t}\n\t\t}\n\t};\n\n\tSECTION(\"empty\")\n\t{\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"observe single in order packet\")\n\t{\n\t\tobserve({ 11 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"observe many in order moves cumulative tsn ack\")\n\t{\n\t\tobserve({ 11, 12, 13 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 13);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"observe out of order moves cumulative tsn ack\")\n\t{\n\t\tobserve({ 12, 13, 14, 11 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"single gap\")\n\t{\n\t\tobserve({ 12 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 }\n    });\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"example from RFC 9260 section 3.3.4\")\n\t{\n\t\tobserve({ 11, 12, 14, 15, 17 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 12);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 3 },\n                                        { 5, 5 }\n    });\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"ack already received chunks\")\n\t{\n\t\tobserve({ 11 });\n\n\t\tconst auto packet1     = createPacket();\n\t\tconst auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk1);\n\t\tREQUIRE(sackChunk1->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(sackChunk1->GetGapAckBlocks().empty());\n\n\t\t// Receive old chunk.\n\t\tobserve({ 8 }, /*expectAsDuplicate*/ true);\n\n\t\tconst auto packet2     = createPacket();\n\t\tconst auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk2);\n\t\tREQUIRE(sackChunk2->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(sackChunk2->GetGapAckBlocks().empty());\n\t}\n\n\tSECTION(\"double send retransmitted chunk\")\n\t{\n\t\tobserve({ 11, 13, 14, 15 });\n\n\t\tconst auto packet1     = createPacket();\n\t\tconst auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk1);\n\t\tREQUIRE(sackChunk1->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(\n\t\t  sackChunk1->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                     { 2, 4 },\n    });\n\n\t\t// Fill in the hole.\n\t\tobserve({ 12, 16, 17, 18 });\n\n\t\tconst auto packet2     = createPacket();\n\t\tconst auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk2);\n\t\tREQUIRE(sackChunk2->GetCumulativeTsnAck() == 18);\n\t\tREQUIRE(sackChunk2->GetGapAckBlocks().empty());\n\n\t\t// Receive chunk 12 again.\n\t\tobserve({ 12 }, /*expectAsDuplicate*/ true);\n\t\tobserve({ 19, 20, 21 });\n\n\t\tconst auto packet3     = createPacket();\n\t\tconst auto* sackChunk3 = addSackChunk(packet3.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk3);\n\t\tREQUIRE(sackChunk3->GetCumulativeTsnAck() == 21);\n\t\tREQUIRE(sackChunk3->GetGapAckBlocks().empty());\n\t}\n\n\tSECTION(\"forward tsn simple\")\n\t{\n\t\t// Messages (11, 12, 13), (14, 15) - first message expires.\n\t\tobserve({ 11, 12, 15 });\n\n\t\tdataTracker.HandleForwardTsn(13);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 13);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 },\n    });\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"forward tsn skips from gap block\")\n\t{\n\t\t// Messages (11, 12, 13), (14, 15) - first message expires.\n\t\tobserve({ 11, 12, 14 });\n\n\t\tdataTracker.HandleForwardTsn(13);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"example from RFC 3758\")\n\t{\n\t\tdataTracker.HandleForwardTsn(102);\n\n\t\tobserve({ 102 }, /*expectAsDuplicate*/ true);\n\t\tobserve({ 104, 105, 107 });\n\n\t\tdataTracker.HandleForwardTsn(103);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 105);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 },\n    });\n\t}\n\n\tSECTION(\"empty all acks\")\n\t{\n\t\tobserve({ 11, 13, 14, 15 });\n\n\t\tdataTracker.HandleForwardTsn(100);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 100);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"sets arwnd correctly\")\n\t{\n\t\tconst auto packet1     = createPacket();\n\t\tconst auto* sackChunk1 = addSackChunk(packet1.get(), 100);\n\n\t\tREQUIRE(sackChunk1);\n\t\tREQUIRE(sackChunk1->GetAdvertisedReceiverWindowCredit() == 100);\n\n\t\tconst auto packet2     = createPacket();\n\t\tconst auto* sackChunk2 = addSackChunk(packet2.get(), 101);\n\n\t\tREQUIRE(sackChunk2);\n\t\tREQUIRE(sackChunk2->GetAdvertisedReceiverWindowCredit() == 101);\n\t}\n\n\tSECTION(\"will increase cumulative ack tsn\")\n\t{\n\t\tREQUIRE(dataTracker.GetLastCumulativeAckedTsn() == 10);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(10) == false);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(11) == true);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(12) == false);\n\n\t\tobserve({ 11, 12, 13, 14, 15 });\n\n\t\tREQUIRE(dataTracker.GetLastCumulativeAckedTsn() == 15);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(15) == false);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(16) == true);\n\t\tREQUIRE(dataTracker.WillIncreaseCumAckTsn(17) == false);\n\t}\n\n\tSECTION(\"force should send sack immediately\")\n\t{\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\n\t\tdataTracker.ForceImmediateSack();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t}\n\n\tSECTION(\"will accept valid tsns\")\n\t{\n\t\t// The initial TSN is always one more than the last, which is our base.\n\t\tconst uint32_t lastTsn = InitialTsn - 1;\n\t\tconst int limit = static_cast<int>(RTC::SCTP::DataTracker::MaxAcceptedOutstandingFragments);\n\n\t\tfor (int i{ -limit }; i <= limit; ++i)\n\t\t{\n\t\t\tREQUIRE(dataTracker.IsTsnValid(lastTsn + i) == true);\n\t\t}\n\t}\n\n\tSECTION(\"will not accept invalid tsns\")\n\t{\n\t\t// The initial TSN is always one more than the last, which is our base.\n\t\tconst uint32_t lastTsn = InitialTsn - 1;\n\t\tconst uint32_t limit   = RTC::SCTP::DataTracker::MaxAcceptedOutstandingFragments;\n\n\t\tREQUIRE(dataTracker.IsTsnValid(lastTsn + limit + 1) == false);\n\t\tREQUIRE(dataTracker.IsTsnValid(lastTsn - (limit + 1)) == false);\n\t\tREQUIRE(dataTracker.IsTsnValid(lastTsn + 0x8000000) == false);\n\t\tREQUIRE(dataTracker.IsTsnValid(lastTsn - 0x8000000) == false);\n\t}\n\n\tSECTION(\"report single duplicate tsns\")\n\t{\n\t\tobserve({ 11, 12 });\n\t\tobserve({ 11 }, /*expectAsDuplicate*/ true);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 12);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns() == std::vector<uint32_t>{ 11 });\n\t}\n\n\tSECTION(\"report multiple duplicate tsns\")\n\t{\n\t\tobserve({ 11, 12, 13, 14 });\n\t\tobserve({ 12, 13, 12, 13 }, /*expectAsDuplicate*/ true);\n\t\tobserve({ 15, 16 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 16);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns() == std::vector<uint32_t>{ 12, 13 });\n\t}\n\n\tSECTION(\"report duplicate tsns in gap-ack-blocks\")\n\t{\n\t\tobserve({ 11, /*12,*/ 13, 14 });\n\t\tobserve({ 13, 14 }, /*expectAsDuplicate*/ true);\n\t\tobserve({ 15, 16 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 5 },\n    });\n\t\tREQUIRE(sackChunk->GetDuplicateTsns() == std::vector<uint32_t>{ 13, 14 });\n\t}\n\n\tSECTION(\"clears duplicate tsns after creating sack\")\n\t{\n\t\tobserve({ 11, 12, 13, 14 });\n\t\tobserve({ 12, 13, 12, 13 }, /*expectAsDuplicate*/ true);\n\t\tobserve({ 15, 16 });\n\n\t\tconst auto packet1     = createPacket();\n\t\tconst auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk1);\n\t\tREQUIRE(sackChunk1->GetCumulativeTsnAck() == 16);\n\t\tREQUIRE(sackChunk1->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk1->GetDuplicateTsns() == std::vector<uint32_t>{ 12, 13 });\n\n\t\tobserve({ 17 });\n\n\t\tconst auto packet2     = createPacket();\n\t\tconst auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk2);\n\t\tREQUIRE(sackChunk2->GetCumulativeTsnAck() == 17);\n\t\tREQUIRE(sackChunk2->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk2->GetDuplicateTsns().empty());\n\t}\n\n\tSECTION(\"limits number of duplicates reported\")\n\t{\n\t\tfor (size_t i{ 0 }; i < RTC::SCTP::DataTracker::MaxDuplicateTsnReported + 10; ++i)\n\t\t{\n\t\t\tconst uint32_t tsn = 11 + static_cast<uint32_t>(i);\n\n\t\t\tdataTracker.Observe(tsn, /*immediateAck*/ false);\n\t\t\tdataTracker.Observe(tsn, /*immediateAck*/ false);\n\t\t}\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t\tREQUIRE(sackChunk->GetDuplicateTsns().size() == RTC::SCTP::DataTracker::MaxDuplicateTsnReported);\n\t}\n\n\tSECTION(\"limits number of gap-ack-blocks reported\")\n\t{\n\t\tfor (size_t i{ 0 }; i < RTC::SCTP::DataTracker::MaxGapAckBlocksReported + 10; ++i)\n\t\t{\n\t\t\tconst uint32_t tsn = 11 + static_cast<uint32_t>(i * 2);\n\n\t\t\tdataTracker.Observe(tsn, /*immediateAck*/ false);\n\t\t}\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 11);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().size() == RTC::SCTP::DataTracker::MaxGapAckBlocksReported);\n\t}\n\n\tSECTION(\"sends sack for first packet observed\")\n\t{\n\t\tobserve({ 11 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\n\t\tconst auto* backoffTimer = shared.GetBackoffTimer(\"mock-sctp-delayed-ack\");\n\n\t\tREQUIRE(backoffTimer);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\t}\n\n\tSECTION(\"sends sack every second packet when there is no packet loss\")\n\t{\n\t\tauto* backoffTimer = shared.GetBackoffTimer(\"mock-sctp-delayed-ack\");\n\n\t\tREQUIRE(backoffTimer);\n\n\t\tobserve({ 11 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 12 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\t\tREQUIRE(backoffTimer->IsRunning() == true);\n\n\t\tobserve({ 13 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 14 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\t\tREQUIRE(backoffTimer->IsRunning() == true);\n\n\t\tobserve({ 15 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\t}\n\n\tSECTION(\"sends sack every packet on packet loss\")\n\t{\n\t\tconst auto* backoffTimer = shared.GetBackoffTimer(\"mock-sctp-delayed-ack\");\n\n\t\tREQUIRE(backoffTimer);\n\n\t\tobserve({ 11 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 13 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 14 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 15 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 16 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\t// Fill the hole.\n\t\tobserve({ 12 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\t\tREQUIRE(backoffTimer->IsRunning() == true);\n\n\t\t// Goes back to every second packet.\n\t\tobserve({ 17 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 18 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\t\tREQUIRE(backoffTimer->IsRunning() == true);\n\t}\n\n\tSECTION(\"sends sack on duplicate data chunks\")\n\t{\n\t\tconst auto* backoffTimer = shared.GetBackoffTimer(\"mock-sctp-delayed-ack\");\n\n\t\tREQUIRE(backoffTimer);\n\n\t\tobserve({ 11 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 11 }, /*expectAsDuplicate*/ true);\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\tobserve({ 12 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == false);\n\t\tREQUIRE(backoffTimer->IsRunning() == true);\n\n\t\t// Goes back to every second packet.\n\t\tobserve({ 13 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\n\t\t// Duplicate again.\n\t\tobserve({ 12 }, /*expectAsDuplicate*/ true);\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tREQUIRE(dataTracker.ShouldSendAck() == true);\n\t\tREQUIRE(backoffTimer->IsRunning() == false);\n\t}\n\n\tSECTION(\"gap-ack-block add single block\")\n\t{\n\t\tobserve({ 12 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block adds another\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 14 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 },\n\t\t                                    { 4, 4 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block adds duplicate\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 12 }, /*expectAsDuplicate*/ true);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 2 },\n    });\n\t\tREQUIRE(sackChunk->GetDuplicateTsns() == std::vector<uint32_t>{ 12 });\n\t}\n\n\tSECTION(\"gap-ack-block expands to right\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 13 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 3 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block expands to right with other\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 20 });\n\t\tobserve({ 30 });\n\t\tobserve({ 21 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2,  2  },\n\t\t                                    { 10, 11 },\n\t\t                                    { 20, 20 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block expands to left\")\n\t{\n\t\tobserve({ 13 });\n\t\tobserve({ 12 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2, 3 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block expands to left with other\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 21 });\n\t\tobserve({ 30 });\n\t\tobserve({ 20 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2,  2  },\n\t\t                                    { 10, 11 },\n\t\t                                    { 20, 20 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block expands to right and merges\")\n\t{\n\t\tobserve({ 12 });\n\t\tobserve({ 20 });\n\t\tobserve({ 22 });\n\t\tobserve({ 30 });\n\t\tobserve({ 21 });\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2,  2  },\n\t\t                                    { 10, 12 },\n\t\t                                    { 20, 20 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block merges many blocks into one\")\n\t{\n\t\tauto getGapAckBlocks = [&]()\n\t\t{\n\t\t\tconst auto packet     = createPacket();\n\t\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\t\tREQUIRE(sackChunk);\n\n\t\t\treturn sackChunk->GetGapAckBlocks();\n\t\t};\n\n\t\tobserve({ 22 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 }\n    });\n\n\t\tobserve({ 30 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 },\n\t\t                         { 20, 20 },\n    });\n\n\t\tobserve({ 24 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 },\n\t\t                         { 14, 14 },\n\t\t                         { 20, 20 },\n    });\n\n\t\tobserve({ 28 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 },\n\t\t                         { 14, 14 },\n\t\t                         { 18, 18 },\n\t\t                         { 20, 20 },\n    });\n\n\t\tobserve({ 26 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 },\n\t\t                         { 14, 14 },\n\t\t                         { 16, 16 },\n\t\t                         { 18, 18 },\n\t\t                         { 20, 20 },\n    });\n\n\t\tobserve({ 29 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 12 },\n\t\t                         { 14, 14 },\n\t\t                         { 16, 16 },\n\t\t                         { 18, 20 },\n    });\n\n\t\tobserve({ 23 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 14 },\n\t\t                         { 16, 16 },\n\t\t                         { 18, 20 },\n    });\n\n\t\tobserve({ 27 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 14 },\n\t\t                         { 16, 20 },\n    });\n\n\t\tobserve({ 25 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 12, 20 }\n    });\n\n\t\tobserve({ 20 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 10, 10 },\n\t\t                         { 12, 20 },\n    });\n\n\t\tobserve({ 32 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 10, 10 },\n\t\t                         { 12, 20 },\n\t\t                         { 22, 22 },\n    });\n\n\t\tobserve({ 21 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 10, 20 },\n\t\t                         { 22, 22 },\n    });\n\n\t\tobserve({ 31 });\n\n\t\tREQUIRE(\n\t\t  getGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                         { 10, 22 }\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove before cumulative ack tsn\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(8);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 10);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2,  4  },\n\t\t                                    { 10, 12 },\n\t\t                                    { 20, 21 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove before first block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(11);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 6,  8  },\n\t\t                                    { 16, 17 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove at beginning of first block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(12);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 6,  8  },\n\t\t                                    { 16, 17 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove at middle of first block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(13);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 6,  8  },\n\t\t                                    { 16, 17 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove at end of first block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(14);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 14);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 6,  8  },\n\t\t                                    { 16, 17 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove right after first block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(18);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 18);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 2,  4  },\n\t\t                                    { 12, 13 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove right before second block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(19);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 22);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 8, 9 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove right at start of second block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(20);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 22);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 8, 9 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove right at middle of second block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(21);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 22);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 8, 9 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove right at end of second block\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(22);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 22);\n\t\tREQUIRE(\n\t\t  sackChunk->GetGapAckBlocks() == std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t                                    { 8, 9 },\n    });\n\t}\n\n\tSECTION(\"gap-ack-block remove far after all blocks\")\n\t{\n\t\tobserve({ 12, 13, 14, 20, 21, 22, 30, 31 });\n\n\t\tdataTracker.HandleForwardTsn(40);\n\n\t\tconst auto packet     = createPacket();\n\t\tconst auto* sackChunk = addSackChunk(packet.get(), Arwnd);\n\n\t\tREQUIRE(sackChunk);\n\t\tREQUIRE(sackChunk->GetCumulativeTsnAck() == 40);\n\t\tREQUIRE(sackChunk->GetGapAckBlocks().empty());\n\t}\n\n\tSECTION(\"does not accept data before forward tsn\")\n\t{\n\t\tobserve({ 12, 13, 14, 15, 17 });\n\t\tdataTracker.ObservePacketEnd();\n\n\t\tdataTracker.HandleForwardTsn(13);\n\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false);\n\t}\n\n\tSECTION(\"does not accept data at forward tsn\")\n\t{\n\t\tobserve({ 12, 13, 14, 15, 17 });\n\n\t\tdataTracker.ObservePacketEnd();\n\t\tdataTracker.HandleForwardTsn(16);\n\n\t\tREQUIRE(dataTracker.Observe(16, /*immediateAck*/ false) == false);\n\t}\n\n\tSECTION(\"does not accept data before cumulative ack tsn\")\n\t{\n\t\tREQUIRE(InitialTsn == 11);\n\n\t\tREQUIRE(dataTracker.Observe(10, /*immediateAck*/ false) == false);\n\t}\n\n\tSECTION(\"does not accept contiguous duplicate data\")\n\t{\n\t\tREQUIRE(InitialTsn == 11);\n\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false);\n\t\tREQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == false);\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false);\n\t\tREQUIRE(dataTracker.Observe(10, /*immediateAck*/ false) == false);\n\t}\n\n\tSECTION(\"does not accept gaps with duplicate data\")\n\t{\n\t\tREQUIRE(InitialTsn == 11);\n\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false);\n\n\t\tREQUIRE(dataTracker.Observe(14, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(14, /*immediateAck*/ false) == false);\n\n\t\tREQUIRE(dataTracker.Observe(13, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(13, /*immediateAck*/ false) == false);\n\n\t\tREQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == true);\n\t\tREQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == false);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/rx/TestInterleavedReassemblyStreams.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/SCTP/rx/InterleavedReassemblyStreams.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyStreamsInterface.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <span>\n#include <vector>\n\nSCENARIO(\"SCTP InterleavedReassemblyStreams\", \"[sctp][interleavedreassemblystreams]\")\n{\n\tclass OnAssembledMessageTester\n\t{\n\tpublic:\n\t\tRTC::SCTP::ReassemblyStreamsInterface::OnAssembledMessage MakeCallback()\n\t\t{\n\t\t\treturn [this](std::span<const RTC::SCTP::Types::UnwrappedTsn> tsns, RTC::SCTP::Message message)\n\t\t\t{\n\t\t\t\tthis->callCount++;\n\n\t\t\t\t// Copy the span to a vector to survive the callback.\n\t\t\t\tthis->lastTsns = std::vector<RTC::SCTP::Types::UnwrappedTsn>(tsns.begin(), tsns.end());\n\t\t\t\tthis->lastMessages.push_back(std::move(message));\n\t\t\t};\n\t\t}\n\n\t\tbool GetCallCount(size_t expectedCallCount) const\n\t\t{\n\t\t\treturn this->callCount == expectedCallCount;\n\t\t}\n\n\t\tstd::vector<uint32_t> GetLastTsns() const\n\t\t{\n\t\t\tif (this->lastTsns.empty())\n\t\t\t{\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tstd::vector<uint32_t> tsns;\n\n\t\t\ttsns.reserve(this->lastTsns.size());\n\n\t\t\tfor (const auto& tsn : this->lastTsns)\n\t\t\t{\n\t\t\t\ttsns.push_back(tsn.Wrap());\n\t\t\t}\n\n\t\t\treturn tsns;\n\t\t}\n\n\t\tstd::vector<RTC::SCTP::Message>& GetLastMessages()\n\t\t{\n\t\t\treturn this->lastMessages;\n\t\t}\n\n\t\tbool CheckCallbackNotCalled() const\n\t\t{\n\t\t\treturn (this->callCount == 0 && this->lastTsns.empty() && this->lastMessages.empty());\n\t\t}\n\n\t\tvoid Reset()\n\t\t{\n\t\t\tthis->callCount = 0;\n\t\t\tthis->lastTsns.clear();\n\t\t\tthis->lastMessages.clear();\n\t\t}\n\n\tprivate:\n\t\tsize_t callCount{ 0 };\n\t\tstd::vector<RTC::SCTP::Types::UnwrappedTsn> lastTsns;\n\t\tstd::vector<RTC::SCTP::Message> lastMessages;\n\t};\n\n\tRTC::SCTP::Types::UnwrappedTsn::Unwrapper tsn;\n\n\tauto getTsn = [&tsn](uint32_t value)\n\t{\n\t\treturn tsn.Unwrap(value);\n\t};\n\n\tSECTION(\"add unordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ true)) == 1);\n\n\t\tREQUIRE(\n\t\t  interleavedReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, true)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  interleavedReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, true)) == 2);\n\n\t\t// Adding the end fragment should make it empty again.\n\t\tREQUIRE(\n\t\t  interleavedReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, true)) == -6);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1, 2, 3, 4 });\n\t}\n\n\tSECTION(\"add simple ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == -6);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1, 2, 3, 4 });\n\t}\n\n\tSECTION(\"add more complex ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// Captured without adding yet: mid=0, fsn=1 (second fragment of first message).\n\t\tRTC::SCTP::UserData lateData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: mid=1, single fragment.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: mid=2, two fragments.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Adding the late chunk completes mid=0, which triggers delivery of\n\t\t// mid=1 and mid=2 as well.\n\t\t// NOTE: clang-tidy doesn't understand that this is fine.\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(interleavedreassemblystreams.AddData(getTsn(2), std::move(lateData)) == -8);\n\n\t\tREQUIRE(tester.GetCallCount(3));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 6, 7 });\n\t}\n\n\tSECTION(\"delete unordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ true)) == 1);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, true)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, true)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.HandleForwardTsn(\n\t\t    getTsn(3),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*unordered*/ true,\n           /*streamId*/ 1,\n           /*mid*/ 0 }\n    }) == 6);\n\t}\n\n\tSECTION(\"delete simple ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.HandleForwardTsn(\n\t\t    getTsn(3),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*unordered*/ false,\n           /*streamId*/ 1,\n           /*mid*/ 0 }\n    }) == 6);\n\t}\n\n\tSECTION(\"delete many ordered messages returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// mid=0 middle fragment (not added).\n\t\t// RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: mid=1.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: mid=2.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Expire all three messages (skip through mid=2 inclusive).\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.HandleForwardTsn(\n\t\t    getTsn(8),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*unordered*/ false,\n           /*streamId*/ 1,\n           /*mid*/ 2 }\n    }) == 8);\n\t}\n\n\tSECTION(\"delete ordered message delivers two returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// mid=0 middle fragment (not added).\n\t\t// RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: mid=1.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: mid=2.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Expire the first message (mid=0). The following two (mid=1 and mid=2) are\n\t\t// delivered.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.HandleForwardTsn(\n\t\t    getTsn(4),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*unordered*/ false,\n           /*streamId*/ 1,\n           /*mid*/ 0 }\n    }) == 8);\n\t}\n\n\tSECTION(\"can delete first ordered message\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\t// Not received: mid=0, sid=1, tsn=1 (BE, single-chunk message).\n\t\t// Deleted via I-FORWARD-tsn: sid=1, mid=0.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.HandleForwardTsn(\n\t\t    getTsn(1),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*unordered*/ false,\n           /*streamId*/ 1,\n           /*mid*/ 0 }\n    }) == 0);\n\n\t\t// Receive mid=1 (next after the skipped mid=0): should be delivered immediately.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(2),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 1,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x02, 0x03, 0x04 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ true,\n\t\t      /*isUnordered*/ false)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 2 });\n\n\t\tauto& lastMessages = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages.size() == 1);\n\t\tREQUIRE(std::move(lastMessages[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02, 0x03, 0x04 });\n\t}\n\n\tSECTION(\"can reassemble fast path unordered\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\t// Each chunk is a complete single-fragment message (BE), delivered immediately.\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ true,\n\t\t      /*isUnordered*/ true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1 });\n\n\t\tauto& lastMessages1 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages1.size() == 1);\n\t\tREQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector<uint8_t>{ 0x01 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x03 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 3 });\n\n\t\tauto& lastMessages2 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages2.size() == 1);\n\t\tREQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector<uint8_t>{ 0x03 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x02 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 2 });\n\n\t\tauto& lastMessages3 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages3.size() == 1);\n\t\tREQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  interleavedreassemblystreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 3, 0, 53, { 0x04 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 4 });\n\n\t\tauto& lastMessages4 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages4.size() == 1);\n\t\tREQUIRE(std::move(lastMessages4[0]).ReleasePayload() == std::vector<uint8_t>{ 0x04 });\n\t}\n\n\tSECTION(\"can reassemble fast path ordered\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback());\n\n\t\tRTC::SCTP::UserData data1(1, 0, 0, 0, 53, { 0x01 }, true, true, false);\n\t\tRTC::SCTP::UserData data2(1, 0, 1, 0, 53, { 0x02 }, true, true, false);\n\t\tRTC::SCTP::UserData data3(1, 0, 2, 0, 53, { 0x03 }, true, true, false);\n\t\tRTC::SCTP::UserData data4(1, 0, 3, 0, 53, { 0x04 }, true, true, false);\n\n\t\t// tsn(1)/mid=0: delivered immediately (nextMid=0).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(interleavedreassemblystreams.AddData(getTsn(1), std::move(data1)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1 });\n\n\t\tauto& lastMessages1 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages1.size() == 1);\n\t\tREQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector<uint8_t>{ 0x01 });\n\n\t\ttester.Reset();\n\n\t\t// tsn(3)/mid=2: buffered (nextMid=1).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(interleavedreassemblystreams.AddData(getTsn(3), std::move(data3)) == 1);\n\n\t\tREQUIRE(tester.CheckCallbackNotCalled());\n\n\t\t// tsn(2)/mid=1: completes mid=1, then mid=2 is also delivered.\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(interleavedreassemblystreams.AddData(getTsn(2), std::move(data2)) == -1);\n\n\t\tREQUIRE(tester.GetCallCount(2));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 3 });\n\n\t\tauto& lastMessages2 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages2.size() == 2);\n\t\tREQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02 });\n\t\tREQUIRE(std::move(lastMessages2[1]).ReleasePayload() == std::vector<uint8_t>{ 0x03 });\n\n\t\ttester.Reset();\n\n\t\t// tsn(4)/mid=3: delivered immediately (nextMid=3).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(interleavedreassemblystreams.AddData(getTsn(4), std::move(data4)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 4 });\n\n\t\tauto& lastMessages3 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages3.size() == 1);\n\t\tREQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector<uint8_t>{ 0x04 });\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyQueue.hpp\"\n#include <array>\n#include <catch2/catch_test_macros.hpp>\n#include <catch2/matchers/catch_matchers_range_equals.hpp>\n#include <iterator>\n#include <ranges>\n#include <span>\n#include <vector>\n\nSCENARIO(\"SCTP ReassemblyQueue\", \"[sctp][reassemblyqueue]\")\n{\n\t// The default maximum length of the reassembly queue.\n\tconstexpr size_t BufferLength{ 10000 };\n\n\tconstexpr std::array<uint8_t, 4> ShortPayload    = { 1, 2, 3, 4 };\n\tconstexpr std::array<uint8_t, 4> Message2Payload = { 5, 6, 7, 8 };\n\tconstexpr std::array<uint8_t, 6> SixBytePayload  = { 1, 2, 3, 4, 5, 6 };\n\tconstexpr std::array<uint8_t, 8> MediumPayload1  = { 1, 2, 3, 4, 5, 6, 7, 8 };\n\tconstexpr std::array<uint8_t, 8> MediumPayload2  = { 9, 10, 11, 12, 13, 14, 15, 16 };\n\tconstexpr std::array<uint8_t, 16> LongPayload    = { 1, 2,  3,  4,  5,  6,  7,  8,\n\t\t                                                   9, 10, 11, 12, 13, 14, 15, 16 };\n\n\tauto flushMessages = [](RTC::SCTP::ReassemblyQueue& reassemblyQueue)\n\t{\n\t\tstd::vector<RTC::SCTP::Message> messages;\n\n\t\twhile (reassemblyQueue.HasMessages())\n\t\t{\n\t\t\tmessages.emplace_back(reassemblyQueue.GetNextMessage().value());\n\t\t}\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 0);\n\n\t\treturn messages;\n\t};\n\n\tSECTION(\"empty queue\")\n\t{\n\t\tconst RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 0);\n\t}\n\n\tSECTION(\"single unordered chunk message\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1, 2, 3, 4 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ true,\n\t\t    /*isUnordered*/ true));\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload));\n\t}\n\n\tSECTION(\"large unordered chunk all permutations\")\n\t{\n\t\tstd::vector<uint32_t> tsns = { 10, 11, 12, 13 };\n\n\t\tconst std::span<const uint8_t> payload(LongPayload);\n\n\t\tdo\n\t\t{\n\t\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\t\tfor (size_t i{ 0 }; i < tsns.size(); ++i)\n\t\t\t{\n\t\t\t\tconst auto span        = payload.subspan((tsns[i] - 10) * 4, 4);\n\t\t\t\tconst bool isBeginning = (tsns[i] == 10);\n\t\t\t\tconst bool isEnd       = (tsns[i] == 13);\n\n\t\t\t\treassemblyQueue.AddData(\n\t\t\t\t  tsns[i],\n\t\t\t\t  RTC::SCTP::UserData(\n\t\t\t\t    /*streamId*/ 1,\n\t\t\t\t    /*ssn*/ 0,\n\t\t\t\t    /*mid*/ 0,\n\t\t\t\t    /*fsn*/ 0,\n\t\t\t\t    /*ppid*/ 53,\n\t\t\t\t    /*payload*/ std::vector<uint8_t>(span.begin(), span.end()),\n\t\t\t\t    /*isBeginning*/ isBeginning,\n\t\t\t\t    /*isEnd*/ isEnd,\n\t\t\t\t    /*isUnordered*/ false));\n\n\t\t\t\tif (i < 3)\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\t\t\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\t\t\t\tREQUIRE(messages.size() == 1);\n\t\t\t\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\t\t\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\t\t\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(LongPayload));\n\t\t\t\t}\n\t\t\t}\n\t\t} while (std::ranges::next_permutation(tsns).found);\n\t}\n\n\tSECTION(\"single ordered chunk message\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1, 2, 3, 4 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ true,\n\t\t    /*isUnordered*/ false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload));\n\t}\n\n\tSECTION(\"many small ordered messages\")\n\t{\n\t\tstd::vector<uint32_t> tsns = { 10, 11, 12, 13 };\n\n\t\tconst std::span<const uint8_t> payload(LongPayload);\n\n\t\tdo\n\t\t{\n\t\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\t\tfor (size_t i{ 0 }; i < tsns.size(); ++i)\n\t\t\t{\n\t\t\t\tconst auto span = payload.subspan((tsns[i] - 10) * 4, 4);\n\t\t\t\tconst bool isBeginning{ true };\n\t\t\t\tconst bool isEnd{ true };\n\t\t\t\tconst auto ssn = static_cast<uint16_t>(tsns[i] - 10);\n\n\t\t\t\treassemblyQueue.AddData(\n\t\t\t\t  tsns[i],\n\t\t\t\t  RTC::SCTP::UserData(\n\t\t\t\t    /*streamId*/ 1,\n\t\t\t\t    /*ssn*/ ssn,\n\t\t\t\t    /*mid*/ 0,\n\t\t\t\t    /*fsn*/ 0,\n\t\t\t\t    /*ppid*/ 53,\n\t\t\t\t    /*payload*/ std::vector<uint8_t>(span.begin(), span.end()),\n\t\t\t\t    /*isBeginning*/ isBeginning,\n\t\t\t\t    /*isEnd*/ isEnd,\n\t\t\t\t    /*isUnordered*/ false));\n\t\t\t}\n\n\t\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\t\tREQUIRE(messages.size() == 4);\n\t\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(0, 4)));\n\t\t\tREQUIRE(messages[1].GetStreamId() == 1);\n\t\t\tREQUIRE(messages[1].GetPayloadProtocolId() == 53);\n\t\t\tREQUIRE_THAT(messages[1].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(4, 4)));\n\t\t\tREQUIRE(messages[2].GetStreamId() == 1);\n\t\t\tREQUIRE(messages[2].GetPayloadProtocolId() == 53);\n\t\t\tREQUIRE_THAT(messages[2].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(8, 4)));\n\t\t\tREQUIRE(messages[3].GetStreamId() == 1);\n\t\t\tREQUIRE(messages[3].GetPayloadProtocolId() == 53);\n\t\t\tREQUIRE_THAT(messages[3].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(12, 4)));\n\t\t} while (std::ranges::next_permutation(tsns).found);\n\t}\n\n\tSECTION(\"retransmission in large ordered\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ false));\n\t\treassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false));\n\t\treassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, false, false));\n\t\treassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 5 }, false, false, false));\n\t\treassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 6 }, false, false, false));\n\t\treassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 7 }, false, false, false));\n\t\treassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 8 }, false, false, false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 7);\n\n\t\t// Lost and retransmitted.\n\t\treassemblyQueue.AddData(11, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 2 }, false, false, false));\n\n\t\treassemblyQueue.AddData(18, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 9 }, false, false, false));\n\t\treassemblyQueue.AddData(19, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 10 }, false, false, false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 10);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\n\t\t// Bit \"E\".\n\t\treassemblyQueue.AddData(\n\t\t  20, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 11, 12, 13, 14, 15, 16 }, false, true, false));\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(LongPayload));\n\t}\n\n\tSECTION(\"Forward-TSN remove unordered\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ true));\n\t\treassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, true));\n\t\treassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, true));\n\n\t\treassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, true));\n\t\treassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, true));\n\t\treassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 6);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\n\t\treassemblyQueue.HandleForwardTsn(13, std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{});\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 3);\n\n\t\t// The second lost chunk comes, message is assembled.\n\t\treassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\t}\n\n\tSECTION(\"Fforward-TSN remove ordered\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\t// First message.\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ false));\n\t\treassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false));\n\t\treassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, false));\n\n\t\t// Second message.\n\t\treassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, false));\n\t\treassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, false));\n\t\treassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, false));\n\t\treassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 7);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\n\t\treassemblyQueue.HandleForwardTsn(\n\t\t  13,\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { /*streamId*/ 1, /*ssn*/ 0 }\n    });\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\n\t\t// The lost chunk comes, but too late.\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload));\n\t}\n\n\tSECTION(\"Forward-TSN remove a lot ordered\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ false));\n\t\treassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false));\n\t\treassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, false));\n\n\t\treassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, false));\n\t\treassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, false));\n\t\treassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, false));\n\t\treassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 7);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\n\t\treassemblyQueue.HandleForwardTsn(\n\t\t  13,\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { /*streamId*/ 1, /*ssn*/ 0 }\n    });\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\n\t\t// The lost chunk comes, but too late.\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload));\n\t}\n\n\tSECTION(\"single unordered chunk message in RFC 8260\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1, 2, 3, 4 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ true,\n\t\t    /*isUnordered*/ true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload));\n\t}\n\n\tSECTION(\"two interleaved chunks\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true);\n\n\t\t// Stream 1.\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1, 2, 3, 4 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ true));\n\n\t\t// Stream 2.\n\t\treassemblyQueue.AddData(\n\t\t  11, RTC::SCTP::UserData(2, 0, 0, 0, 53, { 9, 10, 11, 12 }, true, false, true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 8);\n\n\t\t// Stream 1.\n\t\treassemblyQueue.AddData(\n\t\t  12, RTC::SCTP::UserData(1, 0, 0, 1, 53, { 5, 6, 7, 8 }, false, true, true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 12);\n\n\t\t// Stream 2.\n\t\treassemblyQueue.AddData(\n\t\t  13, RTC::SCTP::UserData(2, 0, 0, 1, 53, { 13, 14, 15, 16 }, false, true, true));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 16);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 2);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(MediumPayload1));\n\t\tREQUIRE(messages[1].GetStreamId() == 2);\n\t\tREQUIRE(messages[1].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[1].GetPayload(), Catch::Matchers::RangeEquals(MediumPayload2));\n\t}\n\n\tSECTION(\"unordered interleaved messages all permutations\")\n\t{\n\t\tstd::vector<size_t> idxs = { 0, 1, 2, 3, 4, 5 };\n\n\t\tconst std::vector<uint32_t> tsns      = { 10, 11, 12, 13, 14, 15 };\n\t\tconst std::vector<uint16_t> streamIds = { 1, 2, 1, 1, 2, 2 };\n\t\tconst std::vector<uint32_t> fsns      = { 0, 0, 1, 2, 1, 2 };\n\t\tconst std::span<const uint8_t> payload(SixBytePayload);\n\n\t\tdo\n\t\t{\n\t\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true);\n\n\t\t\tfor (auto i : idxs)\n\t\t\t{\n\t\t\t\tconst auto span        = payload.subspan(fsns[i] * 2, 2);\n\t\t\t\tconst bool isBeginning = (fsns[i] == 0);\n\t\t\t\tconst bool isEnd       = (fsns[i] == 2);\n\n\t\t\t\treassemblyQueue.AddData(\n\t\t\t\t  tsns[i],\n\t\t\t\t  RTC::SCTP::UserData(\n\t\t\t\t    /*streamId*/ streamIds[i],\n\t\t\t\t    /*ssn*/ 0,\n\t\t\t\t    /*mid*/ 0,\n\t\t\t\t    /*fsn*/ fsns[i],\n\t\t\t\t    /*ppid*/ 53,\n\t\t\t\t    /*payload*/ std::vector<uint8_t>(span.begin(), span.end()),\n\t\t\t\t    /*isBeginning*/ isBeginning,\n\t\t\t\t    /*isEnd*/ isEnd,\n\t\t\t\t    /*isUnordered*/ true));\n\t\t\t}\n\n\t\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\t\tREQUIRE(messages.size() == 2);\n\n\t\t\t// NOTE: These are unordered messages so during the test permutations\n\t\t\t// they can be generated in different order.\n\t\t\tfor (const auto& message : messages)\n\t\t\t{\n\t\t\t\tREQUIRE((message.GetStreamId() == 1 || message.GetStreamId() == 2));\n\t\t\t\tREQUIRE(message.GetPayloadProtocolId() == 53);\n\t\t\t\tREQUIRE_THAT(message.GetPayload(), Catch::Matchers::RangeEquals(SixBytePayload));\n\t\t\t}\n\t\t} while (std::ranges::next_permutation(idxs).found);\n\t}\n\n\tSECTION(\"I-Forward-TSN remove a lot ordered\")\n\t{\n\t\tRTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true);\n\n\t\treassemblyQueue.AddData(\n\t\t  /*tsn*/ 10,\n\t\t  RTC::SCTP::UserData(\n\t\t    /*streamId*/ 1,\n\t\t    /*ssn*/ 0,\n\t\t    /*mid*/ 0,\n\t\t    /*fsn*/ 0,\n\t\t    /*ppid*/ 53,\n\t\t    /*payload*/ { 1 },\n\t\t    /*isBeginning*/ true,\n\t\t    /*isEnd*/ false,\n\t\t    /*isUnordered*/ false));\n\t\t// NOTE: Second fragment (fsn=1) is lost.\n\t\treassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 2, 53, { 3 }, false, false, false));\n\t\treassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 3, 53, { 4 }, false, true, false));\n\n\t\treassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 0, 1, 0, 53, { 5 }, true, false, false));\n\t\treassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 0, 1, 1, 53, { 6 }, false, false, false));\n\t\treassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 0, 1, 2, 53, { 7 }, false, false, false));\n\t\treassemblyQueue.AddData(18, RTC::SCTP::UserData(1, 0, 1, 3, 53, { 8 }, false, true, false));\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 7);\n\t\tREQUIRE(reassemblyQueue.HasMessages() == false);\n\n\t\treassemblyQueue.HandleForwardTsn(\n\t\t  13,\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 0 }\n    });\n\n\t\tREQUIRE(reassemblyQueue.GetQueuedBytes() == 4);\n\n\t\t// The lost chunk comes, but too late.\n\n\t\tREQUIRE(reassemblyQueue.HasMessages() == true);\n\n\t\tconst auto& messages = flushMessages(reassemblyQueue);\n\n\t\tREQUIRE(messages.size() == 1);\n\t\tREQUIRE(messages[0].GetStreamId() == 1);\n\t\tREQUIRE(messages[0].GetPayloadProtocolId() == 53);\n\t\tREQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload));\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/rx/TestTraditionalReassemblyStreams.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/SCTP/rx/ReassemblyStreamsInterface.hpp\"\n#include \"RTC/SCTP/rx/TraditionalReassemblyStreams.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <span>\n#include <vector>\n\nSCENARIO(\"SCTP TraditionalReassemblyStreams\", \"[sctp][traditionalreassemblystreams]\")\n{\n\tclass OnAssembledMessageTester\n\t{\n\tpublic:\n\t\tRTC::SCTP::ReassemblyStreamsInterface::OnAssembledMessage MakeCallback()\n\t\t{\n\t\t\treturn [this](std::span<const RTC::SCTP::Types::UnwrappedTsn> tsns, RTC::SCTP::Message message)\n\t\t\t{\n\t\t\t\tthis->callCount++;\n\n\t\t\t\t// Copy the span to a vector to survive the callback.\n\t\t\t\tthis->lastTsns = std::vector<RTC::SCTP::Types::UnwrappedTsn>(tsns.begin(), tsns.end());\n\t\t\t\tthis->lastMessages.push_back(std::move(message));\n\t\t\t};\n\t\t}\n\n\t\tbool GetCallCount(size_t expectedCallCount) const\n\t\t{\n\t\t\treturn this->callCount == expectedCallCount;\n\t\t}\n\n\t\tstd::vector<uint32_t> GetLastTsns() const\n\t\t{\n\t\t\tif (this->lastTsns.empty())\n\t\t\t{\n\t\t\t\treturn {};\n\t\t\t}\n\n\t\t\tstd::vector<uint32_t> tsns;\n\n\t\t\ttsns.reserve(this->lastTsns.size());\n\n\t\t\tfor (const auto& tsn : this->lastTsns)\n\t\t\t{\n\t\t\t\ttsns.push_back(tsn.Wrap());\n\t\t\t}\n\n\t\t\treturn tsns;\n\t\t}\n\n\t\tstd::vector<RTC::SCTP::Message>& GetLastMessages()\n\t\t{\n\t\t\treturn this->lastMessages;\n\t\t}\n\n\t\tbool CheckCallbackNotCalled() const\n\t\t{\n\t\t\treturn (this->callCount == 0 && this->lastTsns.empty() && this->lastMessages.empty());\n\t\t}\n\n\t\tvoid Reset()\n\t\t{\n\t\t\tthis->callCount = 0;\n\t\t\tthis->lastTsns.clear();\n\t\t\tthis->lastMessages.clear();\n\t\t}\n\n\tprivate:\n\t\tsize_t callCount{ 0 };\n\t\tstd::vector<RTC::SCTP::Types::UnwrappedTsn> lastTsns;\n\t\tstd::vector<RTC::SCTP::Message> lastMessages;\n\t};\n\n\tRTC::SCTP::Types::UnwrappedTsn::Unwrapper tsn;\n\n\tauto getTsn = [&tsn](uint32_t value)\n\t{\n\t\treturn tsn.Unwrap(value);\n\t};\n\n\tSECTION(\"add unordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ true)) == 1);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, true)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, true)) == 2);\n\n\t\t// Adding the end fragment should make it empty again.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, true)) == -6);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1, 2, 3, 4 });\n\t}\n\n\tSECTION(\"add simple ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == -6);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1, 2, 3, 4 });\n\t}\n\n\tSECTION(\"add more complex ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// Captured without adding yet: ssn=0, middle fragment of the first message.\n\t\tRTC::SCTP::UserData lateData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: ssn=1.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: ssn=2.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Adding the late chunk completes ssn=0, which triggers delivery of ssn=1\n\t\t// and ssn=2 as well.\n\t\t// NOTE: clang-tidy doesn't understand that this is fine.\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(traditionalReassemblyStreams.AddData(getTsn(2), std::move(lateData)) == -8);\n\n\t\tREQUIRE(tester.GetCallCount(3));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 6, 7 });\n\t}\n\n\tSECTION(\"delete unordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ true)) == 1);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, true)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, true)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.HandleForwardTsn(\n\t\t    getTsn(3), std::span<const RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{}) == 6);\n\t}\n\n\tSECTION(\"delete simple ordered message returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)) ==\n\t\t  3);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.HandleForwardTsn(\n\t\t    getTsn(3),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*streamId*/ 1, /*ssn*/ 0 }\n    }) == 6);\n\t}\n\n\tSECTION(\"delete many ordered messages returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// ssn=0 middle fragment (not added, consumed to advance the generator).\n\t\t// RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: ssn=1.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: ssn=2.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Expire all three messages (skip through ssn=2 inclusive).\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.HandleForwardTsn(\n\t\t    getTsn(8),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*streamId*/ 1, /*ssn*/ 2 }\n    }) == 8);\n\t}\n\n\tSECTION(\"delete ordered message delivers two returns correct size\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ false,\n\t\t      /*isUnordered*/ false)) == 1);\n\n\t\t// ssn=0 middle fragment (not added, consumed to advance the generator).\n\t\t// RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Second message: ssn=1.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1);\n\n\t\t// Third message: ssn=2.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2);\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1);\n\n\t\t// Expire the first message (ssn=0). The following two (ssn=1 and ssn=2) are\n\t\t// delivered.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.HandleForwardTsn(\n\t\t    getTsn(4),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*streamId*/ 1, /*ssn*/ 0 }\n    }) == 8);\n\t}\n\n\tSECTION(\"can delete first ordered message\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\t// Not received: ssn=0, sid=1, tsn=1 (BE, single-chunk message).\n\t\t// Consumed here only to advance the ssn counter conceptually.\n\n\t\t// Deleted via FORWARD-tsn: sid=1, ssn=0.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.HandleForwardTsn(\n\t\t    getTsn(1),\n\t\t    std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t      { /*streamId*/ 1, /*ssn*/ 0 }\n    }) == 0);\n\n\t\t// Receive ssn=1 (next after the skipped ssn=0): should be delivered immediately.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 1,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x02, 0x03, 0x04 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ true,\n\t\t      /*isUnordered*/ false)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 2 });\n\n\t\tauto& lastMessages = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages.size() == 1);\n\t\tREQUIRE(std::move(lastMessages[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02, 0x03, 0x04 });\n\t}\n\n\tSECTION(\"can reassemble fast path unordered\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\t// Each chunk is a complete single-fragment message (BE), delivered immediately.\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(1),\n\t\t    RTC::SCTP::UserData(\n\t\t      /*streamId*/ 1,\n\t\t      /*ssn*/ 0,\n\t\t      /*mid*/ 0,\n\t\t      /*fsn*/ 0,\n\t\t      /*ppid*/ 53,\n\t\t      /*payload*/ { 0x01 },\n\t\t      /*isBeginning*/ true,\n\t\t      /*isEnd*/ true,\n\t\t      /*isUnordered*/ true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1 });\n\n\t\tauto& lastMessages1 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages1.size() == 1);\n\t\tREQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector<uint8_t>{ 0x01 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(3), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x03 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 3 });\n\n\t\tauto& lastMessages2 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages2.size() == 1);\n\t\tREQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector<uint8_t>{ 0x03 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(2), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x02 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 2 });\n\n\t\tauto& lastMessages3 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages3.size() == 1);\n\t\tREQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02 });\n\n\t\ttester.Reset();\n\n\t\tREQUIRE(\n\t\t  traditionalReassemblyStreams.AddData(\n\t\t    getTsn(4), RTC::SCTP::UserData(1, 3, 0, 0, 53, { 0x04 }, true, true, true)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 4 });\n\n\t\tauto& lastMessages4 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages4.size() == 1);\n\t\tREQUIRE(std::move(lastMessages4[0]).ReleasePayload() == std::vector<uint8_t>{ 0x04 });\n\t}\n\n\tSECTION(\"can reassemble fast path ordered\")\n\t{\n\t\tOnAssembledMessageTester tester;\n\t\tRTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback());\n\n\t\tRTC::SCTP::UserData data1(1, 0, 0, 0, 53, { 0x01 }, true, true, false);\n\t\tRTC::SCTP::UserData data2(1, 1, 0, 0, 53, { 0x02 }, true, true, false);\n\t\tRTC::SCTP::UserData data3(1, 2, 0, 0, 53, { 0x03 }, true, true, false);\n\t\tRTC::SCTP::UserData data4(1, 3, 0, 0, 53, { 0x04 }, true, true, false);\n\n\t\t// tsn(1)/ssn=0: delivered immediately (nextSsn=0).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(traditionalReassemblyStreams.AddData(getTsn(1), std::move(data1)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 1 });\n\n\t\tauto& lastMessages1 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages1.size() == 1);\n\t\tREQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector<uint8_t>{ 0x01 });\n\n\t\ttester.Reset();\n\n\t\t// tsn(3)/ssn=2: buffered (nextSsn=1).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(traditionalReassemblyStreams.AddData(getTsn(3), std::move(data3)) == 1);\n\n\t\tREQUIRE(tester.CheckCallbackNotCalled());\n\n\t\t// tsn(2)/ssn=1: completes ssn=1, then ssn=2 is also delivered.\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(traditionalReassemblyStreams.AddData(getTsn(2), std::move(data2)) == -1);\n\n\t\tREQUIRE(tester.GetCallCount(2));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 3 });\n\n\t\tauto& lastMessages2 = tester.GetLastMessages();\n\n\t\t// Notice that here we got message ssn=2 before last message ssn=3.\n\t\tREQUIRE(lastMessages2.size() == 2);\n\t\tREQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector<uint8_t>{ 0x02 });\n\t\tREQUIRE(std::move(lastMessages2[1]).ReleasePayload() == std::vector<uint8_t>{ 0x03 });\n\n\t\ttester.Reset();\n\n\t\t// tsn(4)/ssn=3: delivered immediately (nextSsn=3).\n\t\t// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved)\n\t\tREQUIRE(traditionalReassemblyStreams.AddData(getTsn(4), std::move(data4)) == 0);\n\n\t\tREQUIRE(tester.GetCallCount(1));\n\t\tREQUIRE(tester.GetLastTsns() == std::vector<uint32_t>{ 4 });\n\n\t\tauto& lastMessages3 = tester.GetLastMessages();\n\n\t\tREQUIRE(lastMessages3.size() == 1);\n\t\tREQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector<uint8_t>{ 0x04 });\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/sctpCommon.cpp",
    "content": "#include \"test/include/RTC/SCTP/sctpCommon.hpp\" // in worker/test/include/\n#include <cstring>                              // std::memset\n\nnamespace sctpCommon\n{\n\t// NOTE: Buffers must be 4-byte aligned since SCTP Packet parsing casts them\n\t// to structs that require 4-byte alignment. Without this, accessing multi-byte\n\t// fields would be undefined behavior on strict-alignment architectures.\n\talignas(4) thread_local uint8_t FactoryBuffer[];\n\talignas(4) thread_local uint8_t SerializeBuffer[];\n\talignas(4) thread_local uint8_t CloneBuffer[];\n\talignas(4) thread_local uint8_t DataBuffer[];\n\talignas(4) thread_local uint8_t ThrowBuffer[];\n\n\tvoid ResetBuffers()\n\t{\n\t\tstd::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer));\n\t\tstd::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer));\n\t\tstd::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer));\n\t\tstd::memset(DataBuffer, 0xDD, sizeof(DataBuffer));\n\t\tstd::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer));\n\n\t\tfor (size_t i = 0; i < 256; ++i)\n\t\t{\n\t\t\tsctpCommon::DataBuffer[i] = static_cast<uint8_t>(i);\n\t\t}\n\t}\n} // namespace sctpCommon\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestOutstandingData.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/public/SctpTypes.hpp\"\n#include \"RTC/SCTP/tx/OutstandingData.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <stdexcept>\n#include <vector>\n\nSCENARIO(\"SCTP OutstandingData\", \"[sctp][outstandingdata]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tclass DiscardFromSendQueueTester\n\t{\n\tpublic:\n\t\tvoid Prepare(bool returnValue)\n\t\t{\n\t\t\tif (this->prepared)\n\t\t\t{\n\t\t\t\tthrow std::runtime_error(\"already prepared\");\n\t\t\t}\n\n\t\t\tthis->prepared    = true;\n\t\t\tthis->returnValue = returnValue;\n\t\t}\n\n\t\tbool Called(uint16_t streamId, uint32_t outgoingMessageId)\n\t\t{\n\t\t\tif (!this->prepared)\n\t\t\t{\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthis->callCount++;\n\t\t\tthis->lastStreamId          = streamId;\n\t\t\tthis->lastOutgoingMessageId = outgoingMessageId;\n\n\t\t\treturn this->returnValue;\n\t\t}\n\n\t\tvoid Test(\n\t\t  size_t expectedCallCount, uint16_t expectedLastStreamId, uint32_t expectedLastOutgoingMessageId)\n\t\t{\n\t\t\tif (!this->prepared)\n\t\t\t{\n\t\t\t\tthrow std::runtime_error(\"not prepared\");\n\t\t\t}\n\n\t\t\tREQUIRE(this->callCount == expectedCallCount);\n\t\t\tREQUIRE(this->lastStreamId == expectedLastStreamId);\n\t\t\tREQUIRE(this->lastOutgoingMessageId == expectedLastOutgoingMessageId);\n\n\t\t\t// Reset.\n\t\t\tthis->prepared              = false;\n\t\t\tthis->callCount             = 0;\n\t\t\tthis->lastStreamId          = 0;\n\t\t\tthis->lastOutgoingMessageId = 0;\n\t\t\tthis->returnValue           = false;\n\t\t}\n\n\tprivate:\n\t\tbool prepared{ false };\n\t\tsize_t callCount{ 0 };\n\t\tuint16_t lastStreamId{ 0 };\n\t\tuint32_t lastOutgoingMessageId{ 0 };\n\t\tbool returnValue{ false };\n\t};\n\n\tconstexpr uint64_t NowMs{ 42 };\n\tconstexpr uint32_t OutgoingMessageId{ 17 };\n\n\tRTC::SCTP::Types::UnwrappedTsn::Unwrapper unwrapper;\n\tDiscardFromSendQueueTester discardFromSendQueueTester;\n\n\tauto discardFromSendQueue =\n\t  [&discardFromSendQueueTester](uint16_t streamId, uint32_t outgoingMessageId)\n\t{\n\t\treturn discardFromSendQueueTester.Called(streamId, outgoingMessageId);\n\t};\n\n\tRTC::SCTP::OutstandingData buffer(\n\t  /*dataChunkHeaderLength*/ RTC::SCTP::DataChunk::DataChunkHeaderLength,\n\t  /*lastCumulativeTsnAck*/ unwrapper.Unwrap(9),\n\t  discardFromSendQueue);\n\n\tSECTION(\"has initial state\")\n\t{\n\t\tREQUIRE(buffer.IsEmpty() == true);\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 0);\n\t\tREQUIRE(buffer.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(buffer.GetUnackedItems() == 0);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 10);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 9);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == false);\n\t}\n\n\tSECTION(\"insert chunk\")\n\t{\n\t\tconst auto tsn = buffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tREQUIRE(tsn.has_value());\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(tsn->Wrap() == 10);\n\n\t\tREQUIRE(buffer.IsEmpty() == false);\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 1);\n\t\tREQUIRE(buffer.GetUnackedItems() == 1);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 11);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"acks single chunk\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tconst auto ackInfo = buffer.HandleSack(unwrapper.Unwrap(10), {}, false);\n\n\t\tREQUIRE(\n\t\t  ackInfo.bytesAcked ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(ackInfo.highestTsnAcked.Wrap() == 10);\n\t\tREQUIRE(ackInfo.hasPacketLoss == false);\n\n\t\tREQUIRE(buffer.IsEmpty() == true);\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 0);\n\t\tREQUIRE(buffer.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(buffer.GetUnackedItems() == 0);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 10);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 11);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\t}\n\n\tSECTION(\"acks previous chunk doesn't update\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tREQUIRE(buffer.IsEmpty() == false);\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 1);\n\t\tREQUIRE(buffer.GetUnackedItems() == 1);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 11);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"acks and nacks with gap-ack-blocks\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);\n\n\t\tconst auto ackInfo = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 2 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(\n\t\t  ackInfo.bytesAcked ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(ackInfo.highestTsnAcked.Wrap() == 11);\n\t\tREQUIRE(ackInfo.hasPacketLoss == false);\n\n\t\t// TSN 10 is still outstanding.\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 1);\n\t\tREQUIRE(buffer.GetUnackedItems() == 1);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 12);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 11);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\t}\n\n\tSECTION(\"nacks three times with same TSN doesn't retransmit\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);\n\n\t\tconst std::vector<RTC::SCTP::SackChunk::GapAckBlock> gab1 = {\n\t\t\t{ 2, 2 }\n\t\t};\n\n\t\tREQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\t}\n\n\tSECTION(\"nacks three times results in retransmission\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 2 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 3 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tconst auto ackInfo = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(\n\t\t  ackInfo.bytesAcked ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);\n\t\tREQUIRE(ackInfo.hasPacketLoss == true);\n\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == true);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED               },\n    });\n\n\t\tstd::vector<std::pair<uint32_t, RTC::SCTP::UserData>> expectedChunksToBeFastRetransmitted;\n\n\t\texpectedChunksToBeFastRetransmitted.emplace_back(\n\t\t  10, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false));\n\n\t\tREQUIRE(buffer.GetChunksToBeFastRetransmitted(1000) == expectedChunksToBeFastRetransmitted);\n\t\tREQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true);\n\t}\n\n\tSECTION(\"nacks three times results in abandoning\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 2 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 3 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ false);\n\n\t\tconst auto ackInfo = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(\n\t\t  ackInfo.bytesAcked ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);\n\t\tREQUIRE(ackInfo.hasPacketLoss == true);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 14);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t}\n\n\tSECTION(\"nacks three times results in abandoning with placeholder\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 2 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 3 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ true);\n\n\t\tconst auto ackInfo = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(\n\t\t  ackInfo.bytesAcked ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(ackInfo.highestTsnAcked.Wrap() == 13);\n\t\tREQUIRE(ackInfo.hasPacketLoss == true);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 15);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t}\n\n\tSECTION(\"expires chunk before it is inserted\")\n\t{\n\t\tconstexpr uint64_t ExpiresAtMs = NowMs + 1;\n\n\t\tauto tsn = buffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  ExpiresAtMs);\n\n\t\tREQUIRE(tsn.has_value());\n\n\t\ttsn = buffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  ExpiresAtMs);\n\n\t\tREQUIRE(tsn.has_value());\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ false);\n\n\t\ttsn = buffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),\n\t\t  NowMs + 1,\n\t\t  /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  ExpiresAtMs);\n\n\t\tREQUIRE(!tsn.has_value());\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9);\n\t\tREQUIRE(buffer.GetNextTsn().Wrap() == 13);\n\t\tREQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 12);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t}\n\n\tSECTION(\"can generate Forward-TSN\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ false);\n\n\t\tbuffer.NackAll();\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tconst auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12);\n\t}\n\n\tSECTION(\"ack with gap blocks from RFC 9260 section 3.3.4\")\n\t{\n\t\tfor (int i{ 0 }; i < 8; ++i)\n\t\t{\n\t\t\tconst bool isBeginning = (i == 0);\n\t\t\tconst bool isEnd       = (i == 7);\n\n\t\t\tbuffer.Insert(\n\t\t\t  OutgoingMessageId,\n\t\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false),\n\t\t\t  NowMs);\n\t\t}\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(12),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 3 },\n        { 5, 5 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\t}\n\n\tSECTION(\"MeasureRtt()\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 1);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 2);\n\n\t\tconstexpr uint64_t Duration{ 123 };\n\n\t\tconst auto duration = buffer.MeasureRtt(NowMs + Duration, unwrapper.Unwrap(11));\n\n\t\tREQUIRE(duration.has_value());\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(duration.value() == Duration - 1);\n\t}\n\n\tSECTION(\"must retransmit before getting nacked again\")\n\t{\n\t\t// A chunk that has been nacked and scheduled for retransmission should not\n\t\t// get nacked again until it has actually been sent on the wire.\n\t\tfor (int i{ 0 }; i <= 10; ++i)\n\t\t{\n\t\t\tconst bool isBeginning = (i == 0);\n\t\t\tconst bool isEnd       = (i == 10);\n\n\t\t\tbuffer.Insert(\n\t\t\t  OutgoingMessageId,\n\t\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false),\n\t\t\t  NowMs,\n\t\t\t  /*maxRetransmits*/ 1);\n\t\t}\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 2 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 3 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tconst auto ackInfo = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo.hasPacketLoss == true);\n\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == true);\n\n\t\t// Don't retransmit yet. S simulate congestion window blocking it.\n\t\t// It still gets more SACKs indicating packet loss.\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 5 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == true);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 6 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == true);\n\n\t\tconst auto ackInfo2 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 7 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo2.hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == true);\n\n\t\t// Now retransmit.\n\t\tconst auto fastRetransmit = buffer.GetChunksToBeFastRetransmitted(1000);\n\n\t\tREQUIRE(fastRetransmit.size() == 1);\n\t\tREQUIRE(fastRetransmit[0].first == 10);\n\t\tREQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true);\n\n\t\t// TSN 10 is now lost again. It gets nacked and eventually abandoned.\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 8 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(\n\t\t  buffer\n\t\t    .HandleSack(\n\t\t      unwrapper.Unwrap(9),\n\t\t      std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t        { 2, 9 }\n    },\n\t\t      false)\n\t\t    .hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tconst auto ackInfo3 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 10 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo3.hasPacketLoss == true);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t}\n\n\tSECTION(\"lifecyle returns acked items in ack-info\")\n\t{\n\t\tbuffer.Insert(\n\t\t  1,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  RTC::SCTP::Types::ExpiresAtMsInfinite,\n\t\t  /*lifecycleId*/ 42);\n\n\t\tbuffer.Insert(\n\t\t  2,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  RTC::SCTP::Types::ExpiresAtMsInfinite,\n\t\t  /*lifecycleId*/ 43);\n\n\t\tbuffer.Insert(\n\t\t  3,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  RTC::SCTP::Types::MaxRetransmitsNoLimit,\n\t\t  RTC::SCTP::Types::ExpiresAtMsInfinite,\n\t\t  /*lifecycleId*/ 44);\n\n\t\tconst auto ackInfo1 = buffer.HandleSack(unwrapper.Unwrap(11), {}, false);\n\n\t\tstd::vector<uint64_t> expectedAckedLifecycleIds = { 42, 43 };\n\n\t\tREQUIRE(ackInfo1.ackedLifecycleIds == expectedAckedLifecycleIds);\n\n\t\tconst auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(12), {}, false);\n\n\t\texpectedAckedLifecycleIds = { 44 };\n\n\t\tREQUIRE(ackInfo2.ackedLifecycleIds == expectedAckedLifecycleIds);\n\t}\n\n\tSECTION(\"lifecyle returns abandoned nacked three times\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tconst auto ackInfo1 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 2 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo1.hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tconst auto ackInfo2 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 3 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo2.hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ false);\n\n\t\tconst auto ackInfo3 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(ackInfo3.hasPacketLoss == true);\n\t\tREQUIRE(ackInfo3.abandonedLifecycleIds.empty() == true);\n\t}\n\n\tSECTION(\"lifecyle returns abandoned after T3 RTX timer expired\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId,\n\t\t  RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0,\n\t\t  /*expiresAtMs*/ RTC::SCTP::Types::ExpiresAtMsInfinite,\n\t\t  /*lifecycleId*/ 42);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tconst auto ackInfo1 = buffer.HandleSack(\n\t\t  unwrapper.Unwrap(9),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 4 }\n    },\n\t\t  false);\n\n\t\tREQUIRE(ackInfo1.hasPacketLoss == false);\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\n\t\tdiscardFromSendQueueTester.Prepare(/*returnValue*/ false);\n\n\t\tbuffer.NackAll();\n\n\t\tdiscardFromSendQueueTester.Test(\n\t\t  /*expectedCallCount*/ 1,\n\t\t  /*expectedLastStreamId*/ 1,\n\t\t  /*expectedLastOutgoingMessageId*/ OutgoingMessageId);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t\t;\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tconst auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 13);\n\n\t\tconst auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(13), {}, false);\n\n\t\tREQUIRE(ackInfo2.hasPacketLoss == false);\n\t\tREQUIRE(ackInfo2.abandonedLifecycleIds == std::vector<uint64_t>{ 42 });\n\t}\n\n\tSECTION(\"generates Forward-TSN until next stream reset TSN\")\n\t{\n\t\t// This test generates:\n\t\t// * Stream 1: TSN 10, 11, 12 <RESET>\n\t\t// * Stream 2: TSN 13, 14 <RESET>\n\t\t// * Stream 3: TSN 15, 16\n\t\t//\n\t\t// Then it expires chunk 12-15, and ensures that the generated FORWARD-TSN\n\t\t// only includes up till TSN 12 until the cum ack TSN has reached 12, and\n\t\t// then 13 and 14 are included, and then after the cum ack TSN has reached\n\t\t// 14, then 15 is included.\n\t\t//\n\t\t// What it shouldn't do, is to generate a FORWARD-TSN directly at the start\n\t\t// with new TSN=15, and setting [(sid=1, ssn=44), (sid=2, ssn=46),\n\t\t// (sid=3, ssn=47)], because that will confuse the receiver at TSN=17,\n\t\t// receiving SID=1, SSN=0 (it's reset!), expecting SSN to be 45.\n\n\t\t// TSN 10-12.\n\t\tbuffer.Insert(\n\t\t  0,\n\t\t  RTC::SCTP::UserData(1, /*ssn*/ 42, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.Insert(\n\t\t  1,\n\t\t  RTC::SCTP::UserData(1, /*ssn*/ 43, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.Insert(\n\t\t  2,\n\t\t  RTC::SCTP::UserData(1, /*ssn*/ 44, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.BeginResetStreams();\n\n\t\t// TSN 13, 14.\n\t\tbuffer.Insert(\n\t\t  3,\n\t\t  RTC::SCTP::UserData(2, /*ssn*/ 45, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.Insert(\n\t\t  4,\n\t\t  RTC::SCTP::UserData(2, /*ssn*/ 46, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.BeginResetStreams();\n\n\t\t// TSN 15, 16.\n\t\tbuffer.Insert(\n\t\t  5,\n\t\t  RTC::SCTP::UserData(3, /*ssn*/ 47, 0, 0, 53, { 0x00 }, true, true, false),\n\t\t  NowMs,\n\t\t  /*maxRetransmits*/ 0);\n\t\tbuffer.Insert(6, RTC::SCTP::UserData(3, /*ssn*/ 48, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == false);\n\n\t\tbuffer.HandleSack(unwrapper.Unwrap(11), {}, false);\n\t\tbuffer.NackAll();\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED           },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED           },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ABANDONED           },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ABANDONED           },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n    });\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) };\n\n\t\tconst auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{\n\t\t                                            { 1, 44 },\n    });\n\n\t\t// Ack 12, allowing a FORWARD-TSN that spans to TSN=14 to be created.\n\t\tbuffer.HandleSack(unwrapper.Unwrap(12), {}, false);\n\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tpacket.reset(\n\t\t  RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));\n\n\t\tforwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{\n\t\t                                            { 2, 46 },\n    });\n\n\t\t// Ack 13, allowing a FORWARD-TSN that spans to TSN=14 to be created.\n\t\tbuffer.HandleSack(unwrapper.Unwrap(13), {}, false);\n\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tpacket.reset(\n\t\t  RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));\n\n\t\tforwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{\n\t\t                                            { 2, 46 },\n    });\n\n\t\t// Ack 14, allowing a FORWARD-TSN that spans to TSN=15 to be created.\n\t\tbuffer.HandleSack(unwrapper.Unwrap(14), {}, false);\n\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == true);\n\n\t\tpacket.reset(\n\t\t  RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)));\n\n\t\tforwardTsnChunk = buffer.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\t// NOLINTNEXTLINE(bugprone-unchecked-optional-access)\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 15);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() == std::vector<RTC::SCTP::ForwardTsnChunk::SkippedStream>{\n\t\t                                            { 3, 47 },\n    });\n\n\t\tbuffer.HandleSack(unwrapper.Unwrap(15), {}, false);\n\n\t\tREQUIRE(buffer.ShouldSendForwardTsn() == false);\n\t}\n\n\tSECTION(\"treats unacked payload bytes different from packet bytes\")\n\t{\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 1);\n\t\tREQUIRE(\n\t\t  buffer.GetUnackedPacketBytes() ==\n\t\t  RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1));\n\t\tREQUIRE(buffer.GetUnackedItems() == 1);\n\n\t\tbuffer.Insert(\n\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs);\n\n\t\tREQUIRE(buffer.GetUnackedPayloadBytes() == 2);\n\t\tREQUIRE(\n\t\t  buffer.GetUnackedPacketBytes() ==\n\t\t  2 * (RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes<size_t>(1)));\n\t\tREQUIRE(buffer.GetUnackedItems() == 2);\n\t}\n\n\tSECTION(\"fast recovery increments nack count when cumulative TSN advances\")\n\t{\n\t\t// This test verifies that the Fast Recovery retransmission rules are\n\t\t// correctly applied when the Cumulative TSN Ack point advances. RFC 9260\n\t\t// Section 7.2.4: \"If an endpoint is in Fast Recovery and a SACK arrives that\n\t\t// advances the Cumulative TSN Ack Point, the miss indications are incremented\n\t\t// for all TSNs reported missing in the SACK.\"\n\n\t\tfor (int i{ 10 }; i <= 16; ++i)\n\t\t{\n\t\t\tbuffer.Insert(\n\t\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);\n\t\t}\n\n\t\t// SACK 1: Cumulative Ack = 10. Gap blocks for 12, 14, 16.\n\t\t// Missing: 11, 13, 15.\n\t\t// This marks 12, 14, 16 as Acked.\n\t\t// TSNs 11, 13, 15 get their 1st miss indication each.\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(10),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 2 }, // TSN 12\n\t\t    { 4, 4 }, // TSN 14\n\t\t    { 6, 6 }  // TSN 16\n    },\n\t\t  /*isInFastRecovery*/ false);\n\n\t\t// SACK 2: Cumulative Ack advances to 11. Same gap blocks (12, 14, 16).\n\t\t// Endpoint is now in Fast Recovery (is_in_fast_recovery = true). Because the\n\t\t// Cumulative TSN Ack Point advanced from 10 to 11, 13 and 15 should get their\n\t\t// 2nd miss indication.\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(11),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 1, 1 }, // TSN 12\n\t\t    { 3, 3 }, // TSN 14\n\t\t    { 5, 5 }  // TSN 16\n    },\n\t\t  /*isInFastRecovery*/ true);\n\n\t\t// SACK 3: Cumulative Ack advances to 12.\n\t\t// Note: TSN 12 was already acked via gap block, so this just advances the\n\t\t// Cumulative Ack. 13 and 15 should get their 3rd miss indication and trigger\n\t\t// retransmission.\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(12),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 2 }, // TSN 14\n\t\t    { 4, 4 }  // TSN 16\n    },\n\t\t  /*isInFastRecovery*/ true);\n\n\t\tREQUIRE(\n\t\t  buffer.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ACKED               },\n    });\n\t}\n\n\tSECTION(\"nack between ack blocks does not access out of bounds\")\n\t{\n\t\tfor (int i{ 0 }; i < 5; ++i)\n\t\t{\n\t\t\tbuffer.Insert(\n\t\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);\n\t\t}\n\n\t\t// Inject a malformed SACK where the GapAckBlock exceeds the number of\n\t\t// outstanding items, potentially triggering an OOB read/write.\n\t\tstd::vector<RTC::SCTP::SackChunk::GapAckBlock> malformedBlocks = {\n\t\t\t{ 1, 40000 },\n\t\t};\n\n\t\tbuffer.HandleSack(unwrapper.Unwrap(10), malformedBlocks, /*isInFastRecovery*/ false);\n\n\t\tREQUIRE(buffer.HasDataToBeRetransmitted() == false);\n\t}\n\n\tSECTION(\"handles SACKs with out of bounds TSNs\")\n\t{\n\t\t// Send chunks with TSNs 10, 11, 12, 13, 14, 15, 16\n\t\tfor (int i{ 0 }; i < 7; ++i)\n\t\t{\n\t\t\tbuffer.Insert(\n\t\t\t  OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs);\n\t\t}\n\n\t\t// This NACKs TSN 11, 13, 15 (1st miss indication).\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(10),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 2, 2 }, // TSN 12\n\t\t    { 4, 4 }, // TSN 14\n\t\t    { 6, 6 }  // TSN 16\n    },\n\t\t  /*isInFastRecovery*/ false);\n\n\t\tREQUIRE(buffer.GetUnackedItems() == 3);\n\n\t\t// The gap between block1-end (12) and block2-start (1011) causes\n\t\t// NackBetweenAckBlocks to loop TSN 13..1010, but only TSN 13..16 are valid.\n\t\tbuffer.HandleSack(\n\t\t  unwrapper.Unwrap(11),\n\t\t  std::vector<RTC::SCTP::SackChunk::GapAckBlock>{\n\t\t    { 1,    1     }, // TSN 12\n\t\t    { 1000, 60000 }  // TSN 1011..60011\n    },\n\t\t  /*isInFastRecovery*/ true);\n\n\t\t// Packet 11 has been acknowledged.\n\t\tREQUIRE(buffer.GetUnackedItems() == 2);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestRetransmissionErrorCounter.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionErrorCounter.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <optional>\n\nSCENARIO(\"SCTP RetransmissionErrorCounter\", \"[sctp][retransmissionerrorcounter]\")\n{\n\tSECTION(\"can handle zero retransmission\")\n\t{\n\t\tconst RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 0 };\n\n\t\tRTC::SCTP::RetransmissionErrorCounter counter(sctpOptions);\n\n\t\tREQUIRE(counter.GetCounter() == 0);\n\t\tREQUIRE(counter.Increment(\"test\") == false); // One is too many.\n\t\tREQUIRE(counter.GetCounter() == 1);\n\t}\n\n\tSECTION(\"is exhausted at maximum\")\n\t{\n\t\tconst RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 3 };\n\n\t\tRTC::SCTP::RetransmissionErrorCounter counter(sctpOptions);\n\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 1\n\t\tREQUIRE(counter.GetCounter() == 1);\n\t\tREQUIRE(counter.IsExhausted() == false);\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 2\n\t\tREQUIRE(counter.GetCounter() == 2);\n\t\tREQUIRE(counter.IsExhausted() == false);\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 3\n\t\tREQUIRE(counter.GetCounter() == 3);\n\t\tREQUIRE(counter.IsExhausted() == false);\n\t\tREQUIRE(counter.Increment(\"test\") == false); // Too many retransmissions.\n\t\tREQUIRE(counter.GetCounter() == 4);\n\t\tREQUIRE(counter.IsExhausted() == true);\n\t\tREQUIRE(counter.Increment(\"test\") == false); // One after too many.\n\t\tREQUIRE(counter.GetCounter() == 5);\n\t\tREQUIRE(counter.IsExhausted() == true);\n\t}\n\n\tSECTION(\"clearing counter\")\n\t{\n\t\tconst RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 3 };\n\n\t\tRTC::SCTP::RetransmissionErrorCounter counter(sctpOptions);\n\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 1\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 2\n\t\tREQUIRE(counter.GetCounter() == 2);\n\t\tREQUIRE(counter.IsExhausted() == false);\n\n\t\tcounter.Clear();\n\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 1\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 2\n\t\tREQUIRE(counter.Increment(\"test\") == true); // 3\n\t\tREQUIRE(counter.IsExhausted() == false);\n\t\tREQUIRE(counter.Increment(\"test\") == false); // Too many retransmissions.\n\t\tREQUIRE(counter.GetCounter() == 4);\n\t\tREQUIRE(counter.IsExhausted() == true);\n\t}\n\n\tSECTION(\"can be limitless\")\n\t{\n\t\tconst RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = std::nullopt };\n\n\t\tRTC::SCTP::RetransmissionErrorCounter counter(sctpOptions);\n\n\t\tfor (size_t i{ 1 }; i < 1000; ++i)\n\t\t{\n\t\t\tREQUIRE(counter.Increment(\"test\") == true);\n\t\t\tREQUIRE(counter.GetCounter() == i);\n\t\t\tREQUIRE(counter.IsExhausted() == false);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestRetransmissionQueue.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"mocks/include/RTC/SCTP/association/MockAssociationListener.hpp\"\n#include \"mocks/include/RTC/SCTP/tx/MockSendQueue.hpp\"\n#include \"test/include/RTC/SCTP/sctpCommon.hpp\"\n#include \"test/include/catch2Macros.hpp\"\n#include \"RTC/SCTP/packet/Packet.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/DataChunk.hpp\"\n#include \"RTC/SCTP/packet/chunks/SackChunk.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionQueue.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include \"handles/BackoffTimerHandleInterface.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <vector>\n\nSCENARIO(\"SCTP RetransmissionQueue\", \"[sctp][retransmissionqueue]\")\n{\n\tsctpCommon::ResetBuffers();\n\n\tclass MockRetransmissionQueueListener : public RTC::SCTP::RetransmissionQueue::Listener\n\t{\n\tpublic:\n\t\tvoid OnRetransmissionQueueNewRttMs(uint64_t rttMs) override\n\t\t{\n\t\t\tthis->lastRttMs = rttMs;\n\t\t}\n\n\t\tvoid OnRetransmissionQueueClearRetransmissionCounter() override\n\t\t{\n\t\t\t++this->clearRetransmissionCounterCalls;\n\t\t}\n\n\tpublic:\n\t\tuint64_t lastRttMs{ 0 };\n\t\tsize_t clearRetransmissionCounterCalls{ 0 };\n\t};\n\n\tclass MockBackoffTimerHandleListener : public BackoffTimerHandleInterface::Listener\n\t{\n\t\t/* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */\n\tpublic:\n\t\tvoid OnBackoffTimer(\n\t\t  BackoffTimerHandleInterface* /*backoffTimer*/, uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) override\n\t\t{\n\t\t}\n\t};\n\n\tconstexpr uint32_t Arwnd{ 100000 };\n\tconstexpr uint64_t Mtu{ 1191 };\n\t// InitialTsn is the first TSN that will be assigned. The TSN before it\n\t// (InitialTsn - 1) starts as ACKED in OutstandingData, matching dcsctp's\n\t// invariant that the initial state has the previous TSN already\n\t// cumulative-acked.\n\tconstexpr uint32_t InitialTsn{ 10 };\n\n\tconst RTC::SCTP::SctpOptions sctpOptions{ .mtu = Mtu };\n\n\tMockRetransmissionQueueListener retransmissionQueueListener;\n\tMockBackoffTimerHandleListener backoffTimerHandleListener;\n\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\tmocks::RTC::SCTP::MockSendQueue sendQueue;\n\tuint64_t nowMs{ 10000 };\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         [&nowMs]()\n\t                         {\n\t\t                         return nowMs;\n\t                         });\n\n\tconst std::unique_ptr<BackoffTimerHandleInterface> t3RtxTimerUniquePtr{ shared.CreateBackoffTimer(\n\t\tBackoffTimerHandleInterface::BackoffTimerHandleOptions{\n\t\t  .listener            = std::addressof(backoffTimerHandleListener),\n\t\t  .label               = \"mock-sctp-t3-rtx\",\n\t\t  .baseTimeoutMs       = sctpOptions.initialRtoMs,\n\t\t  .backoffAlgorithm    = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL,\n\t\t  .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs,\n\t\t  .maxRestarts         = std::nullopt }) };\n\n\tauto* t3RtxTimer = t3RtxTimerUniquePtr.get();\n\n\tauto createRetransmissionQueue =\n\t  [&retransmissionQueueListener, &associationListener, &sendQueue, &t3RtxTimer, &sctpOptions](\n\t    bool supportsPartialReliability = true, bool useMessageInterleaving = false)\n\t{\n\t\treturn RTC::SCTP::RetransmissionQueue(\n\t\t  std::addressof(retransmissionQueueListener),\n\t\t  associationListener,\n\t\t  InitialTsn,\n\t\t  Arwnd,\n\t\t  sendQueue,\n\t\t  t3RtxTimer,\n\t\t  sctpOptions,\n\t\t  supportsPartialReliability,\n\t\t  useMessageInterleaving);\n\t};\n\n\tauto createDataToSend = [](\n\t                          uint32_t outgoingMessageId,\n\t                          uint16_t maxRetransmissions = RTC::SCTP::Types::MaxRetransmitsNoLimit)\n\t{\n\t\treturn [outgoingMessageId, maxRetransmissions](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t{\n\t\t\tRTC::SCTP::UserData data(\n\t\t\t  /*streamId*/ 1,\n\t\t\t  /*ssn*/ 0,\n\t\t\t  /*mid*/ 0,\n\t\t\t  /*fsn*/ 0,\n\t\t\t  /*ppid*/ 53,\n\t\t\t  /*payload*/ { 0x01, 0x02, 0x03, 0x04 },\n\t\t\t  /*isBeginning*/ true,\n\t\t\t  /*isEnd*/ true,\n\t\t\t  /*isUnordered*/ false);\n\n\t\t\tRTC::SCTP::SendQueueInterface::DataToSend dataToSend(outgoingMessageId, std::move(data));\n\n\t\t\tdataToSend.maxRetransmissions = maxRetransmissions;\n\n\t\t\treturn dataToSend;\n\t\t};\n\t};\n\n\tauto createSackChunk = [sctpOptions](\n\t                         uint32_t tsn,\n\t                         uint32_t arwnd,\n\t                         const std::vector<RTC::SCTP::SackChunk::GapAckBlock>&& gapAckBlocks = {})\n\t{\n\t\tstd::unique_ptr<RTC::SCTP::SackChunk> chunk{ RTC::SCTP::SackChunk::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sctpOptions.mtu) };\n\n\t\tchunk->SetCumulativeTsnAck(tsn);\n\t\tchunk->SetAdvertisedReceiverWindowCredit(arwnd);\n\n\t\tfor (const auto& gapAckBlock : gapAckBlocks)\n\t\t{\n\t\t\tchunk->AddAckBlock(gapAckBlock);\n\t\t}\n\n\t\treturn chunk;\n\t};\n\n\tauto getTSNsForFastRetransmit = [](RTC::SCTP::RetransmissionQueue& queue)\n\t{\n\t\tstd::vector<uint32_t> tsns;\n\n\t\tfor (const auto& elem : queue.GetChunksForFastRetransmit(10000))\n\t\t{\n\t\t\ttsns.push_back(elem.first);\n\t\t}\n\n\t\treturn tsns;\n\t};\n\n\tauto getSentPacketTSNs = [&nowMs](RTC::SCTP::RetransmissionQueue& queue, size_t maxLength = 10000)\n\t{\n\t\tstd::vector<uint32_t> tsns;\n\n\t\tfor (const auto& elem : queue.GetChunksToSend(nowMs, maxLength))\n\t\t{\n\t\t\ttsns.push_back(elem.first);\n\t\t}\n\n\t\treturn tsns;\n\t};\n\n\tSECTION(\"initial acked previous TSN\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetNextTsn() == InitialTsn);\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t, RTC::SCTP::OutstandingData::State>>{\n\t\t    { InitialTsn - 1, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\t}\n\n\tSECTION(\"send one chunk\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"send one chunk and ack\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10 });\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\t}\n\n\tSECTION(\"send three chunks and ack two\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10, 11, 12 });\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"ack with gap blocks from RFC 4960 section 334\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceOnce(createDataToSend(5))\n\t\t  .WillProduceOnce(createDataToSend(6))\n\t\t  .WillProduceOnce(createDataToSend(7))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17 });\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    12,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n          { 5, 5 }\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\t}\n\n\tSECTION(\"resend packet when nacked three times\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceOnce(createDataToSend(5))\n\t\t  .WillProduceOnce(createDataToSend(6))\n\t\t  .WillProduceOnce(createDataToSend(7))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17 });\n\n\t\t// Send more chunks, but leave some as gaps to force retransmission after\n\t\t// three NACKs.\n\n\t\t// Send TSN 18.\n\t\tsendQueue.WillProduceOnce(createDataToSend(8))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 18 });\n\n\t\t// Ack 12, 14-15, 17-18.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    12,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n          { 5, 6 }\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\n\t\t// Send TSN 19.\n\t\tsendQueue.WillProduceOnce(createDataToSend(9))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 19 });\n\n\t\t// Ack 12, 14-15, 17-19.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    12,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n          { 5, 7 }\n    })\n\t\t    .get());\n\n\t\t// Send TSN 20.\n\t\tsendQueue.WillProduceOnce(createDataToSend(10))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 20 });\n\n\t\t// Ack 12, 14-15, 17-20.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    12,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n          { 5, 8 }\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 20, RTC::SCTP::OutstandingData::State::ACKED               },\n    });\n\n\t\t// This will trigger \"fast retransmit\" mode and only chunks 13 and 16 will\n\t\t// be resent right now. The send queue will not even be queried.\n\t\tsendQueue.ExpectProduceCalledTimes(0);\n\n\t\tREQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector<uint32_t>{ 13, 16 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 20, RTC::SCTP::OutstandingData::State::ACKED     },\n    });\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\t}\n\n\tSECTION(\"restarts T3-rtx timer on retransmit first outstanding TSN\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\t// Starting time.\n\t\tnowMs = 100 * 1000; // 100 seconds.\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10, 11, 12 });\n\n\t\t// Ack 10, 12, after 100ms.\n\t\tnowMs += 100;\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    10,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 2 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\n\t\t// Send 13.\n\t\tsendQueue.WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 13 });\n\n\t\t// Ack 10, 12-13, after 100ms.\n\t\tnowMs += 100;\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    10,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n    })\n\t\t    .get());\n\n\t\t// Send 14.\n\t\tsendQueue.WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 14 });\n\n\t\t// Ack 10, 12-14, after 100 ms.\n\t\tnowMs += 100;\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    10,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 4 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED               },\n    });\n\n\t\t// This will trigger \"fast retransmit\" mode and only chunks 13 and 16 will\n\t\t// be resent right now. The send queue will not even be queried.\n\t\tsendQueue.ExpectProduceCalledTimes(0);\n\n\t\tREQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector<uint32_t>{ 11 });\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED     },\n    });\n\n\t\t// Verify that the timer was really restarted when fast-retransmitting. The\n\t\t// timeout is `sctpOptions.initialRtoMs`, so advance the time just before\n\t\t// that.\n\t\tnowMs += (sctpOptions.initialRtoMs - 1);\n\n\t\tauto* backoffTimer = shared.GetBackoffTimer(\"mock-sctp-t3-rtx\");\n\n\t\tREQUIRE(backoffTimer);\n\t\tREQUIRE(backoffTimer->EvaluateHasExpired() == false);\n\n\t\tnowMs += 1;\n\n\t\tREQUIRE(backoffTimer->EvaluateHasExpired() == true);\n\t}\n\n\tSECTION(\"can only produce two packets but wants to send three\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10, 11 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"retransmits on T3-rtx expiry\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Will force chunks to be retransmitted.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"limited retransmission only with RFC 3758 support\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue(/*supportsPartialReliability*/ false);\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Will force chunks to be retransmitted.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n    });\n\n\t\t// Discard must NOT be called.\n\t\tsendQueue.ExpectDiscardCalledTimes(0);\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\t}\n\n\tSECTION(\"limits retransmissions as UDP\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Will force chunks to be retransmitted (abandoned because\n\t\t// `maxRetransmissions: 0`).\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000).empty());\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t}\n\n\tSECTION(\"limits retransmissions to three sends\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 3))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// `Discard()` must NOT be called for the first three retransmissions.\n\t\tsendQueue.ExpectDiscardCalledTimes(0);\n\n\t\t// Retransmission 1.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1);\n\n\t\t// Retransmission 2.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1);\n\n\t\t// Retransmission 3.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1);\n\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\n\t\t// Retransmission 4. Not allowed, chunk is abandoned.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).empty());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t}\n\n\tSECTION(\"retransmits when send buffer is full on T3-rtx expiry\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconstexpr size_t Cwnd{ 1200 };\n\n\t\tretransmissionQueue.SetCwnd(Cwnd);\n\n\t\tREQUIRE(retransmissionQueue.GetCwnd() == Cwnd);\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\n\t\tconst std::vector<uint8_t> payload(1000, 0x00);\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, true, false));\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetUnackedPacketBytes() ==\n\t\t  payload.size() + RTC::SCTP::DataChunk::DataChunkHeaderLength);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 1);\n\n\t\t// Will force chunks to be retransmitted.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n    });\n\n\t\t// After T3 expiry in-flight counters are cleared.\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetUnackedPacketBytes() ==\n\t\t  payload.size() + RTC::SCTP::DataChunk::DataChunkHeaderLength);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 1);\n\t}\n\n\tSECTION(\"produces valid FORWARD-TSN\")\n\t{\n\t\t// Three middle fragments (no \"E\"), same message (outgoingMessageId=42,\n\t\t// ssn=42). `Discard()` returns true, placeholder TSN 13 created.\n\t\t// FORWARD-TSN newCumulativeTsn=13, skippedStreams={ (streamId=1, ssn=42) }.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\t// \"B\" — beginning.\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // Middle fragment.\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // Another middle fragment (message not fully sent — no \"E\").\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10, 11, 12 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Ack TSN 10, but the remaining are lost.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\t// T3 expiry: TSN 11, 12 abandoned. `Discard()` returns true, placeholder TSN 13.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ true);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\t// NOTE: TSN 13 represents the placeholder end fragment.\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\tconst std::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sctpOptions.mtu) };\n\t\tconst auto* forwardTsnChunk = retransmissionQueue.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 13);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() ==\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { 1, 42 }\n    });\n\t}\n\n\tSECTION(\"produces valid FORWARD-TSN when fully sent\")\n\t{\n\t\t// Three fragments \"B\"/\"\"/\"\"E\" (message fully sent). `Discard()` returns\n\t\t// false, no placeholder. FORWARD-TSN newCumulativeTsn=12.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // \"E\" — end fragment (message fully sent).\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, true, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10, 11, 12 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Ack TSN 10, but the remaining are lost.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\t// T3 expiry: TSN 11, 12 abandoned. `Discard()` returns false, no placeholder.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\tconst std::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sctpOptions.mtu) };\n\t\tconst auto* forwardTsnChunk = retransmissionQueue.AddForwardTsn(packet.get());\n\n\t\tREQUIRE(forwardTsnChunk);\n\t\tREQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12);\n\t\tREQUIRE(\n\t\t  forwardTsnChunk->GetSkippedStreams() ==\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { 1, 42 }\n    });\n\t}\n\n\tSECTION(\"produces valid I-FORWARD-TSN\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue(\n\t\t  /*supportsPartialReliability*/ true, /*useMessageInterleaving*/ true);\n\n\t\t// Stream 1, ordered, outgoingMessageId=42, mid=42, \"B\".\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 42, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // Stream 2, unordered, outgoingMessageId=43, mid=42, \"B\".\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(2, 0, 42, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, true);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(43, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // Stream 3, ordered, outgoingMessageId=44, mid=42, \"B\".\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(3, 0, 42, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // Stream 4, ordered, outgoingMessageId=45, mid=42, \"B\".\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(4, 0, 42, 0, 53, { 0x0d, 0x0e, 0x0f, 0x10 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(45, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10, 11, 12, 13 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// TSN 13 is acked via gap block; TSN 10-12 are nacked.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 4, 4 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED  },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::NACKED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED  },\n    });\n\n\t\t// T3 expiry: TSN 10-12 abandoned. `Discard()` called 3 times (one per stream),\n\t\t// each returns true, placeholder TSNs 14, 15, 16.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ true);\n\t\tsendQueue.WillDiscardOnce(2, 43, /*returnValue*/ true);\n\t\tsendQueue.WillDiscardOnce(3, 44, /*returnValue*/ true);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    // Placeholder end fragments for streams 1, 2 and 3.\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\t// I-FORWARD-TSN: newCumulativeTsn=12 (can't go past ACKED TSN 13).\n\t\tstd::unique_ptr<RTC::SCTP::Packet> packet{ RTC::SCTP::Packet::Factory(\n\t\t\tsctpCommon::FactoryBuffer, sctpOptions.mtu) };\n\n\t\tconst auto* iForwardTsnChunk1 = retransmissionQueue.AddIForwardTsn(packet.get());\n\n\t\tREQUIRE(iForwardTsnChunk1);\n\t\tREQUIRE(iForwardTsnChunk1->GetNewCumulativeTsn() == 12);\n\t\tREQUIRE(\n\t\t  iForwardTsnChunk1->GetSkippedStreams() ==\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { false, 1, 42 },\n\t\t    { false, 3, 42 },\n\t\t    { true,  2, 42 },\n    });\n\n\t\t// When TSN 13 is acked, the placeholder end fragments must be skipped too.\n\t\t// A receiver is more likely to ack TSN 13, but do it incrementally.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get());\n\n\t\tsendQueue.ExpectDiscardCalledTimes(0);\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(13, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\n\t\tpacket.reset(RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sctpOptions.mtu));\n\n\t\tconst auto* iForwardTsnChunk2 = retransmissionQueue.AddIForwardTsn(packet.get());\n\n\t\tREQUIRE(iForwardTsnChunk2);\n\t\tREQUIRE(iForwardTsnChunk2->GetNewCumulativeTsn() == 16);\n\t\tREQUIRE(\n\t\t  iForwardTsnChunk2->GetSkippedStreams() ==\n\t\t  std::vector<RTC::SCTP::AnyForwardTsnChunk::SkippedStream>{\n\t\t    { false, 1, 42 },\n\t\t    { false, 3, 42 },\n\t\t    { true,  2, 42 },\n    });\n\t}\n\n\tSECTION(\"measure RTT\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue(\n\t\t  /*supportsPartialReliability*/ true, /*useMessageInterleaving*/ true);\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0, /*maxRetranmissions*/ 0))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10 });\n\n\t\tconstexpr uint64_t DurationMs{ 123 };\n\n\t\tnowMs += DurationMs;\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueueListener.lastRttMs == DurationMs);\n\t}\n\n\tSECTION(\"validate cumulative TSN at rest\")\n\t{\n\t\t// Nothing outstanding. TSN 8 is below lastCumulativeTsnAck(9) -> rejected.\n\t\t// TSN 9 equals lastCumulativeTsnAck(9) -> accepted (no-op).\n\t\t// TSN 10 is above highestOutstandingTsn(9) -> rejected.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(8, Arwnd).get()) == false);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(9, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()) == false);\n\t}\n\n\tSECTION(\"validate cumulative TSN ack on inflight data\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceOnce(createDataToSend(5))\n\t\t  .WillProduceOnce(createDataToSend(6))\n\t\t  .WillProduceOnce(createDataToSend(7))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17 });\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(8, Arwnd).get()) == false);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(9, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(13, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(14, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(15, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(16, Arwnd).get()) == true);\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(17, Arwnd).get()) == true);\n\t\t// TSN 18 has never been sent -> rejected.\n\t\tREQUIRE(\n\t\t  retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(18, Arwnd).get()) == false);\n\t}\n\n\tSECTION(\"handle gap-ack-blocks matching no inflight data\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceOnce(createDataToSend(5))\n\t\t  .WillProduceOnce(createDataToSend(6))\n\t\t  .WillProduceOnce(createDataToSend(7))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17 });\n\n\t\t// Ack 9, 20-25. This is an invalid SACK Chunk, but should still be handled.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 11, 16 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"handle invalid gap-ack-blocks\")\n\t{\n\t\t// Nothing outstanding. Gap blocks referencing non-existent TSNs are\n\t\t// rejected.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\t// cumTsn=9 (no change), gap {3,4} -> TSN 12-13, both beyond\n\t\t// highestOutstandingTsn(9) -> rejected. State unchanged.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 3, 4 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\t}\n\n\tSECTION(\"gap-ack-blocks do not move cumulative TSN ack\")\n\t{\n\t\t// cumTsn=9, gap {1,5} acks TSN 10-14 via gap blocks. The cumulative TSN\n\t\t// ack point itself must NOT advance, gap acks are renegable.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tsendQueue.WillProduceOnce(createDataToSend(0))\n\t\t  .WillProduceOnce(createDataToSend(1))\n\t\t  .WillProduceOnce(createDataToSend(2))\n\t\t  .WillProduceOnce(createDataToSend(3))\n\t\t  .WillProduceOnce(createDataToSend(4))\n\t\t  .WillProduceOnce(createDataToSend(5))\n\t\t  .WillProduceOnce(createDataToSend(6))\n\t\t  .WillProduceOnce(createDataToSend(7))\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17 });\n\n\t\t// Ack 9, 10-14. This is actually an invalid ACK as the first gap can't be\n\t\t// adjacent to the cum-tsn-ack, but it's not strictly forbidden. However,\n\t\t// the cum-tsn-ack should not move, as the gap-ack-blocks are just advisory.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 1, 5 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t}\n\n\tSECTION(\"stays within available size\")\n\t{\n\t\t// With `GetChunksToSend(nowMs, 1188-12=1176)`, the first `Produce()` receives\n\t\t// 1176 - DataChunkHeaderLength bytes, the second receives the remainder.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\t\tconstexpr size_t AvailableBytes{ 1188 - 12 }; // 1176\n\n\t\tbool sizeCheck1Ok{ false };\n\t\tbool sizeCheck2Ok{ false };\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [&sizeCheck1Ok](uint64_t /*nowMs*/, size_t maxLength)\n\t\t    {\n\t\t\t    sizeCheck1Ok = (maxLength == AvailableBytes - RTC::SCTP::DataChunk::DataChunkHeaderLength);\n\n\t\t\t    std::vector<uint8_t> payload(183, 0x00);\n\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, std::move(payload), true, true, false));\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [&sizeCheck2Ok](uint64_t /*nowMs*/, size_t maxLength)\n\t\t    {\n\t\t\t    sizeCheck2Ok = (maxLength == 976 - RTC::SCTP::DataChunk::DataChunkHeaderLength);\n\n\t\t\t    std::vector<uint8_t> payload(957, 0x00);\n\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      1, RTC::SCTP::UserData(1, 0, 0, 0, 53, std::move(payload), true, true, false));\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, AvailableBytes) == std::vector<uint32_t>{ 10, 11 });\n\t\tREQUIRE(sizeCheck1Ok == true);\n\t\tREQUIRE(sizeCheck2Ok == true);\n\t}\n\n\tSECTION(\"accounts nacked abandoned chunks as not outstanding\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconst size_t chunkSerializedLength = RTC::SCTP::DataChunk::DataChunkHeaderLength + 4;\n\n\t\tREQUIRE(chunkSerializedLength == 16 + 4);\n\n\t\t// Three middle fragments of the same message, maxRetransmissions=0.\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.maxRetransmissions = 0;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10, 11, 12 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == chunkSerializedLength * 3);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 3);\n\n\t\t// Mark the message as lost.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED },\n    });\n\t\t// Abandoned chunks are not counted as outstanding.\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\n\t\t// Acking abandoned chunks one by one changes nothing in the counters.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0);\n\t\tREQUIRE(retransmissionQueue.GetUnackedItems() == 0);\n\t}\n\n\tSECTION(\"expire from send queue when partially sent\")\n\t{\n\t\t// Two fragments on stream 17, outgoingMessageId=42. First is produced and\n\t\t// goes in flight. After nowMs advances past `expiresAtMs`, the second is\n\t\t// produced but expired on Insert() -> first also abandoned, `Discard()`\n\t\t// called (returns true -> placeholder TSN 12).\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconst uint64_t expiresAtMs = nowMs + 10;\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(17, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(17, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\t// First `GetChunksToSend()` produces TSN 10 (nowMs < expiresAtMs).\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 24) == std::vector<uint32_t>{ 10 });\n\n\t\t// Advance past expiry.\n\t\tnowMs += 100;\n\n\t\t// `Discard()` called for TSN 11 (unsent tail) -> returns true -> placeholder\n\t\t// TSN 12.\n\t\tsendQueue.WillDiscardOnce(17, 42, /*returnValue*/ true);\n\n\t\t// Second `GetChunksToSend()` produces TSN 11 but now > expiresAtMs ->\n\t\t// abandoned on `Insert()`, TSN 10 also abandoned, placeholder TSN 12\n\t\t// created.\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24).empty());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     }, // Initial TSN.\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, // Produced and in-flight.\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, // Produced and expired.\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, // Placeholder end.\n    });\n\t}\n\n\tSECTION(\"expire correct message from send queue\")\n\t{\n\t\t// Three messages on stream 1. Messages 42 (mid=0) and 43 (mid=1) are\n\t\t// complete single-fragment messages. Message 44 (mid=0, stream reset)\n\t\t// has a beginning fragment produced before expiry and a middle fragment\n\t\t// produced after expiry -> message 44 gets abandoned, messages 42 and 43\n\t\t// remain IN_FLIGHT.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconst uint64_t expiresAtMs = nowMs + 10;\n\n\t\t// outgoingMessageId=42, mid=0, \"BE\" — complete message.\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // outgoingMessageId=43, mid=1, \"BE\" — complete message.\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 1, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(43, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // outgoingMessageId=44, mid=0 (stream reset), \"B\" — beginning only.\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  // outgoingMessageId=44, mid=0, middle fragment (produced after expiry).\n\t\t  .WillProduceOnce(\n\t\t    [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false);\n\t\t\t    RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data));\n\n\t\t\t    dataToSend.expiresAtMs = expiresAtMs;\n\n\t\t\t    return dataToSend;\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tstd::vector<std::pair<uint32_t, RTC::SCTP::UserData>> expectedChunksToSend;\n\n\t\t// TSN 10, msgId=42.\n\t\texpectedChunksToSend.emplace_back(\n\t\t  10,\n\t\t  RTC::SCTP::UserData{\n\t\t    1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 },\n             true, true, false\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend);\n\n\t\t// TSN 11, msgId=43.\n\t\texpectedChunksToSend.clear();\n\t\texpectedChunksToSend.emplace_back(\n\t\t  11,\n\t\t  RTC::SCTP::UserData{\n\t\t    1, 0, 1, 0, 53, { 0x01, 0x02, 0x03, 0x04 },\n             true, true, false\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend);\n\n\t\t// TSN 12, msgId=44 \"B\"\n\t\texpectedChunksToSend.clear();\n\t\texpectedChunksToSend.emplace_back(\n\t\t  12,\n\t\t  RTC::SCTP::UserData{\n\t\t    1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 },\n             true, false, false\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend);\n\n\t\t// Advance past expiry.\n\t\tnowMs += 100;\n\n\t\t// `Discard()` called for message 44 (unsent middle fragment), returns true\n\t\t// -> placeholder TSN 14 created.\n\t\tsendQueue.WillDiscardOnce(1, 44, /*returnValue*/ true);\n\n\t\t// Fourth call produces TSN 13 (middle of message 44) but it's now expired\n\t\t// -> TSN 12 and 13 abandoned, placeholder TSN 14 created.\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24).empty());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     }, // Initial TSN.\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, // msgId=42, BE.\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, // msgId=43, BE.\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, // msgId=44, B.\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, // msgId=44, produced and expired.\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, // Placeholder end.\n    });\n\t}\n\n\tSECTION(\"limits retransmissions only when nacked three times\")\n\t{\n\t\t// A chunk with maxRetransmissions=0 is NOT abandoned immediately when\n\t\t// nacked — it takes exactly three nacks like any other chunk, and is\n\t\t// abandoned on the third (not retransmitted, since maxRetransmissions=0).\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\t// TSN 10: maxRetransmissions=0.\n\t\tsendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0))\n\t\t  .WillProduceOnce(createDataToSend(0)) // TSN 11.\n\t\t  .WillProduceOnce(createDataToSend(1)) // TSN 12.\n\t\t  .WillProduceOnce(createDataToSend(2)) // TSN 13.\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10, 11, 12, 13 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\t// `Discard()` must NOT be called for the first two nacks.\n\t\tsendQueue.ExpectDiscardCalledTimes(0);\n\n\t\t// First nack for TSN 10.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 2 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED    },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\t// Second nack for TSN 10.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 3 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED    },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\n\t\t// Third nack -> TSN 10 abandoned (maxRetransmissions=0 means 0\n\t\t// retransmits).\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 4 },\n    })\n\t\t    .get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\t}\n\n\tSECTION(\"abandons rtx limit 2 when nacked nine times\")\n\t{\n\t\t// maxRetransmits=2 for TSN 10: first 3 nacks -> fast-retransmit #1;\n\t\t// next 3 nacks -> regular retransmit #2; next 3 nacks -> abandoned.\n\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\t// TSN 10: maxRetransmissions=2.\n\t\tsendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 2))\n\t\t  .WillProduceOnce(createDataToSend(0)) // TSN 11.\n\t\t  .WillProduceOnce(createDataToSend(1)) // TSN 12.\n\t\t  .WillProduceOnce(createDataToSend(2)) // TSN 13.\n\t\t  .WillProduceOnce(createDataToSend(3)) // TSN 14.\n\t\t  .WillProduceOnce(createDataToSend(4)) // TSN 15.\n\t\t  .WillProduceOnce(createDataToSend(5)) // TSN 16.\n\t\t  .WillProduceOnce(createDataToSend(6)) // TSN 17.\n\t\t  .WillProduceOnce(createDataToSend(7)) // TSN 18.\n\t\t  .WillProduceOnce(createDataToSend(8)) // TSN 19.\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue) ==\n\t\t  std::vector<uint32_t>{ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// No `Discard()` calls during nack rounds 1 and 2.\n\t\tsendQueue.ExpectDiscardCalledTimes(0);\n\n\t\t// Ack TSN 11-13 — three nacks for TSN 10 -> TO_BE_RETRANSMITTED.\n\t\tfor (uint32_t tsn{ 11 }; tsn <= 13; ++tsn)\n\t\t{\n\t\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t\t  nowMs,\n\t\t\t  createSackChunk(\n\t\t\t    9,\n\t\t\t    Arwnd,\n\t\t\t    {\n\t\t\t      { 2, static_cast<uint16_t>(tsn - 9) }\n      })\n\t\t\t    .get());\n\t\t}\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n    });\n\n\t\t// Fast retransmit #1.\n\t\tREQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector<uint32_t>{ 10 });\n\n\t\t// Ack TSN 14-16 — three more nacks -> retransmit #2 (TO_BE_RETRANSMITTED).\n\t\tfor (uint32_t tsn{ 14 }; tsn <= 16; ++tsn)\n\t\t{\n\t\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t\t  nowMs,\n\t\t\t  createSackChunk(\n\t\t\t    9,\n\t\t\t    Arwnd,\n\t\t\t    {\n\t\t\t      { 2, static_cast<uint16_t>(tsn - 9) }\n      })\n\t\t\t    .get());\n\t\t}\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ACKED               },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT           },\n    });\n\n\t\t// Regular retransmit #2.\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector<uint32_t>{ 10 });\n\n\t\t// Ack TSN 17-18 — two more nacks (TSN 10 is now in-flight again after retransmit).\n\t\tfor (uint32_t tsn{ 17 }; tsn <= 18; ++tsn)\n\t\t{\n\t\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t\t  nowMs,\n\t\t\t  createSackChunk(\n\t\t\t    9,\n\t\t\t    Arwnd,\n\t\t\t    {\n\t\t\t      { 2, static_cast<uint16_t>(tsn - 9) }\n      })\n\t\t\t    .get());\n\t\t}\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::NACKED    },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false);\n\n\t\tREQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations());\n\n\t\t// Ack TSN 19 — third nack; numRetransmissions(2) == maxRetransmissions(2)\n\t\t// -> ABANDON.\n\t\tsendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false);\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    Arwnd,\n\t\t    {\n\t\t      { 2, 10 }\n    })\n\t\t    .get());\n\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).empty());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ABANDONED },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 15, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 16, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 17, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 18, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 19, RTC::SCTP::OutstandingData::State::ACKED     },\n    });\n\n\t\tREQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true);\n\t}\n\n\tSECTION(\"cwnd recovers when acking\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconstexpr size_t Cwnd{ 1200 };\n\n\t\tretransmissionQueue.SetCwnd(Cwnd);\n\n\t\tREQUIRE(retransmissionQueue.GetCwnd() == Cwnd);\n\n\t\tconst std::vector<uint8_t> payload(1000, 0x00);\n\t\tconst size_t chunkSerializedLength = RTC::SCTP::DataChunk::DataChunkHeaderLength + payload.size();\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, true, false));\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector<uint32_t>{ 10 });\n\t\tREQUIRE(retransmissionQueue.GetUnackedPacketBytes() == chunkSerializedLength);\n\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get());\n\n\t\tREQUIRE(retransmissionQueue.GetCwnd() == Cwnd + chunkSerializedLength);\n\t}\n\n\tSECTION(\"can always send one packet\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tconst size_t mtu{ Utils::Byte::PadDownTo4Bytes(Mtu) }; // 1188.\n\t\tconst std::vector<uint8_t> payload(mtu - 100, 0x00);   // 1088 bytes.\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t, size_t)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, false, false));\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t, size_t)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false));\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t, size_t)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false));\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t, size_t)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false));\n\t\t    })\n\t\t  .WillProduceOnce(\n\t\t    [&payload](uint64_t, size_t)\n\t\t    {\n\t\t\t    return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t      0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, true, false));\n\t\t    })\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\t// Produce all 5 chunks (TSN 10-14) in one call.\n\t\tREQUIRE(\n\t\t  getSentPacketTSNs(retransmissionQueue, 5 * mtu) == std::vector<uint32_t>{ 10, 11, 12, 13, 14 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\t// Ack 12, and report an empty receiver window (the peer obviously has a\n\t\t// tiny receive window).\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    /*aRwnd*/ 0,\n\t\t    {\n\t\t      { 3, 3 }\n    })\n\t\t    .get());\n\n\t\t// Force TSN 10 to be retransmitted.\n\t\tretransmissionQueue.HandleT3RtxTimerExpiry();\n\n\t\t// Even with rwnd=0, one packet can be sent (no in-flight data after NackAll).\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector<uint32_t>{ 10 });\n\n\t\t// But not a second one — TSN 10 is now in-flight again.\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty());\n\n\t\t// Still rwnd=0, TSN 10 in-flight.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    9,\n\t\t    /*aRwnd=*/0,\n\t\t    {\n\t\t      { 3, 3 }\n    })\n\t\t    .get());\n\n\t\t// There is in-flight data, so new data should not be allowed to be send since\n\t\t// the receiver window is full.\n\t\tREQUIRE(retransmissionQueue.GetChunksToSend(nowMs, mtu).empty());\n\n\t\t// Ack TSN 10 (no more in-flight data), still rwnd=0.\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs,\n\t\t  createSackChunk(\n\t\t    10,\n\t\t    /*aRwnd=*/0,\n\t\t    {\n\t\t      { 2, 2 }\n    })\n\t\t    .get());\n\n\t\t// TSN 11 can be sent since there is no in-flight data.\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector<uint32_t>{ 11 });\n\n\t\t// But not a second one.\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty());\n\n\t\t// Ack and recover the receiver window\n\t\tretransmissionQueue.HandleReceivedSackChunk(\n\t\t  nowMs, createSackChunk(12, static_cast<uint32_t>(5 * mtu)).get());\n\n\t\t// Remaining TO_BE_RETRANSMITTED chunks can now be sent.\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector<uint32_t>{ 13 });\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector<uint32_t>{ 14 });\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty());\n\t}\n\n\tSECTION(\"updates rwnd from SACK and unacked payload bytes\")\n\t{\n\t\tauto retransmissionQueue = createRetransmissionQueue();\n\n\t\tREQUIRE(retransmissionQueue.GetRwnd() == Arwnd);\n\n\t\t// Payload is 4 bytes (padded to 4).\n\t\tconstexpr size_t PayloadSize{ 4 };\n\n\t\tsendQueue\n\t\t  .WillProduceOnce(createDataToSend(0)) // TSN 10.\n\t\t  .WillProduceOnce(createDataToSend(1)) // TSN 11.\n\t\t  .WillProduceOnce(createDataToSend(2)) // TSN 12.\n\t\t  .WillProduceRepeatedly(\n\t\t    [](uint64_t, size_t)\n\t\t    {\n\t\t\t    return std::nullopt;\n\t\t    });\n\n\t\tREQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector<uint32_t>{ 10, 11, 12 });\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 9,  RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetRwnd() == Arwnd - (PayloadSize * 3));\n\n\t\t// Ack TSN 10, new aRwnd=1000.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, 1000).get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 10, RTC::SCTP::OutstandingData::State::ACKED     },\n\t\t    { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n\t\t    { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT },\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetRwnd() == 1000 - (PayloadSize * 2));\n\n\t\t// Ack everything, new aRwnd=2000.\n\t\tretransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, 2000).get());\n\n\t\tREQUIRE(\n\t\t  retransmissionQueue.GetChunkStatesForTesting() ==\n\t\t  std::vector<std::pair<uint32_t /*tsn*/, RTC::SCTP::OutstandingData::State>>{\n\t\t    { 12, RTC::SCTP::OutstandingData::State::ACKED },\n    });\n\n\t\tREQUIRE(retransmissionQueue.GetRwnd() == 2000);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestRetransmissionTimeout.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/RetransmissionTimeout.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP RetransmissionTimeout\", \"[sctp][retransmissiontimeout]\")\n{\n\tconstexpr uint64_t MaxRttMs{ 8000 };\n\tconstexpr uint64_t InitialRtoMs{ 200 };\n\tconstexpr uint64_t MaxRtoMs{ 800 };\n\tconstexpr uint64_t MinRtoMs{ 120 };\n\tconstexpr uint64_t MinRttVarianceMs{ 220 };\n\n\t// NOTE: No need to pass const integers to the lambda.\n\tauto makeSctpOptions = []()\n\t{\n\t\tRTC::SCTP::SctpOptions sctpOptions{ .maxRttMs         = MaxRttMs,\n\t\t\t                                  .initialRtoMs     = InitialRtoMs,\n\t\t\t                                  .minRtoMs         = MinRtoMs,\n\t\t\t                                  .maxRtoMs         = MaxRtoMs,\n\t\t\t                                  .minRttVarianceMs = MinRttVarianceMs };\n\n\t\treturn sctpOptions;\n\t};\n\n\tSECTION(\"has valid initial RTO\")\n\t{\n\t\tconst RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\tREQUIRE(rto.GetRtoMs() == InitialRtoMs);\n\t}\n\n\tSECTION(\"too large values don't affect RTO\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\trto.ObserveRttMs(MaxRttMs + 100);\n\n\t\tREQUIRE(rto.GetRtoMs() == InitialRtoMs);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 372);\n\n\t\trto.ObserveRttMs(MaxRttMs + 100);\n\n\t\tREQUIRE(rto.GetRtoMs() == 372);\n\t}\n\n\tSECTION(\"will never go below minimum RTO\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\tfor (int i{ 0 }; i < 1000; ++i)\n\t\t{\n\t\t\trto.ObserveRttMs(1);\n\t\t}\n\n\t\tREQUIRE(rto.GetRtoMs() <= MinRtoMs);\n\t}\n\n\tSECTION(\"will never go above maximum RTO\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\tfor (int i{ 0 }; i < 1000; ++i)\n\t\t{\n\t\t\trto.ObserveRttMs(MaxRttMs - 1);\n\t\t\t// Adding jitter, which would make it RTO be well above RTT.\n\t\t\trto.ObserveRttMs(MaxRttMs - 100);\n\t\t}\n\n\t\tREQUIRE(rto.GetRtoMs() >= MaxRtoMs);\n\t}\n\n\tSECTION(\"calculates RTO for stable RTT\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 372);\n\n\t\trto.ObserveRttMs(128);\n\n\t\tREQUIRE(rto.GetRtoMs() == 315);\n\n\t\trto.ObserveRttMs(123);\n\n\t\tREQUIRE(rto.GetRtoMs() == 268);\n\n\t\trto.ObserveRttMs(125);\n\n\t\t// NOTE: This should be 234 (as per same test in libwebrtc) but we are not\n\t\t// that precise.\n\t\t// REQUIRE(rto.GetRtoMs() == 234);\n\t\tREQUIRE(rto.GetRtoMs() == 233);\n\n\t\trto.ObserveRttMs(127);\n\n\t\t// NOTE: This should be 235 (as per same test in libwebrtc) but we are not\n\t\t// that precise.\n\t\t// REQUIRE(rto.GetRtoMs() == 235);\n\t\tREQUIRE(rto.GetRtoMs() == 233);\n\t}\n\n\tSECTION(\"calculates RTO for unstable RTT\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 372);\n\n\t\trto.ObserveRttMs(402);\n\n\t\tREQUIRE(rto.GetRtoMs() == 623);\n\n\t\trto.ObserveRttMs(728);\n\n\t\tREQUIRE(rto.GetRtoMs() == 800);\n\n\t\trto.ObserveRttMs(89);\n\n\t\tREQUIRE(rto.GetRtoMs() == 800);\n\n\t\trto.ObserveRttMs(126);\n\n\t\tREQUIRE(rto.GetRtoMs() == 800);\n\t}\n\n\tSECTION(\"will stabilize RTO after a while\")\n\t{\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\trto.ObserveRttMs(124);\n\t\trto.ObserveRttMs(402);\n\t\trto.ObserveRttMs(728);\n\t\trto.ObserveRttMs(89);\n\t\trto.ObserveRttMs(126);\n\n\t\tREQUIRE(rto.GetRtoMs() == 800);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 800);\n\n\t\trto.ObserveRttMs(122);\n\n\t\tREQUIRE(rto.GetRtoMs() == 709);\n\n\t\trto.ObserveRttMs(123);\n\n\t\tREQUIRE(rto.GetRtoMs() == 630);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 562);\n\n\t\trto.ObserveRttMs(122);\n\n\t\tREQUIRE(rto.GetRtoMs() == 505);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 454);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 410);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 372);\n\n\t\trto.ObserveRttMs(124);\n\n\t\tREQUIRE(rto.GetRtoMs() == 340);\n\t}\n\n\tSECTION(\"will always stay above RTT\")\n\t{\n\t\t// In simulations, it's quite common to have a very stable RTT, and having\n\t\t// an RTO at the same value will cause issues as expiry timers will be\n\t\t// scheduled to be expire exactly when a packet is supposed to arrive. The\n\t\t// RTO must be larger than the RTT. In non-simulated environments, this is\n\t\t// a non-issue as any jitter will increase the RTO.\n\n\t\tRTC::SCTP::RetransmissionTimeout rto(makeSctpOptions());\n\n\t\tfor (int i{ 0 }; i < 1000; ++i)\n\t\t{\n\t\t\trto.ObserveRttMs(124);\n\t\t}\n\n\t\t// NOTE: This should be 234 (as per same test in libwebrtc) but we are not\n\t\t// that precise.\n\t\t// REQUIRE(rto.GetRtoMs() == 234);\n\t\tREQUIRE(rto.GetRtoMs() == 232);\n\t}\n\n\tSECTION(\"can specify smaller minimum RTT variance\")\n\t{\n\t\tauto sctpOptions = makeSctpOptions();\n\n\t\tsctpOptions.minRttVarianceMs = MinRttVarianceMs - 100;\n\n\t\tRTC::SCTP::RetransmissionTimeout rto(sctpOptions);\n\n\t\tfor (int i{ 0 }; i < 1000; ++i)\n\t\t{\n\t\t\trto.ObserveRttMs(124);\n\t\t}\n\n\t\tREQUIRE(rto.GetRtoMs() == 184);\n\t}\n\n\tSECTION(\"can specify larger minimum RTT variance\")\n\t{\n\t\tauto sctpOptions = makeSctpOptions();\n\n\t\tsctpOptions.minRttVarianceMs = MinRttVarianceMs + 100;\n\n\t\tRTC::SCTP::RetransmissionTimeout rto(sctpOptions);\n\n\t\tfor (int i{ 0 }; i < 1000; ++i)\n\t\t{\n\t\t\trto.ObserveRttMs(124);\n\t\t}\n\n\t\tREQUIRE(rto.GetRtoMs() == 284);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestRoundRobinSendQueue.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/RTC/SCTP/association/MockAssociationListener.hpp\"\n#include \"RTC/SCTP/public/Message.hpp\"\n#include \"RTC/SCTP/public/SctpOptions.hpp\"\n#include \"RTC/SCTP/tx/RoundRobinSendQueue.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <ranges>\n#include <vector>\n\nSCENARIO(\"SCTP RoundRobinSendQueue\", \"[sctp][roundrobinsendqueue]\")\n{\n\tconstexpr size_t Mtu{ 1100 };\n\tconstexpr uint64_t NowMs{ 0 };\n\tconstexpr uint16_t StreamId{ 1 };\n\tconstexpr uint32_t Ppid{ 53 };\n\tconstexpr uint16_t DefaultPriority{ 10 };\n\tconstexpr size_t BufferedAmountLowThreshold{ 500 };\n\tconstexpr size_t OneFragmentPacketLength{ 100 };\n\tconstexpr size_t TwoFragmentPacketLength{ 101 };\n\n\tSECTION(\"empty buffer\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tREQUIRE(q.IsEmpty());\n\t\tREQUIRE(q.Produce(NowMs, OneFragmentPacketLength).has_value() == false);\n\t}\n\n\tSECTION(\"add and get single chunk\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, { 1, 2, 4, 5, 6 }));\n\n\t\tREQUIRE(!q.IsEmpty());\n\n\t\tconst auto dataToSend = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSend.has_value());\n\t\tREQUIRE(dataToSend->data.IsBeginning());\n\t\tREQUIRE(dataToSend->data.IsEnd());\n\t}\n\n\tSECTION(\"carve out beginning middle and end\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(60);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendBeg = q.Produce(NowMs, /*maxLength*/ 20);\n\n\t\tREQUIRE(dataToSendBeg.has_value());\n\t\tREQUIRE(dataToSendBeg->data.IsBeginning());\n\t\tREQUIRE(!dataToSendBeg->data.IsEnd());\n\n\t\tconst auto dataToSendMid = q.Produce(NowMs, /*maxLength*/ 20);\n\n\t\tREQUIRE(dataToSendMid.has_value());\n\t\tREQUIRE(!dataToSendMid->data.IsBeginning());\n\t\tREQUIRE(!dataToSendMid->data.IsEnd());\n\n\t\tconst auto dataToSendEnd = q.Produce(NowMs, /*maxLength*/ 20);\n\n\t\tREQUIRE(dataToSendEnd.has_value());\n\t\tREQUIRE(!dataToSendEnd->data.IsBeginning());\n\t\tREQUIRE(dataToSendEnd->data.IsEnd());\n\n\t\tREQUIRE(q.Produce(NowMs, OneFragmentPacketLength).has_value() == false);\n\t}\n\n\tSECTION(\"get chunks from two messages\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(60);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, 54, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadProtocolId() == Ppid);\n\t\tREQUIRE(dataToSendOne->data.IsBeginning());\n\t\tREQUIRE(dataToSendOne->data.IsEnd());\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadProtocolId() == 54);\n\t\tREQUIRE(dataToSendTwo->data.IsBeginning());\n\t\tREQUIRE(dataToSendTwo->data.IsEnd());\n\t}\n\n\tSECTION(\"buffer becomes full and emptied\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(600);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() < 1000);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() < 1000);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, 54, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() >= 1000);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(5, 55, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() >= 1000);\n\n\t\tauto dataToSendOne = q.Produce(NowMs, 1000);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadProtocolId() == Ppid);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() >= 1000);\n\n\t\tauto dataToSendTwo = q.Produce(NowMs, 1000);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadProtocolId() == 54);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() < 1000);\n\t\tREQUIRE(!q.IsEmpty());\n\n\t\tauto dataToSendThree = q.Produce(NowMs, 1000);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 5);\n\t\tREQUIRE(dataToSendThree->data.GetPayloadProtocolId() == 55);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() < 1000);\n\t\tREQUIRE(q.IsEmpty());\n\t}\n\n\tSECTION(\"defaults to ordered send\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(20);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(!dataToSendOne->data.IsUnordered());\n\n\t\tRTC::SCTP::SendMessageOptions options;\n\n\t\toptions.unordered = true;\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload), options);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.IsUnordered());\n\t}\n\n\tSECTION(\"produce with lifetime expiry\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(20);\n\n\t\tuint64_t now = NowMs;\n\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tnow += 1000000;\n\n\t\tREQUIRE(q.Produce(now, OneFragmentPacketLength).has_value());\n\n\t\tRTC::SCTP::SendMessageOptions expires2s;\n\n\t\texpires2s.lifetimeMs = 2000;\n\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s);\n\n\t\tnow += 2000;\n\n\t\tREQUIRE(q.Produce(now, OneFragmentPacketLength).has_value());\n\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s);\n\n\t\tnow += 2001;\n\n\t\tREQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value());\n\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s);\n\n\t\tnow += 1000000;\n\n\t\tREQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value());\n\n\t\tRTC::SCTP::SendMessageOptions expires4s;\n\n\t\texpires4s.lifetimeMs = 4000;\n\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s);\n\t\tq.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires4s);\n\n\t\tnow += 2001;\n\n\t\tREQUIRE(q.Produce(now, OneFragmentPacketLength).has_value());\n\t\tREQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value());\n\t}\n\n\tSECTION(\"discard partial packets\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(120);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, 54, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(!dataToSendOne->data.IsEnd());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\n\t\tq.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(!dataToSendTwo->data.IsEnd());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 2);\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.IsEnd());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 2);\n\n\t\tREQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value());\n\n\t\tq.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId);\n\n\t\tREQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value());\n\t}\n\n\tSECTION(\"prepare reset streams discards stream\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, { 1, 2, 3 }));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, 54, { 1, 2, 3, 4, 5 }));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 8);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 5);\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end());\n\n\t\tq.CommitResetStreams();\n\t\tq.PrepareResetStream(2);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 0);\n\t}\n\n\tSECTION(\"prepare reset streams not partial packets\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(120);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, 50);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == (2 * payload.size()) - 50);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size() - 50);\n\t}\n\n\tSECTION(\"enqueued items are paused during stream reset\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(50);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 0);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size());\n\n\t\tREQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value());\n\n\t\tREQUIRE(q.HasStreamsReadyToBeReset());\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end());\n\n\t\tREQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value());\n\n\t\tq.CommitResetStreams();\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size());\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, 50);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 0);\n\t}\n\n\tSECTION(\"paused streams still send partial messages until end\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst size_t payloadLength  = 100;\n\t\tconst size_t fragmentLength = 50;\n\n\t\tconst std::vector<uint8_t> payload(payloadLength);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, fragmentLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == (2 * payloadLength) - fragmentLength);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payloadLength - fragmentLength);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, fragmentLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == StreamId);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == 0);\n\n\t\tREQUIRE(!q.Produce(NowMs, fragmentLength).has_value());\n\t}\n\n\tSECTION(\"committing resets SSN\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(50);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tREQUIRE(q.HasStreamsReadyToBeReset());\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end());\n\n\t\tq.CommitResetStreams();\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 0);\n\t}\n\n\tSECTION(\"committing does not reset message id\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(50);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(dataToSendOne->outgoingMessageId == 0);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1);\n\t\tREQUIRE(dataToSendTwo->outgoingMessageId == 1);\n\n\t\tq.PrepareResetStream(StreamId);\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end());\n\n\t\tq.CommitResetStreams();\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 0);\n\t\tREQUIRE(dataToSendThree->outgoingMessageId == 2);\n\t}\n\n\tSECTION(\"committing resets SSN for paused streams only\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(50);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 0);\n\n\t\tq.PrepareResetStream(3);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, payload));\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, 3) != streamsReadyToBeReset.end());\n\n\t\tq.CommitResetStreams();\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 1);\n\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendFour->data.GetStreamSequenceNumber() == 0);\n\t}\n\n\tSECTION(\"rollback resumes SSN\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(50);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1);\n\n\t\tq.PrepareResetStream(1);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tconst auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset();\n\n\t\tREQUIRE(streamsReadyToBeReset.size() == 1);\n\t\tREQUIRE(std::ranges::find(streamsReadyToBeReset, 1) != streamsReadyToBeReset.end());\n\n\t\tq.RollbackResetStreams();\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 2);\n\t}\n\n\tSECTION(\"returns fragments for one message before moving to next\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(200);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 1);\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 2);\n\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetStreamId() == 2);\n\t}\n\n\tSECTION(\"returns also small fragments before moving to next\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(TwoFragmentPacketLength);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == OneFragmentPacketLength);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 1);\n\t\tREQUIRE(\n\t\t  dataToSendTwo->data.GetPayloadLength() == TwoFragmentPacketLength - OneFragmentPacketLength);\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 2);\n\t\tREQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketLength);\n\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetStreamId() == 2);\n\t\tREQUIRE(\n\t\t  dataToSendFour->data.GetPayloadLength() == TwoFragmentPacketLength - OneFragmentPacketLength);\n\t}\n\n\tSECTION(\"will cycle in round robin fashion between streams\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(1)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(2)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector<uint8_t>(3)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector<uint8_t>(4)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector<uint8_t>(5)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector<uint8_t>(6)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(4, Ppid, std::vector<uint8_t>(7)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(4, Ppid, std::vector<uint8_t>(8)));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 1);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 2);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadLength() == 3);\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendThree->data.GetPayloadLength() == 5);\n\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetStreamId() == 4);\n\t\tREQUIRE(dataToSendFour->data.GetPayloadLength() == 7);\n\n\t\tconst auto dataToSendFive = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendFive.has_value());\n\t\tREQUIRE(dataToSendFive->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendFive->data.GetPayloadLength() == 2);\n\n\t\tconst auto dataToSendSix = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendSix.has_value());\n\t\tREQUIRE(dataToSendSix->data.GetStreamId() == 2);\n\t\tREQUIRE(dataToSendSix->data.GetPayloadLength() == 4);\n\n\t\tconst auto dataToSendSeven = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendSeven.has_value());\n\t\tREQUIRE(dataToSendSeven->data.GetStreamId() == 3);\n\t\tREQUIRE(dataToSendSeven->data.GetPayloadLength() == 6);\n\n\t\tconst auto dataToSendEight = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendEight.has_value());\n\t\tREQUIRE(dataToSendEight->data.GetStreamId() == 4);\n\t\tREQUIRE(dataToSendEight->data.GetPayloadLength() == 8);\n\t}\n\n\tSECTION(\"doesn't trigger stream buffered amount low when set to zero\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.SetStreamBufferedAmountLowThreshold(1, 0);\n\n\t\tREQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t}\n\n\tSECTION(\"triggers stream buffered amount low when sent\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(1)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 1);\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 1);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 0);\n\t}\n\n\tSECTION(\"will retrigger stream buffered amount low if adding more\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(1)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 1);\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(1)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 1);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 2);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 1);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 0);\n\t}\n\n\tSECTION(\"only triggers stream buffered amount low when transitioning from above to below or equal\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.SetStreamBufferedAmountLowThreshold(1, 1000);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(10)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 10);\n\n\t\t// Shouldn't trigger the event.\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 10);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 0);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(20)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 20);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadLength() == 20);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 0);\n\t}\n\n\tSECTION(\"will trigger stream buffered amount low set above zero\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.SetStreamBufferedAmountLowThreshold(1, 700);\n\n\t\tconst std::vector<uint8_t> payload(1000);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == OneFragmentPacketLength);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 900);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadLength() == OneFragmentPacketLength);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 800);\n\n\t\t// It goes beyond 700 bytes, it should trigger the event.\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketLength);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 700);\n\n\t\t// Buffer decreases so it shouldn't emit the event.\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetPayloadLength() == OneFragmentPacketLength);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 600);\n\t}\n\n\tSECTION(\"will retrigger stream buffered amount low set above zero\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.SetStreamBufferedAmountLowThreshold(1, 700);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(1000)));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, 400);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 400);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 600);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(200)));\n\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 800);\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, 200);\n\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 2);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadLength() == 200);\n\t\tREQUIRE(q.GetStreamBufferedAmount(1) == 600);\n\t}\n\n\tSECTION(\"triggers stream buffered amount low on threshold changed\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector<uint8_t>(100)));\n\n\t\t// Modifying the threshold, still under buffered_amount, should not trigger\n\t\t// event.\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 50);\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 99);\n\n\t\tREQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(StreamId));\n\n\t\t// When the threshold reaches buffered_amount, it will trigger event.\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 100);\n\n\t\tREQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(StreamId));\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 1);\n\n\t\t// But not when it's set low again.\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 50);\n\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 1);\n\n\t\t// But it will trigger when it overshoots.\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 150);\n\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 2);\n\n\t\t// But not when it's set back to zero.\n\t\tq.SetStreamBufferedAmountLowThreshold(StreamId, 0);\n\n\t\tREQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 2);\n\t}\n\n\tSECTION(\"total buffered amount low does not trigger on buffer filling up\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(BufferedAmountLowThreshold - 1);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size());\n\n\t\t// Will not trigger if going above but never below.\n\t\tq.AddMessage(\n\t\t  NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector<uint8_t>(OneFragmentPacketLength)));\n\n\t\tREQUIRE(associationListener.CountOnTotalBufferedAmountLowCalls() == 0);\n\t\tREQUIRE(q.GetTotalBufferedAmount() > payload.size());\n\t}\n\n\tSECTION(\"triggers total buffered amount low when crossing\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(BufferedAmountLowThreshold);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size());\n\n\t\t// Reaches it.\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector<uint8_t>(1)));\n\n\t\t// Drain it a bit, will trigger.\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(associationListener.CountOnTotalBufferedAmountLowCalls() == 1);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(q.GetTotalBufferedAmount() < BufferedAmountLowThreshold);\n\t}\n\n\tSECTION(\"will stay in a stream as long as that message is sending\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconstexpr size_t OneFragmentPacketSize = OneFragmentPacketLength;\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(5, Ppid, std::vector<uint8_t>(1)));\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketSize);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 5);\n\t\tREQUIRE(dataToSendOne->data.GetPayloadLength() == 1);\n\n\t\t// Next, it should pick a different stream.\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(OneFragmentPacketSize * 2)));\n\n\t\tconst auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketSize);\n\n\t\tREQUIRE(dataToSendTwo.has_value());\n\t\tREQUIRE(dataToSendTwo->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendTwo->data.GetPayloadLength() == OneFragmentPacketSize);\n\n\t\t// It should still stay on the Stream1 now, even if might be tempted to switch\n\t\t// to this stream, as it's the stream following 5.\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(6, Ppid, std::vector<uint8_t>(1)));\n\n\t\tconst auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketSize);\n\n\t\tREQUIRE(dataToSendThree.has_value());\n\t\tREQUIRE(dataToSendThree->data.GetStreamId() == 1);\n\t\tREQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketSize);\n\n\t\t// After stream 1 message is complete, it should move to stream 6.\n\t\tconst auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketSize);\n\n\t\tREQUIRE(dataToSendFour.has_value());\n\t\tREQUIRE(dataToSendFour->data.GetStreamId() == 6);\n\t\tREQUIRE(dataToSendFour->data.GetPayloadLength() == 1);\n\n\t\tREQUIRE(q.Produce(NowMs, OneFragmentPacketSize).has_value() == false);\n\t}\n\n\tSECTION(\"streams have initial priority\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tREQUIRE(q.GetStreamPriority(1) == DefaultPriority);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector<uint8_t>(40)));\n\n\t\tREQUIRE(q.GetStreamPriority(2) == DefaultPriority);\n\t}\n\n\tSECTION(\"can change stream priority\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.SetStreamPriority(1, 42);\n\n\t\tREQUIRE(q.GetStreamPriority(1) == 42);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector<uint8_t>(40)));\n\t\tq.SetStreamPriority(2, 42);\n\n\t\tREQUIRE(q.GetStreamPriority(2) == 42);\n\t}\n\n\tSECTION(\"will send messages by priority\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tq.EnableMessageInterleaving(true);\n\n\t\tq.SetStreamPriority(1, 10);\n\t\tq.SetStreamPriority(2, 20);\n\t\tq.SetStreamPriority(3, 30);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector<uint8_t>(40)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector<uint8_t>(20)));\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector<uint8_t>(10)));\n\n\t\tconst std::vector<uint16_t> expectedStreams = { 3, 2, 2, 1, 1, 1, 1 };\n\n\t\tfor (const uint16_t streamId : expectedStreams)\n\t\t{\n\t\t\tconst auto dataToSend = q.Produce(NowMs, 10);\n\n\t\t\tREQUIRE(dataToSend.has_value());\n\t\t\tREQUIRE(dataToSend->data.GetStreamId() == streamId);\n\t\t}\n\n\t\tREQUIRE(q.Produce(NowMs, 1).has_value() == false);\n\t}\n\n\tSECTION(\"will send lifecycle expire when expired in send queue\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(OneFragmentPacketLength);\n\n\t\tRTC::SCTP::SendMessageOptions options;\n\n\t\toptions.lifetimeMs  = 1000;\n\t\toptions.lifecycleId = 1;\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload), options);\n\n\t\tREQUIRE(q.Produce(NowMs + 1001, OneFragmentPacketLength).has_value() == false);\n\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(\n\t\t  1, false));\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1));\n\t}\n\n\tSECTION(\"will send lifecycle expire when discarding during pause\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(120);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 1 });\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 2 });\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, 50);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == (2 * payload.size()) - 50);\n\n\t\tq.PrepareResetStream(1);\n\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(\n\t\t  2, false));\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(2));\n\n\t\tREQUIRE(q.GetTotalBufferedAmount() == payload.size() - 50);\n\t}\n\n\tSECTION(\"will send lifecycle expire when discarding explicitly\")\n\t{\n\t\tmocks::RTC::SCTP::MockAssociationListener associationListener;\n\t\tRTC::SCTP::RoundRobinSendQueue q(\n\t\t  associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold);\n\n\t\tconst std::vector<uint8_t> payload(OneFragmentPacketLength + 20);\n\n\t\tq.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 1 });\n\n\t\tconst auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength);\n\n\t\tREQUIRE(dataToSendOne.has_value());\n\t\tREQUIRE(!dataToSendOne->data.IsEnd());\n\t\tREQUIRE(dataToSendOne->data.GetStreamId() == 1);\n\n\t\tq.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId);\n\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId(\n\t\t  1, false));\n\t\tREQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1));\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/SCTP/tx/TestStreamScheduler.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SCTP/packet/UserData.hpp\"\n#include \"RTC/SCTP/tx/SendQueueInterface.hpp\"\n#include \"RTC/SCTP/tx/StreamScheduler.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <deque>\n#include <map>\n#include <vector>\n\nnamespace\n{\n\tconstexpr uint64_t Mtu{ 1000 };\n\tconstexpr size_t PayloadLength{ 4 };\n\tconstexpr uint64_t NowMs{ 0 };\n\n\tbool checkDataToSendHasMid(\n\t  std::optional<RTC::SCTP::SendQueueInterface::DataToSend> dataToSend, uint32_t mid)\n\t{\n\t\tif (!dataToSend.has_value())\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tif (dataToSend->data.GetMessageId() != mid)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tstd::function<std::optional<RTC::SCTP::SendQueueInterface::DataToSend>(uint64_t, size_t)> createChunk(\n\t  uint32_t outgoingMessageId, uint16_t streamId, uint32_t mid, size_t payloadLength = PayloadLength)\n\t{\n\t\treturn\n\t\t  [streamId, mid, payloadLength, outgoingMessageId](uint64_t /*nowMs*/, size_t /*maxLength*/)\n\t\t{\n\t\t\treturn RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t  outgoingMessageId,\n\t\t\t  RTC::SCTP::UserData(\n\t\t\t    streamId,\n\t\t\t    /*ssn*/ 0,\n\t\t\t    mid,\n\t\t\t    /*fsn*/ 0,\n\t\t\t    /*ppid*/ 42,\n\t\t\t    std::vector<uint8_t>(payloadLength),\n\t\t\t    /*isBeginning*/ true,\n\t\t\t    /*isEnd*/ true,\n\t\t\t    /*unoreded*/ true));\n\t\t};\n\t}\n\n\tstd::map<uint16_t, size_t> getPacketCounts(\n\t  RTC::SCTP::StreamScheduler& scheduler, size_t packetsToGenerate)\n\t{\n\t\tstd::map</*streamId*/ uint16_t, size_t> packetCounts;\n\n\t\tfor (size_t i{ 0 }; i < packetsToGenerate; ++i)\n\t\t{\n\t\t\tconst std::optional<RTC::SCTP::SendQueueInterface::DataToSend> dataToSend =\n\t\t\t  scheduler.Produce(NowMs, Mtu);\n\n\t\t\tif (dataToSend.has_value())\n\t\t\t{\n\t\t\t\t++packetCounts[dataToSend->data.GetStreamId()];\n\t\t\t}\n\t\t}\n\n\t\treturn packetCounts;\n\t}\n\n\tclass MockStreamProducer : public RTC::SCTP::StreamScheduler::StreamProducer\n\t{\n\tpublic:\n\t\t/**\n\t\t * Equivalent to EXPECT_CALL(producer, Produce).WillOnce(...).WillOnce(...)\n\t\t * in dcsctp.\n\t\t */\n\t\tvoid PushProduce(\n\t\t  std::function<std::optional<RTC::SCTP::SendQueueInterface::DataToSend>(uint64_t, size_t)> fn)\n\t\t{\n\t\t\tthis->produceQueue.push_back(std::move(fn));\n\t\t}\n\n\t\t/**\n\t\t * Equivalent to EXPECT_CALL(producer, bytes_to_send_in_next_message)\n\t\t * .WillOnce(Return(n)) in dcsctp.\n\t\t */\n\t\tvoid PushBytesToSend(size_t bytes)\n\t\t{\n\t\t\tthis->bytesQueue.push_back(bytes);\n\t\t}\n\n\t\tstd::optional<RTC::SCTP::SendQueueInterface::DataToSend> Produce(uint64_t nowMs, size_t maxLength) override\n\t\t{\n\t\t\tREQUIRE(!this->produceQueue.empty());\n\n\t\t\tconst auto fn = std::move(this->produceQueue.front());\n\n\t\t\tthis->produceQueue.pop_front();\n\n\t\t\treturn fn(nowMs, maxLength);\n\t\t}\n\n\t\tsize_t GetBytesToSendInNextMessage() const override\n\t\t{\n\t\t\tREQUIRE(!this->bytesQueue.empty());\n\n\t\t\tconst size_t bytes = this->bytesQueue.front();\n\n\t\t\tthis->bytesQueue.pop_front();\n\n\t\t\treturn bytes;\n\t\t}\n\n\tprivate:\n\t\tstd::deque<std::function<std::optional<RTC::SCTP::SendQueueInterface::DataToSend>(uint64_t, size_t)>>\n\t\t  produceQueue;\n\t\tmutable std::deque<size_t> bytesQueue;\n\t};\n\n\tclass TestStream\n\t{\n\tpublic:\n\t\tTestStream(\n\t\t  RTC::SCTP::StreamScheduler& scheduler,\n\t\t  uint16_t streamId,\n\t\t  uint16_t priority,\n\t\t  size_t packetLength = PayloadLength)\n\t\t{\n\t\t\tthis->producer.PushBytesToSend(packetLength); // MayMakeActive().\n\n\t\t\t// Equivalent to WillRepeatedly() in dcsctp.\n\t\t\tfor (int i{ 0 }; i < 100; ++i)\n\t\t\t{\n\t\t\t\tthis->producer.PushProduce(createChunk(i, streamId, i, packetLength));\n\t\t\t\tthis->producer.PushBytesToSend(packetLength);\n\t\t\t}\n\n\t\t\tthis->stream = scheduler.CreateStream(std::addressof(producer), streamId, priority);\n\t\t\tthis->stream->MayMakeActive();\n\t\t}\n\n\t\tRTC::SCTP::StreamScheduler::Stream& GetStream()\n\t\t{\n\t\t\treturn *stream;\n\t\t}\n\n\tprivate:\n\t\tMockStreamProducer producer;\n\t\tstd::unique_ptr<RTC::SCTP::StreamScheduler::Stream> stream;\n\t};\n} // namespace\n\nSCENARIO(\"SCTP StreamScheduler\", \"[sctp][streamscheduler]\")\n{\n\t// A scheduler without active streams doesn't produce data.\n\tSECTION(\"has no active streams\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Stream properties can be set and retrieved.\n\tSECTION(\"can set and get stream properties\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer;\n\t\tauto stream = scheduler.CreateStream(std::addressof(producer), 1, 2);\n\n\t\tREQUIRE(stream->GetStreamId() == 1);\n\t\tREQUIRE(stream->GetPriority() == 2);\n\n\t\tstream->SetPriority(0);\n\n\t\tREQUIRE(stream->GetPriority() == 0);\n\t}\n\n\tSECTION(\"can produce from a single stream\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer;\n\n\t\tproducer.PushBytesToSend(PayloadLength);\n\t\tproducer.PushProduce(createChunk(0, 1, 0));\n\t\tproducer.PushBytesToSend(0);\n\n\t\tauto stream = scheduler.CreateStream(std::addressof(producer), 1, 2);\n\n\t\tstream->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 0));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// A scheduler with a single stream produced packets from it.\n\tSECTION(\"will round-robin between streams\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(3, 2, 200));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2);\n\n\t\tstream2->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Switches between two streams after every packet.\n\tSECTION(\"will round-robin between streams\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(3, 2, 200));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2);\n\n\t\tstream2->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Switches between two streams after every packet, but keeps producing from\n\t// the same stream when a packet contains of multiple fragments.\n\tSECTION(\"will round-robin only when finished producing chunk\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(PayloadLength); // MayMakeActive\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\t// MID(101) fragmented in 3 chunks:\n\t\t// 1. beginning:true, end:false\n\t\t// 2. beginning:false, end:false\n\t\t// 3. beginning:false, end:true\n\t\tproducer1.PushProduce(\n\t\t  [](uint64_t, size_t)\n\t\t  {\n\t\t\t  return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t    1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector<uint8_t>(4), true, false, true));\n\t\t  });\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(\n\t\t  [](uint64_t, size_t)\n\t\t  {\n\t\t\t  return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t    1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector<uint8_t>(4), false, false, true));\n\t\t  });\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(\n\t\t  [](uint64_t, size_t)\n\t\t  {\n\t\t\t  return RTC::SCTP::SendQueueInterface::DataToSend(\n\t\t\t    1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector<uint8_t>(4), false, true, true));\n\t\t  });\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\tproducer2.PushBytesToSend(PayloadLength); // MayMakeActive().\n\t\tproducer2.PushProduce(createChunk(3, 2, 200));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2);\n\n\t\tstream2->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200));\n\t\t// MID(101) is fully produced before giving up on stream2.\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Resumes a paused stream - makes a stream active after inactivating it.\n\tSECTION(\"single stream can be resumed\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(PayloadLength); // When making active again.\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\n\t\tstream1->MakeInactive();\n\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\n\t\tstream1->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Iterates between streams, where one is suddenly paused and later resumed.\n\tSECTION(\"will round-robin with paused stream\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(PayloadLength); // When making active again.\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\tproducer2.PushBytesToSend(PayloadLength); // MayMakeActive().\n\t\tproducer2.PushProduce(createChunk(3, 2, 200));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2);\n\n\t\tstream2->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200));\n\n\t\tstream1->MakeInactive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202));\n\n\t\tstream1->MayMakeActive();\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101));\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102));\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Verifies that packet counts are evenly distributed in round robin\n\t// scheduling.\n\tSECTION(\"will distribute round-robin packets evenly between two streams\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tconst TestStream stream1(scheduler, 1, 1);\n\t\tconst TestStream stream2(scheduler, 2, 1);\n\n\t\tconst auto packetCounts = getPacketCounts(scheduler, 10);\n\n\t\tREQUIRE(packetCounts.at(1) == 5);\n\t\tREQUIRE(packetCounts.at(2) == 5);\n\t}\n\n\t// Verifies that packet counts are evenly distributed among active streams,\n\t// where a stream is suddenly made inactive, two are added, and then the\n\t// paused stream is resumed.\n\tSECTION(\"will distribute evenly with paused and added streams\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tconst TestStream stream1(scheduler, 1, 1);\n\t\tTestStream stream2(scheduler, 2, 1);\n\n\t\tconst auto counts1 = getPacketCounts(scheduler, 10);\n\n\t\tREQUIRE(counts1.at(1) == 5);\n\t\tREQUIRE(counts1.at(2) == 5);\n\n\t\tstream2.GetStream().MakeInactive();\n\n\t\tconst TestStream stream3(scheduler, 3, 1);\n\t\tconst TestStream stream4(scheduler, 4, 1);\n\n\t\tconst auto counts2 = getPacketCounts(scheduler, 15);\n\n\t\tREQUIRE(counts2.at(1) == 5);\n\t\t// stream2 is inative, it is not in the map.\n\t\tREQUIRE(!counts2.contains(2));\n\t\tREQUIRE(counts2.at(3) == 5);\n\t\tREQUIRE(counts2.at(4) == 5);\n\n\t\tstream2.GetStream().MayMakeActive();\n\n\t\tconst auto counts3 = getPacketCounts(scheduler, 20);\n\n\t\tREQUIRE(counts3.at(1) == 5);\n\t\tREQUIRE(counts3.at(2) == 5);\n\t\tREQUIRE(counts3.at(3) == 5);\n\t\tREQUIRE(counts3.at(4) == 5);\n\t}\n\n\t// Degrades to fair queuing with streams having identical priority.\n\tSECTION(\"will do fair queuing with same priority\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tconstexpr size_t SmallPacket{ 30 };\n\t\tconstexpr size_t LargePacket{ 70 };\n\n\t\tMockStreamProducer producer1;\n\n\t\tproducer1.PushBytesToSend(SmallPacket); // MayMakeActive().\n\t\tproducer1.PushProduce(createChunk(0, 1, 100, SmallPacket));\n\t\tproducer1.PushBytesToSend(SmallPacket);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101, SmallPacket));\n\t\tproducer1.PushBytesToSend(SmallPacket);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102, SmallPacket));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\tproducer2.PushBytesToSend(LargePacket); // MayMakeActive().\n\t\tproducer2.PushProduce(createChunk(3, 2, 200, LargePacket));\n\t\tproducer2.PushBytesToSend(LargePacket);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201, LargePacket));\n\t\tproducer2.PushBytesToSend(LargePacket);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202, LargePacket));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2);\n\n\t\tstream2->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t = 30\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t = 60\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t = 70\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t = 90\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t = 140\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t = 210\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Will do weighted fair queuing with three streams having different priority.\n\tSECTION(\"will do weighted fair queuing with same size and different priority\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tMockStreamProducer producer1;\n\n\t\t// Priority 125 -> allowed to produce every 1000/125 ~= 80 time units.\n\t\tproducer1.PushBytesToSend(PayloadLength); // MayMakeActive().\n\t\tproducer1.PushProduce(createChunk(0, 1, 100));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(1, 1, 101));\n\t\tproducer1.PushBytesToSend(PayloadLength);\n\t\tproducer1.PushProduce(createChunk(2, 1, 102));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 125);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\t// Priority 200 -> allowed to produce every 1000/200 ~= 50 time units.\n\t\tproducer2.PushBytesToSend(PayloadLength); // MayMakeActive().\n\t\tproducer2.PushProduce(createChunk(3, 2, 200));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(4, 2, 201));\n\t\tproducer2.PushBytesToSend(PayloadLength);\n\t\tproducer2.PushProduce(createChunk(5, 2, 202));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 200);\n\n\t\tstream2->MayMakeActive();\n\n\t\tMockStreamProducer producer3;\n\n\t\t// Priority 500 -> allowed to produce every 1000/500 ~= 20 time units.\n\t\tproducer3.PushBytesToSend(PayloadLength); // MayMakeActive().\n\t\tproducer3.PushProduce(createChunk(6, 3, 300));\n\t\tproducer3.PushBytesToSend(PayloadLength);\n\t\tproducer3.PushProduce(createChunk(7, 3, 301));\n\t\tproducer3.PushBytesToSend(PayloadLength);\n\t\tproducer3.PushProduce(createChunk(8, 3, 302));\n\t\tproducer3.PushBytesToSend(0);\n\n\t\tauto stream3 = scheduler.CreateStream(std::addressof(producer3), 3, 500);\n\n\t\tstream3->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 300)); // t ~= 20\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 301)); // t ~= 40\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t ~= 50\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 302)); // t ~= 60\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t ~= 80\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t ~= 100\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t ~= 150\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t ~= 160\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t ~= 240\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Will do weighted fair queuing with three streams having different priority\n\t// and sending different payload sizes.\n\tSECTION(\"will do weighted fair queuing with different size and priority\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tconstexpr size_t SmallPacket{ 20 };\n\t\tconstexpr size_t MediumPacket{ 50 };\n\t\tconstexpr size_t LargePacket{ 70 };\n\n\t\tMockStreamProducer producer1;\n\n\t\t// Stream with priority = 125 -> inverse weight ~= 80.\n\t\tproducer1.PushBytesToSend(MediumPacket); // MayMakeActive(); vft ~ 0 + 50*80 = 4000\n\t\tproducer1.PushProduce(createChunk(0, 1, 100, MediumPacket));\n\t\tproducer1.PushBytesToSend(SmallPacket); // vft ~ 4000 + 20*80 = 5600\n\t\tproducer1.PushProduce(createChunk(1, 1, 101, SmallPacket));\n\t\tproducer1.PushBytesToSend(LargePacket); // vft ~ 5600 + 70*80 = 11200\n\t\tproducer1.PushProduce(createChunk(2, 1, 102, LargePacket));\n\t\tproducer1.PushBytesToSend(0);\n\n\t\tauto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 125);\n\n\t\tstream1->MayMakeActive();\n\n\t\tMockStreamProducer producer2;\n\n\t\t// Stream with priority = 200 -> inverse weight ~= 50.\n\t\tproducer2.PushBytesToSend(MediumPacket); // MayMakeActive(); vft ~ 0 + 50*50 = 2500\n\t\tproducer2.PushProduce(createChunk(3, 2, 200, MediumPacket));\n\t\tproducer2.PushBytesToSend(LargePacket); // vft ~ 2500 + 70*50 = 6000\n\t\tproducer2.PushProduce(createChunk(4, 2, 201, LargePacket));\n\t\tproducer2.PushBytesToSend(SmallPacket); // vft ~ 6000 + 20*50 = 7000\n\t\tproducer2.PushProduce(createChunk(5, 2, 202, SmallPacket));\n\t\tproducer2.PushBytesToSend(0);\n\n\t\tauto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 200);\n\n\t\tstream2->MayMakeActive();\n\n\t\tMockStreamProducer producer3;\n\n\t\t// Stream with priority = 500 -> inverse weight ~= 20\n\t\tproducer3.PushBytesToSend(SmallPacket); // MayMakeActive; vft ~ 0 + 20*20 = 400\n\t\tproducer3.PushProduce(createChunk(6, 3, 300, SmallPacket));\n\t\tproducer3.PushBytesToSend(MediumPacket); // vft ~ 400 + 50*20 = 1400\n\t\tproducer3.PushProduce(createChunk(7, 3, 301, MediumPacket));\n\t\tproducer3.PushBytesToSend(LargePacket); // vft ~ 1400 + 70*20 = 2800\n\t\tproducer3.PushProduce(createChunk(8, 3, 302, LargePacket));\n\t\tproducer3.PushBytesToSend(0);\n\n\t\tauto stream3 = scheduler.CreateStream(std::addressof(producer3), 3, 500);\n\n\t\tstream3->MayMakeActive();\n\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 300)); // t ~= 400\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 301)); // t ~= 1400\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t ~= 2500\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 302)); // t ~= 2800\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t ~= 4000\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t ~= 5600\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t ~= 6000\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t ~= 7000\n\t\tREQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t ~= 11200\n\t\tREQUIRE(!scheduler.Produce(NowMs, Mtu).has_value());\n\t}\n\n\t// Two streams of different priority, identical packet size: ratio of packets\n\t// must match ratio of priorities.\n\tSECTION(\"will distribute WFQ packets in two streams by priority\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tconst TestStream stream1(scheduler, 1, 100);\n\t\tconst TestStream stream2(scheduler, 2, 200);\n\n\t\tconst auto packetCounts = getPacketCounts(scheduler, 15);\n\n\t\tREQUIRE(packetCounts.at(1) == 5);\n\t\tREQUIRE(packetCounts.at(2) == 10);\n\t}\n\n\tSECTION(\"will distribute WFQ packets in four streams by priority\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tconst TestStream stream1(scheduler, 1, 100);\n\t\tconst TestStream stream2(scheduler, 2, 200);\n\t\tconst TestStream stream3(scheduler, 3, 300);\n\t\tconst TestStream stream4(scheduler, 4, 400);\n\n\t\tconst auto packetCounts = getPacketCounts(scheduler, 50);\n\n\t\tREQUIRE(packetCounts.at(1) == 5);\n\t\tREQUIRE(packetCounts.at(2) == 10);\n\t\tREQUIRE(packetCounts.at(3) == 15);\n\t\tREQUIRE(packetCounts.at(4) == 20);\n\t}\n\n\t// A simple test with two streams of different priority, but sending packets\n\t// of different size. Verifies that the ratio of total packet payload\n\t// represents their priority.\n\t//\n\t// In this example,\n\t// - stream1 has priority 100 and sends packets of size 8.\n\t// -stream2 has priority 400 and sends packets of size 4.\n\t//\n\t// With round robin, stream1 would get twice as many payload bytes on the wire\n\t// as stream2, but with WFQ and a 4x priority increase, stream2 should 4x as\n\t// many payload bytes on the wire. That translates to stream2 getting 8x as\n\t// many packets on the wire as they are half as large.\n\tSECTION(\"will distribute from two streams fairly\")\n\t{\n\t\tRTC::SCTP::StreamScheduler scheduler(Mtu);\n\n\t\tscheduler.EnableMessageInterleaving(true);\n\n\t\tconst TestStream stream1(scheduler, 1, 100, /*packetLength*/ 8);\n\t\tconst TestStream stream2(scheduler, 2, 400, /*packetLength*/ 4);\n\n\t\tconst auto packetCounts = getPacketCounts(scheduler, 90);\n\n\t\tREQUIRE(packetCounts.at(1) == 10);\n\t\tREQUIRE(packetCounts.at(2) == 80);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestKeyFrameRequestManager.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"RTC/KeyFrameRequestManager.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"KeyFrameRequestManager\", \"[rtp][keyframe]\")\n{\n\tclass TestKeyFrameRequestManagerListener : public RTC::KeyFrameRequestManager::Listener\n\t{\n\tpublic:\n\t\tvoid OnKeyFrameNeeded(RTC::KeyFrameRequestManager* /*keyFrameRequestManager */, uint32_t /*ssrc*/) override\n\t\t{\n\t\t\tthis->onKeyFrameNeededTimesCalled++;\n\t\t}\n\n\t\tvoid Reset()\n\t\t{\n\t\t\tthis->onKeyFrameNeededTimesCalled = 0;\n\t\t}\n\n\tpublic:\n\t\tsize_t onKeyFrameNeededTimesCalled{ 0 };\n\t};\n\n\tTestKeyFrameRequestManagerListener listener;\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\n\tSECTION(\"key frame requested once, not received on time\")\n\t{\n\t\tlistener.Reset();\n\t\tRTC::KeyFrameRequestManager keyFrameRequestManager(\n\t\t  std::addressof(listener), std::addressof(shared), 1000);\n\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\n\t\tREQUIRE(listener.onKeyFrameNeededTimesCalled == 1);\n\t}\n\n\tSECTION(\"key frame requested many times, not received on time\")\n\t{\n\t\tlistener.Reset();\n\t\tRTC::KeyFrameRequestManager keyFrameRequestManager(\n\t\t  std::addressof(listener), std::addressof(shared), 500);\n\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\n\t\tREQUIRE(listener.onKeyFrameNeededTimesCalled == 1);\n\t}\n\n\tSECTION(\"key frame is received on time\")\n\t{\n\t\tlistener.Reset();\n\t\tRTC::KeyFrameRequestManager keyFrameRequestManager(\n\t\t  std::addressof(listener), std::addressof(shared), 500);\n\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.KeyFrameReceived(1111);\n\n\t\tREQUIRE(listener.onKeyFrameNeededTimesCalled == 1);\n\t}\n\n\tSECTION(\"key frame is forced, no received on time\")\n\t{\n\t\tlistener.Reset();\n\t\tRTC::KeyFrameRequestManager keyFrameRequestManager(\n\t\t  std::addressof(listener), std::addressof(shared), 500);\n\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.ForceKeyFrameNeeded(1111);\n\n\t\tREQUIRE(listener.onKeyFrameNeededTimesCalled == 2);\n\t}\n\n\tSECTION(\"key frame is forced, received on time\")\n\t{\n\t\tlistener.Reset();\n\t\tRTC::KeyFrameRequestManager keyFrameRequestManager(\n\t\t  std::addressof(listener), std::addressof(shared), 500);\n\n\t\tkeyFrameRequestManager.KeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.ForceKeyFrameNeeded(1111);\n\t\tkeyFrameRequestManager.KeyFrameReceived(1111);\n\n\t\tREQUIRE(listener.onKeyFrameNeededTimesCalled == 2);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestNackGenerator.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"test/include/RTC/RTP/rtpCommon.hpp\"\n#include \"RTC/NackGenerator.hpp\"\n#include \"RTC/RTP/Codecs/PayloadDescriptorHandler.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <vector>\n\nSCENARIO(\"NACK generator\", \"[rtp][rtcp][nack]\")\n{\n\tconstexpr unsigned int SendNackDelay{ 0u }; // In ms.\n\n\tstruct TestNackGeneratorInput\n\t{\n\t\tTestNackGeneratorInput() = default;\n\t\tTestNackGeneratorInput(\n\t\t  uint16_t seq,\n\t\t  bool isKeyFrame,\n\t\t  uint16_t firstNacked,\n\t\t  size_t numNacked,\n\t\t  bool keyFrameRequired = false,\n\t\t  size_t nackListSize   = 0)\n\t\t  : seq(seq),\n\t\t    isKeyFrame(isKeyFrame),\n\t\t    firstNacked(firstNacked),\n\t\t    numNacked(numNacked),\n\t\t    keyFrameRequired(keyFrameRequired),\n\t\t    nackListSize(nackListSize)\n\t\t{\n\t\t}\n\n\t\tuint16_t seq{ 0 };\n\t\tbool isKeyFrame{ false };\n\t\tuint16_t firstNacked{ 0 };\n\t\tsize_t numNacked{ 0 };\n\t\tbool keyFrameRequired{ false };\n\t\tsize_t nackListSize{ 0 };\n\t};\n\n\tclass TestPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler\n\t{\n\tpublic:\n\t\texplicit TestPayloadDescriptorHandler(bool isKeyFrame) : isKeyFrame(isKeyFrame) {};\n\t\t~TestPayloadDescriptorHandler() override = default;\n\t\tvoid Dump(int indentation = 0) const override\n\t\t{\n\t\t}\n\t\tbool Process(\n\t\t  RTC::RTP::Codecs::EncodingContext* /*context*/,\n\t\t  RTC::RTP::Packet* /*packet*/,\n\t\t  bool& /*marker*/) override\n\t\t{\n\t\t\treturn true;\n\t\t}\n\t\tvoid RtpPacketChanged(RTC::RTP::Packet* packet) override\n\t\t{\n\t\t}\n\t\tstd::unique_ptr<RTC::RTP::Codecs::PayloadDescriptor::Encoder> GetEncoder() const override\n\t\t{\n\t\t\treturn nullptr;\n\t\t}\n\t\tvoid Encode(\n\t\t  RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override\n\t\t{\n\t\t}\n\t\tvoid Restore(RTC::RTP::Packet* /*packet*/) override\n\t\t{\n\t\t}\n\t\tuint8_t GetSpatialLayer() const override\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\tuint8_t GetTemporalLayer() const override\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\tbool IsKeyFrame() const override\n\t\t{\n\t\t\treturn this->isKeyFrame;\n\t\t}\n\n\tprivate:\n\t\tbool isKeyFrame{ false };\n\t};\n\n\tclass TestNackGeneratorListener : public RTC::NackGenerator::Listener\n\t{\n\t\tvoid OnNackGeneratorNackRequired(const std::vector<uint16_t>& seqNumbers) override\n\t\t{\n\t\t\tthis->nackRequiredTriggered = true;\n\n\t\t\tauto it          = seqNumbers.begin();\n\t\t\tauto firstNacked = *it;\n\t\t\tauto numNacked   = seqNumbers.size();\n\n\t\t\tREQUIRE(this->currentInput.firstNacked == firstNacked);\n\t\t\tREQUIRE(this->currentInput.numNacked == numNacked);\n\t\t};\n\n\t\tvoid OnNackGeneratorKeyFrameRequired() override\n\t\t{\n\t\t\tthis->keyFrameRequiredTriggered = true;\n\n\t\t\tREQUIRE(this->currentInput.keyFrameRequired);\n\t\t}\n\n\tpublic:\n\t\tvoid Reset(TestNackGeneratorInput& input)\n\t\t{\n\t\t\tthis->currentInput              = input;\n\t\t\tthis->nackRequiredTriggered     = false;\n\t\t\tthis->keyFrameRequiredTriggered = false;\n\t\t}\n\n\t\tvoid Check(RTC::NackGenerator& nackGenerator)\n\t\t{\n\t\t\tREQUIRE(this->nackRequiredTriggered == static_cast<bool>(this->currentInput.numNacked));\n\t\t\tREQUIRE(this->keyFrameRequiredTriggered == this->currentInput.keyFrameRequired);\n\t\t}\n\n\tprivate:\n\t\tTestNackGeneratorInput currentInput{};\n\t\tbool nackRequiredTriggered{ false };\n\t\tbool keyFrameRequiredTriggered{ false };\n\t};\n\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\n\tauto validate =\n\t  [&shared](std::unique_ptr<RTC::RTP::Packet>& packet, std::vector<TestNackGeneratorInput>& inputs)\n\t{\n\t\tTestNackGeneratorListener listener;\n\t\tauto nackGenerator =\n\t\t  RTC::NackGenerator(std::addressof(listener), std::addressof(shared), SendNackDelay);\n\n\t\tfor (auto input : inputs)\n\t\t{\n\t\t\tlistener.Reset(input);\n\n\t\t\tauto* tpdh = new TestPayloadDescriptorHandler(input.isKeyFrame);\n\n\t\t\tpacket->SetPayloadDescriptorHandler(tpdh);\n\t\t\tpacket->SetSequenceNumber(input.seq);\n\t\t\tnackGenerator.ReceivePacket(packet.get(), /*isRecovered*/ false);\n\n\t\t\tlistener.Check(nackGenerator);\n\t\t}\n\t};\n\n\t// clang-format off\n\talignas(4) uint8_t rtpBuffer[] =\n\t{\n\t\t0x80, 0x7b, 0x52, 0x0e,\n\t\t0x5b, 0x6b, 0xca, 0xb5,\n\t\t0x00, 0x00, 0x00, 0x02\n\t};\n\t// clang-format on\n\n\t// [pt:123, seq:21006, timestamp:1533790901]\n\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(rtpBuffer, sizeof(rtpBuffer)) };\n\n\tpacket->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer));\n\n\tSECTION(\"no NACKs required\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 2371, false, 0, 0, false, 0 },\n\t\t\t{ 2372, false, 0, 0, false, 0 },\n\t\t\t{ 2373, false, 0, 0, false, 0 },\n\t\t\t{ 2374, false, 0, 0, false, 0 },\n\t\t\t{ 2375, false, 0, 0, false, 0 },\n\t\t\t{ 2376, false, 0, 0, false, 0 },\n\t\t\t{ 2377, false, 0, 0, false, 0 },\n\t\t\t{ 2378, false, 0, 0, false, 0 },\n\t\t\t{ 2379, false, 0, 0, false, 0 },\n\t\t\t{ 2380, false, 0, 0, false, 0 },\n\t\t\t{ 2254, false, 0, 0, false, 0 },\n\t\t\t{ 2250, false, 0, 0, false, 0 },\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"generate NACK for missing ordered packet\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 2381, false,    0, 0, false, 0 },\n\t\t\t{ 2383, false, 2382, 1, false, 1 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"sequence wrap generates no NACK\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 65534, false, 0, 0, false, 0 },\n\t\t\t{ 65535, false, 0, 0, false, 0 },\n\t\t\t{     0, false, 0, 0, false, 0 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"generate NACK after sequence wrap\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 65534, false, 0, 0, false, 0 },\n\t\t\t{ 65535, false, 0, 0, false, 0 },\n\t\t\t{     1, false, 0, 1, false, 1 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"generate NACK after sequence wrap, and yet another NACK\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 65534, false, 0, 0, false,  0 },\n\t\t\t{ 65535, false, 0, 0, false,  0 },\n\t\t\t{     1, false, 0, 1, false,  1 },\n\t\t\t{    11, false, 2, 9, false, 10 },\n\t\t\t{    12,  true, 0, 0, false, 10 },\n\t\t\t{    13,  true, 0, 0, false,  0 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"intercalated missing packets\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 1, false, 0, 0, false, 0 },\n\t\t\t{ 3, false, 2, 1, false, 1 },\n\t\t\t{ 5, false, 4, 1, false, 2 },\n\t\t\t{ 7, false, 6, 1, false, 3 },\n\t\t\t{ 9, false, 8, 1, false, 4 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"non contiguous intercalated missing packets\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{ 1, false, 0, 0, false, 0 },\n\t\t\t{ 3, false, 2, 1, false, 1 },\n\t\t\t{ 7, false, 4, 3, false, 4 },\n\t\t\t{ 9, false, 8, 1, false, 5 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"big jump\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{   1, false, 0,   0, false,   0 },\n\t\t\t{ 300, false, 2, 298, false, 298 },\n\t\t\t{   3, false, 0,   0, false, 297 },\n\t\t\t{   4, false, 0,   0, false, 296 },\n\t\t\t{   5, false, 0,   0, false, 295 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n\n\tSECTION(\"Key Frame required. Nack list too large to be requested\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestNackGeneratorInput> inputs =\n\t\t{\n\t\t\t{    1, false, 0, 0, false, 0 },\n\t\t\t{ 3000, false, 0, 0,  true, 0 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(packet, inputs);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestRateCalculator.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/RateCalculator.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <limits> // std::numeric_limits\n#include <vector>\n\nSCENARIO(\"RateCalculator\", \"[rate-calculator]\")\n{\n\tstruct TestRateCalculatorData\n\t{\n\t\tint64_t offset;\n\t\tuint32_t size;\n\t\tuint32_t rate;\n\t};\n\n\tauto validate =\n\t  [](RTC::RateCalculator& rate, uint64_t timeBaseMs, std::vector<TestRateCalculatorData>& input)\n\t{\n\t\tfor (auto& item : input)\n\t\t{\n\t\t\trate.Update(item.size, timeBaseMs + item.offset);\n\n\t\t\tREQUIRE(rate.GetRate(timeBaseMs + item.offset) == item.rate);\n\t\t}\n\n\t\t// Repeat forcing nowMs to be 0.\n\t\trate.Reset();\n\n\t\tfor (auto& item : input)\n\t\t{\n\t\t\trate.Update(item.size, timeBaseMs + item.offset);\n\n\t\t\tREQUIRE(rate.GetRate(0 + item.offset) == item.rate);\n\t\t}\n\n\t\t// Repeat forcing nowMs to be std::numeric_limits<uint64_t>::max() - 100.\n\t\trate.Reset();\n\n\t\tfor (auto& item : input)\n\t\t{\n\t\t\trate.Update(item.size, timeBaseMs + item.offset);\n\n\t\t\tREQUIRE(rate.GetRate(std::numeric_limits<uint64_t>::max() - 100 + item.offset) == item.rate);\n\t\t}\n\t};\n\n\tconst uint64_t nowMs = 12345678;\n\n\tSECTION(\"receive single item per 1000 ms\")\n\t{\n\t\tRTC::RateCalculator rate;\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0, 5, 40 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\t}\n\n\tSECTION(\"receive multiple items per 1000 ms\")\n\t{\n\t\tRTC::RateCalculator rate;\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0,   5, 40  },\n\t\t\t{ 100, 2, 56  },\n\t\t\t{ 300, 2, 72  },\n\t\t\t{ 999, 4, 104 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\t}\n\n\tSECTION(\"receive item every 1000 ms\")\n\t{\n\t\tRTC::RateCalculator rate(1000, 8000, 100);\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0,    5, 40 },\n\t\t\t{ 1000, 5, 40 },\n\t\t\t{ 2000, 5, 40 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\t}\n\n\tSECTION(\"slide\")\n\t{\n\t\tRTC::RateCalculator rate(1000, 8000, 1000);\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0,    5, 40 },\n\t\t\t{ 999,  2, 56 },\n\t\t\t{ 1001, 1, 24 },\n\t\t\t{ 1001, 1, 32 },\n\t\t\t{ 2000, 1, 24 }\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\n\t\tREQUIRE(rate.GetRate(nowMs + 3001) == 0);\n\t}\n\n\tSECTION(\"slide with 100 items\")\n\t{\n\t\tRTC::RateCalculator rate(1000, 8000, 100);\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0,    5, 40 },\n\t\t\t{ 999,  2, 56 },\n\t\t\t{ 1001, 1, 24 }, // merged inside 999\n\t\t\t{ 1001, 1, 32 }, // merged inside 999\n\t\t\t{ 2000, 1, 8 } \t // it will erase the item with timestamp=999,\n\t\t\t\t\t\t\t // removing also the next two samples.\n\t\t\t\t\t\t\t // The end estimation will include only the last sample.\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\n\t\tREQUIRE(rate.GetRate(nowMs + 3001) == 0);\n\t}\n\n\tSECTION(\"wrap\")\n\t{\n\t\t// window: 1000ms, items: 5 (granularity: 200ms)\n\t\tRTC::RateCalculator rate(1000, 8000, 5);\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 1000, 1, 1*8 },\n\t\t\t{ 1200, 1, 1*8 + 1*8 },\n\t\t\t{ 1400, 1, 1*8 + 2*8 },\n\t\t\t{ 1600, 1, 1*8 + 3*8 },\n\t\t\t{ 1800, 1, 1*8 + 4*8 },\n\t\t\t{ 2000, 1, 1*8 + (5-1)*8 }, // starts wrap here\n\t\t\t{ 2200, 1, 1*8 + (6-2)*8 },\n\t\t\t{ 2400, 1, 1*8 + (7-3)*8 },\n\t\t\t{ 2600, 1, 1*8 + (8-4)*8 },\n\t\t\t{ 2800, 1, 1*8 + (9-5)*8 },\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\t}\n\n\t// NOTE: This test reproduces a crash (now fixed):\n\t//   https://github.com/versatica/mediasoup/issues/1316\n\tSECTION(\"buffer overflow should not crash\")\n\t{\n\t\t// window: 1000ms, items: 3 (granularity: 333ms)\n\t\tRTC::RateCalculator rate(1000, 8000, 3);\n\n\t\t// clang-format off\n\t\tstd::vector<TestRateCalculatorData> input =\n\t\t{\n\t\t\t{ 0,   1, 8  },\n\t\t\t{ 333, 1, 16  },\n\t\t\t{ 666, 1, 24  },\n\t\t\t{ 999, 1, 32 },\n  \t};\n\t\t// clang-format on\n\n\t\tvalidate(rate, nowMs, input);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestRtpEncodingParameters.cpp",
    "content": "#include \"common.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <regex>\n#include <string>\n\nSCENARIO(\"parseScalabilityMode()\")\n{\n\tstatic const std::regex ScalabilityModeRegex(\n\t  \"^[LS]([1-9]\\\\d{0,1})T([1-9]\\\\d{0,1})(_KEY)?.*\", std::regex_constants::ECMAScript);\n\n\tstruct ScalabilityMode\n\t{\n\t\tuint8_t spatialLayers  = 1;\n\t\tuint8_t temporalLayers = 1;\n\t\tbool ksvc              = false;\n\t};\n\n\tauto parseScalabilityMode = [](const std::string& scalabilityMode)\n\t{\n\t\tstruct ScalabilityMode result;\n\t\tstd::smatch match;\n\n\t\tstd::regex_match(scalabilityMode, match, ScalabilityModeRegex);\n\n\t\tif (!match.empty())\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tresult.spatialLayers  = std::stoul(match[1].str());\n\t\t\t\tresult.temporalLayers = std::stoul(match[2].str());\n\t\t\t\tresult.ksvc           = match.size() >= 4 && match[3].str() == \"_KEY\";\n\t\t\t}\n\t\t\tcatch (std::exception& error) // NOLINT(bugprone-empty-catch)\n\t\t\t{\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tSECTION(\"parse L1T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"L1T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 3);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse S1T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S1T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 3);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse L3T2_KEY\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"L3T2_KEY\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 3);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 2);\n\t\tREQUIRE(scalabilityMode.ksvc == true);\n\t}\n\n\tSECTION(\"parse S2T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S2T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 2);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 3);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse foo\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"foo\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 1);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse ''\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 1);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse S0T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S0T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 1);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse S1T0\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S1T0\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 1);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse S20T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S20T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 20);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 3);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n\n\tSECTION(\"parse S200T3\")\n\t{\n\t\tconst auto scalabilityMode = parseScalabilityMode(\"S200T3\");\n\n\t\tREQUIRE(scalabilityMode.spatialLayers == 1);\n\t\tREQUIRE(scalabilityMode.temporalLayers == 1);\n\t\tREQUIRE(scalabilityMode.ksvc == false);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestSeqManager.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/SeqManager.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <limits> // std::numeric_limits\n#include <string>\n#include <vector>\n\nnamespace\n{\n\ttemplate<typename T>\n\tstruct TestSeqManagerInput\n\t{\n\t\tTestSeqManagerInput(T input, T output, bool sync = false, bool drop = false, int64_t maxInput = -1)\n\t\t  : input(input), output(output), sync(sync), drop(drop), maxInput(maxInput)\n\t\t{\n\t\t}\n\n\t\tT input{ 0 };\n\t\tT output{ 0 };\n\t\tbool sync{ false };\n\t\tbool drop{ false };\n\t\tint64_t maxInput{ -1 };\n\t};\n\n\ttemplate<typename T, uint8_t N>\n\tstd::pair<T, T> validate(RTC::SeqManager<T, N> seqManager, std::vector<TestSeqManagerInput<T>>& inputs)\n\t{\n\t\tfor (auto& element : inputs)\n\t\t{\n\t\t\tif (element.sync)\n\t\t\t{\n\t\t\t\tseqManager.Sync(element.input - 1);\n\t\t\t}\n\n\t\t\tif (element.drop)\n\t\t\t{\n\t\t\t\tseqManager.Drop(element.input);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tT output;\n\n\t\t\t\tseqManager.Input(element.input, output);\n\n\t\t\t\tif (output != element.output)\n\t\t\t\t{\n\t\t\t\t\treturn std::make_pair(output, element.output);\n\t\t\t\t}\n\n\t\t\t\tif (element.maxInput != -1)\n\t\t\t\t{\n\t\t\t\t\tif (element.maxInput != seqManager.GetMaxInput())\n\t\t\t\t\t{\n\t\t\t\t\t\treturn std::make_pair(element.maxInput, seqManager.GetMaxInput());\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Success, return a pair of zeros for successful comparison.\n\t\treturn std::make_pair(0, 0);\n\t}\n} // namespace\n\nSCENARIO(\"SeqManager\", \"[seqmanager]\")\n{\n\tconstexpr uint16_t MaxNumberFor15Bits = (1 << 15) - 1;\n\n\tSECTION(\"0 is greater than 65000\")\n\t{\n\t\tREQUIRE(RTC::SeqManager<uint16_t>::IsSeqHigherThan(0, 65000) == true);\n\t}\n\n\tSECTION(\"0 is greater than 32500 in range 15\")\n\t{\n\t\tREQUIRE(RTC::SeqManager<uint16_t, 15>::IsSeqHigherThan(0, 32500) == true);\n\t}\n\n\tSECTION(\"receive ordered numbers, no sync, no drop\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0,  0, false, false },\n\t\t\t{  1,  1, false, false },\n\t\t\t{  2,  2, false, false },\n\t\t\t{  3,  3, false, false },\n\t\t\t{  4,  4, false, false },\n\t\t\t{  5,  5, false, false },\n\t\t\t{  6,  6, false, false },\n\t\t\t{  7,  7, false, false },\n\t\t\t{  8,  8, false, false },\n\t\t\t{  9,  9, false, false },\n\t\t\t{ 10, 10, false, false },\n\t\t\t{ 11, 11, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, no drop\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0, 0, false, false },\n\t\t\t{  1, 1, false, false },\n\t\t\t{  2, 2, false, false },\n\t\t\t{ 80, 3,  true, false },\n\t\t\t{ 81, 4, false, false },\n\t\t\t{ 82, 5, false, false },\n\t\t\t{ 83, 6, false, false },\n\t\t\t{ 84, 7, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, drop\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0,  0, false, false },\n\t\t\t{  1,  1, false, false },\n\t\t\t{  2,  2, false, false },\n\t\t\t{  3,  3, false, false },\n\t\t\t{  4,  4,  true, false }, // sync.\n\t\t\t{  5,  5, false, false },\n\t\t\t{  6,  6, false, false },\n\t\t\t{  7,  7,  true, false }, // sync.\n\t\t\t{  8,  0, false,  true }, // drop.\n\t\t\t{  9,  8, false, false },\n\t\t\t{ 11,  0, false,  true }, // drop.\n\t\t\t{ 10,  9, false, false },\n\t\t\t{ 12, 10, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered wrapped numbers\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 65533, 65533, false, false },\n\t\t\t{ 65534, 65534, false, false },\n\t\t\t{ 65535, 65535, false, false },\n\t\t\t{     0,     0, false, false },\n\t\t\t{     1,     1, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive sequence numbers with a big jump\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{    0,    0, false, false },\n\t\t\t{    1,    1, false, false },\n\t\t\t{ 1000, 1000, false, false },\n\t\t\t{ 1001, 1001, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive out of order numbers with a big jump\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{      4,     4, false, false },\n\t\t\t{      3,     3, false, false },\n\t\t\t{  65535, 65535, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers with a big jump, drop before jump\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   0,   0, false, false },\n\t\t\t{   1,   0, false,  true }, // drop.\n\t\t\t{ 100,  99, false, false },\n\t\t\t{ 100,  99, false, false },\n\t\t\t{ 103,   0, false,  true }, // drop.\n\t\t\t{ 101, 100, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers with a big jump, drop after jump\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   0,   0, false, false },\n\t\t\t{   1,   1, false, false },\n\t\t\t{ 100,   0, false,  true }, // drop.\n\t\t\t{ 103,   0, false,  true }, // drop.\n\t\t\t{ 101, 100, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop, receive numbers newer and older than the one dropped\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 0, false, false },\n\t\t\t{ 2, 0, false,  true }, // drop.\n\t\t\t{ 3, 2, false, false },\n\t\t\t{ 4, 3, false, false },\n\t\t\t{ 1, 1, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers, sync, drop\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     0,  0, false, false },\n\t\t\t{     1,  1, false, false },\n\t\t\t{     2,  2, false, false },\n\t\t\t{     3,  3, false, false },\n\t\t\t{     7,  7, false, false },\n\t\t\t{     6,  0, false,  true }, // drop.\n\t\t\t{     8,  8, false, false },\n\t\t\t{    10, 10, false, false },\n\t\t\t{     9,  9, false, false },\n\t\t\t{    11, 11, false, false },\n\t\t\t{     0, 12,  true, false }, // sync.\n\t\t\t{     2, 14, false, false },\n\t\t\t{     3, 15, false, false },\n\t\t\t{     4, 16, false, false },\n\t\t\t{     5, 17, false, false },\n\t\t\t{     6, 18, false, false },\n\t\t\t{     7, 19, false, false },\n\t\t\t{     8, 20, false, false },\n\t\t\t{     9, 21, false, false },\n\t\t\t{    10, 22, false, false },\n\t\t\t{     9,  0, false,  true }, // drop.\n\t\t\t{    61, 23,  true, false }, // sync.\n\t\t\t{    62, 24, false, false },\n\t\t\t{    63, 25, false, false },\n\t\t\t{    64, 26, false, false },\n\t\t\t{    65, 27, false, false },\n\t\t\t{    11, 28,  true, false }, // sync.\n\t\t\t{    12, 29, false, false },\n\t\t\t{    13, 30, false, false },\n\t\t\t{    14, 31, false, false },\n\t\t\t{    15, 32, false, false },\n\t\t\t{     1, 33,  true, false }, // sync.\n\t\t\t{     2, 34, false, false },\n\t\t\t{     3, 35, false, false },\n\t\t\t{     4, 36, false, false },\n\t\t\t{     5, 37, false, false },\n\t\t\t{ 65533, 38,  true, false }, // sync.\n\t\t\t{ 65534, 39, false, false },\n\t\t\t{ 65535, 40, false, false },\n\t\t\t{     0, 41,  true, false }, // sync.\n\t\t\t{     1, 42, false, false },\n\t\t\t{     3,  0, false,  true }, // drop.\n\t\t\t{     4, 44, false, false },\n\t\t\t{     5, 45, false, false },\n\t\t\t{     6, 46, false, false },\n\t\t\t{     7, 47, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, no drop, increase input\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0,  0, false, false },\n\t\t\t{  1,  1, false, false },\n\t\t\t{  2,  2, false, false },\n\t\t\t{ 80,  3,  true, false },\n\t\t\t{ 81,  4, false, false },\n\t\t\t{ 82,  5, false, false },\n\t\t\t{ 83,  6, false, false },\n\t\t\t{ 84,  7, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint16_t)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   1,   1, false, false },\n\t\t\t{   2,   0, false,  true }, // drop.\n\t\t\t{   3,   0, false,  true }, // drop.\n\t\t\t{   4,   0, false,  true }, // drop.\n\t\t\t{   5,   0, false,  true }, // drop.\n\t\t\t{   6,   0, false,  true }, // drop.\n\t\t\t{   7,   0, false,  true }, // drop.\n\t\t\t{   8,   0, false,  true }, // drop.\n\t\t\t{   9,   0, false,  true }, // drop.\n\t\t\t{ 120, 112, false, false },\n\t\t\t{ 121, 113, false, false },\n\t\t\t{ 122, 114, false, false },\n\t\t\t{ 123, 115, false, false },\n\t\t\t{ 124, 116, false, false },\n\t\t\t{ 125, 117, false, false },\n\t\t\t{ 126, 118, false, false },\n\t\t\t{ 127, 119, false, false },\n\t\t\t{ 128, 120, false, false },\n\t\t\t{ 129, 121, false, false },\n\t\t\t{ 130, 122, false, false },\n\t\t\t{ 131, 123, false, false },\n\t\t\t{ 132, 124, false, false },\n\t\t\t{ 133, 125, false, false },\n\t\t\t{ 134, 126, false, false },\n\t\t\t{ 135, 127, false, false },\n\t\t\t{ 136, 128, false, false },\n\t\t\t{ 137, 129, false, false },\n\t\t\t{ 138, 130, false, false },\n\t\t\t{ 139, 131, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint8_t)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint8_t>> inputs =\n\t\t{\n\t\t\t{   1,   1, false, false },\n\t\t\t{   2,   0, false,  true }, // drop.\n\t\t\t{   3,   0, false,  true }, // drop.\n\t\t\t{   4,   0, false,  true }, // drop.\n\t\t\t{   5,   0, false,  true }, // drop.\n\t\t\t{   6,   0, false,  true }, // drop.\n\t\t\t{   7,   0, false,  true }, // drop.\n\t\t\t{   8,   0, false,  true }, // drop.\n\t\t\t{   9,   0, false,  true }, // drop.\n\t\t\t{ 120, 112, false, false },\n\t\t\t{ 121, 113, false, false },\n\t\t\t{ 122, 114, false, false },\n\t\t\t{ 123, 115, false, false },\n\t\t\t{ 124, 116, false, false },\n\t\t\t{ 125, 117, false, false },\n\t\t\t{ 126, 118, false, false },\n\t\t\t{ 127, 119, false, false },\n\t\t\t{ 128, 120, false, false },\n\t\t\t{ 129, 121, false, false },\n\t\t\t{ 130, 122, false, false },\n\t\t\t{ 131, 123, false, false },\n\t\t\t{ 132, 124, false, false },\n\t\t\t{ 133, 125, false, false },\n\t\t\t{ 134, 126, false, false },\n\t\t\t{ 135, 127, false, false },\n\t\t\t{ 136, 128, false, false },\n\t\t\t{ 137, 129, false, false },\n\t\t\t{ 138, 130, false, false },\n\t\t\t{ 139, 131, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint8_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers, sync, drop in range 15\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     0,  0, false, false },\n\t\t\t{     1,  1, false, false },\n\t\t\t{     2,  2, false, false },\n\t\t\t{     3,  3, false, false },\n\t\t\t{     7,  7, false, false },\n\t\t\t{     6,  0, false,  true }, // drop.\n\t\t\t{     8,  8, false, false },\n\t\t\t{    10, 10, false, false },\n\t\t\t{     9,  9, false, false },\n\t\t\t{    11, 11, false, false },\n\t\t\t{     0, 12,  true, false }, // sync.\n\t\t\t{     2, 14, false, false },\n\t\t\t{     3, 15, false, false },\n\t\t\t{     4, 16, false, false },\n\t\t\t{     5, 17, false, false },\n\t\t\t{     6, 18, false, false },\n\t\t\t{     7, 19, false, false },\n\t\t\t{     8, 20, false, false },\n\t\t\t{     9, 21, false, false },\n\t\t\t{    10, 22, false, false },\n\t\t\t{     9,  0, false,  true }, // drop.\n\t\t\t{    61, 23,  true, false }, // sync.\n\t\t\t{    62, 24, false, false },\n\t\t\t{    63, 25, false, false },\n\t\t\t{    64, 26, false, false },\n\t\t\t{    65, 27, false, false },\n\t\t\t{    11, 28,  true, false }, // sync.\n\t\t\t{    12, 29, false, false },\n\t\t\t{    13, 30, false, false },\n\t\t\t{    14, 31, false, false },\n\t\t\t{    15, 32, false, false },\n\t\t\t{     1, 33,  true, false }, // sync.\n\t\t\t{     2, 34, false, false },\n\t\t\t{     3, 35, false, false },\n\t\t\t{     4, 36, false, false },\n\t\t\t{     5, 37, false, false },\n\t\t\t{ 32767, 38,  true, false }, // sync.\n\t\t\t{ 32768, 39, false, false },\n\t\t\t{ 32769, 40, false, false },\n\t\t\t{     0, 41,  true, false }, // sync.\n\t\t\t{     1, 42, false, false },\n\t\t\t{     3,  0, false,  true }, // drop.\n\t\t\t{     4, 44, false, false },\n\t\t\t{     5, 45, false, false },\n\t\t\t{     6, 46, false, false },\n\t\t\t{     7, 47, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint16_t with high values)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     1,     1, false, false },\n\t\t\t{     2,     0, false,  true }, // drop.\n\t\t\t{     3,     0, false,  true }, // drop.\n\t\t\t{     4,     0, false,  true }, // drop.\n\t\t\t{     5,     0, false,  true }, // drop.\n\t\t\t{     6,     0, false,  true }, // drop.\n\t\t\t{     7,     0, false,  true }, // drop.\n\t\t\t{     8,     0, false,  true }, // drop.\n\t\t\t{     9,     0, false,  true }, // drop.\n\t\t\t{ 32768, 32760, false, false },\n\t\t\t{ 32769, 32761, false, false },\n\t\t\t{ 32770, 32762, false, false },\n\t\t\t{ 32771, 32763, false, false },\n\t\t\t{ 32772, 32764, false, false },\n\t\t\t{ 32773, 32765, false, false },\n\t\t\t{ 32774, 32766, false, false },\n\t\t\t{ 32775, 32767, false, false },\n\t\t\t{ 32776, 32768, false, false },\n\t\t\t{ 32777, 32769, false, false },\n\t\t\t{ 32778, 32770, false, false },\n\t\t\t{ 32779, 32771, false, false },\n\t\t\t{ 32780, 32772, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"sync and drop some input near max-value\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 65530,  1,  true, false },\n\t\t\t{ 65531,  2, false, false },\n\t\t\t{ 65532,  3, false, false },\n\t\t\t{ 65533,  0, false, true  },\n\t\t\t{ 65534,  0, false, true  },\n\t\t\t{ 65535,  4, false, false },\n\t\t\t{     0,  5, false, false },\n\t\t\t{     1,  6, false, false },\n\t\t\t{     2,  7, false, false },\n\t\t\t{     3,  8, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint16_t range 15 with high values)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     1,     1, false, false },\n\t\t\t{     2,     0, false,  true }, // drop.\n\t\t\t{     3,     0, false,  true }, // drop.\n\t\t\t{     4,     0, false,  true }, // drop.\n\t\t\t{     5,     0, false,  true }, // drop.\n\t\t\t{     6,     0, false,  true }, // drop.\n\t\t\t{     7,     0, false,  true }, // drop.\n\t\t\t{     8,     0, false,  true }, // drop.\n\t\t\t{     9,     0, false,  true }, // drop.\n\t\t\t{ 16384, 16376, false, false },\n\t\t\t{ 16385, 16377, false, false },\n\t\t\t{ 16386, 16378, false, false },\n\t\t\t{ 16387, 16379, false, false },\n\t\t\t{ 16388, 16380, false, false },\n\t\t\t{ 16389, 16381, false, false },\n\t\t\t{ 16390, 16382, false, false },\n\t\t\t{ 16391, 16383, false, false },\n\t\t\t{ 16392, 16384, false, false },\n\t\t\t{ 16393, 16385, false, false },\n\t\t\t{ 16394, 16386, false, false },\n\t\t\t{ 16395, 16387, false, false },\n\t\t\t{ 16396, 16388, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"sync and drop some input near max-value in a 15bit range\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 32762,  1,  true, false, 32762 },\n\t\t\t{ 32763,  2, false, false, 32763 },\n\t\t\t{ 32764,  3, false, false, 32764 },\n\t\t\t{ 32765,  0, false, true,  32765 },\n\t\t\t{ 32766,  0, false, true,  32766 },\n\t\t\t{ 32767,  4, false, false, 32767 },\n\t\t\t{     0,  5, false, false,     0 },\n\t\t\t{     1,  6, false, false,     1 },\n\t\t\t{     2,  7, false, false,     2 },\n\t\t\t{     3,  8, false, false,     3 }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should update all values during multiple roll overs\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 1, true, false, 0 },\n\t\t};\n\t\tfor (uint16_t j = 0; j < 3; ++j) {\n\t\t\tfor (uint16_t i = 1; i < std::numeric_limits<uint16_t>::max(); ++i) {\n\t\t\t\tconst uint16_t output = i + 1;\n\t\t\t\tinputs.emplace_back( i, output, false, false, i );\n\t\t\t}\n\t\t}\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should update all values during multiple roll overs (15 bits range)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 1, true, false, 0, },\n\t\t};\n\t\tfor (uint16_t j = 0; j < 3; ++j) {\n\t\t\tfor (uint16_t i = 1; i < MaxNumberFor15Bits; ++i) {\n\t\t\t\tconst uint16_t output = i + 1;\n\t\t\t\tinputs.emplace_back( i, output, false, false, i );\n\t\t\t}\n\t\t}\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should produce same output for same old input before drop (15 bits range)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 10,  1, true,  false }, // sync.\n\t\t\t{ 11,  2, false, false },\n\t\t\t{ 12,  3, false, false },\n\t\t\t{ 13,  4, false, false },\n\t\t\t{ 14,  0, false, true  }, // drop.\n\t\t\t{ 15,  5, false, false },\n\t\t\t{ 12,  3, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should properly clean previous cycle drops\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint8_t>> inputs =\n\t\t{\n\t\t\t{ 1, 1, false, false },\n\t\t\t{ 2, 0, false, true  }, // Drop.\n\t\t\t{ 3, 2, false, false },\n\t\t\t{ 4, 3, false, false },\n\t\t\t{ 5, 4, false, false },\n\t\t\t{ 6, 5, false, false },\n\t\t\t{ 7, 6, false, false },\n\t\t\t{ 0, 7, false, false },\n\t\t\t{ 1, 0, false, false },\n\t\t\t{ 2, 1, false, false },\n\t\t\t{ 3, 2, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint8_t, 3>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed going out of range, 1.\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36964, 36964, false, false },\n\t\t\t{ 25923,     0, false, true  }, // Drop.\n\t\t\t{ 25701, 25701, false, false },\n\t\t\t{ 17170,     0, false, true  }, // Drop.\n\t\t\t{ 25923, 25923, false, false },\n\t\t\t{  4728,     0, false, true  }, // Drop.\n\t\t\t{ 17170, 17170, false, false },\n\t\t\t{ 30738,     0, false, true  }, // Drop.\n\t\t\t{  4728,  4728, false, false },\n\t\t\t{  4806,     0, false, true  }, // Drop.\n\t\t\t{ 30738, 30738, false, false },\n\t\t\t{ 50886,     0, false, true  }, // Drop.\n\t\t\t{  4806,  4805, false, false }, // Previously dropped.\n\t\t\t{ 50774,     0, false, true  }, // Drop.\n\t\t\t{ 50886,  4805, false, false }, // Previously dropped.\n\t\t\t{ 22136,     0, false, true  }, // Drop.\n\t\t\t{ 50774, 50773, false, false },\n\t\t\t{ 30910,     0, false, true  }, // Drop.\n\t\t\t{ 22136, 50773, false, false }, // Previously dropped.\n\t\t\t{ 48862,     0, false, true  }, // Drop.\n\t\t\t{ 30910, 30909, false, false },\n\t\t\t{ 56832,     0, false, true  }, // Drop.\n\t\t\t{ 48862, 48861, false, false },\n\t\t\t{     2,     0, false, true  }, // Drop.\n\t\t\t{ 56832, 48861, false, false }, // Previously dropped.\n\t\t\t{   530,     0, false, true  }, // Drop.\n\t\t\t{     2, 48861, false, false }, // Previously dropped.\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed go out of range, 2.\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36960, 36960, false, false },\n\t\t\t{  3328,     0, false, true  }, // Drop.\n\t\t\t{ 24589, 24588, false, false },\n\t\t\t{   120,     0, false, true  }, // Drop.\n\t\t\t{  3328, 24588, false, false }, // Previously dropped.\n\t\t\t{ 30848,     0, false, true  }, // Drop.\n\t\t\t{   120,   120, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed go out of range, 3.\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36964, 36964, false, false },\n\t\t\t{ 65396 ,    0, false, true  }, // Drop.\n\t\t\t{ 25855, 25854, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, no sync, no drop (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0, 1000, false, false },\n\t\t\t{  1, 1001, false, false },\n\t\t\t{  2, 1002, false, false },\n\t\t\t{  3, 1003, false, false },\n\t\t\t{  4, 1004, false, false },\n\t\t\t{  5, 1005, false, false },\n\t\t\t{  6, 1006, false, false },\n\t\t\t{  7, 1007, false, false },\n\t\t\t{  8, 1008, false, false },\n\t\t\t{  9, 1009, false, false },\n\t\t\t{ 10, 1010, false, false },\n\t\t\t{ 11, 1011, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, no drop (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0, 2000, false, false },\n\t\t\t{  1, 2001, false, false },\n\t\t\t{  2, 2002, false, false },\n\t\t\t{ 80, 2003,  true, false },\n\t\t\t{ 81, 2004, false, false },\n\t\t\t{ 82, 2005, false, false },\n\t\t\t{ 83, 2006, false, false },\n\t\t\t{ 84, 2007, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, drop (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0, 3000, false, false },\n\t\t\t{  1, 3001, false, false },\n\t\t\t{  2, 3002, false, false },\n\t\t\t{  3, 3003, false, false },\n\t\t\t{  4, 3004,  true, false }, // sync.\n\t\t\t{  5, 3005, false, false },\n\t\t\t{  6, 3006, false, false },\n\t\t\t{  7, 3007,  true, false }, // sync.\n\t\t\t{  8, 3000, false,  true }, // drop.\n\t\t\t{  9, 3008, false, false },\n\t\t\t{ 11, 3000, false,  true }, // drop.\n\t\t\t{ 10, 3009, false, false },\n\t\t\t{ 12, 3010, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 3000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 3000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered wrapped numbers (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 65533,  997, false, false },\n\t\t\t{ 65534,  998, false, false },\n\t\t\t{ 65535,  999, false, false },\n\t\t\t{     0, 1000, false, false },\n\t\t\t{     1, 1001, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive sequence numbers with a big jump (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs1 =\n\t\t{\n\t\t\t{    0, 32000, false, false },\n\t\t\t{    1, 32001, false, false },\n\t\t\t{ 1000, 33000, false, false },\n\t\t\t{ 1001, 33001, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 32000u }, inputs1);\n\t\tREQUIRE(result.first == result.second);\n\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs2 =\n\t\t{\n\t\t\t{    0, 32000, false, false },\n\t\t\t{    1, 32001, false, false },\n\t\t\t{ 1000,   232, false, false },\n\t\t\t{ 1001,   233, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 32000u }, inputs2);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive out of order numbers with a big jump (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{      4, 1004, false, false },\n\t\t\t{      3, 1003, false, false },\n\t\t\t{  65535,  999, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers with a big jump, drop before jump (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   0, 1000, false, false },\n\t\t\t{   1, 1000, false,  true }, // drop.\n\t\t\t{ 100, 1099, false, false },\n\t\t\t{ 100, 1099, false, false },\n\t\t\t{ 103, 1000, false,  true }, // drop.\n\t\t\t{ 101, 1100, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers with a big jump, drop after jump (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   0, 2000, false, false },\n\t\t\t{   1, 2001, false, false },\n\t\t\t{ 100, 2000, false,  true }, // drop.\n\t\t\t{ 103, 2000, false,  true }, // drop.\n\t\t\t{ 101, 2100, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop, receive numbers newer and older than the one dropped (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 2000, false, false },\n\t\t\t{ 2, 2000, false,  true }, // drop.\n\t\t\t{ 3, 2002, false, false },\n\t\t\t{ 4, 2003, false, false },\n\t\t\t{ 1, 2001, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 2000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers, sync, drop (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     0, 10000, false, false },\n\t\t\t{     1, 10001, false, false },\n\t\t\t{     2, 10002, false, false },\n\t\t\t{     3, 10003, false, false },\n\t\t\t{     7, 10007, false, false },\n\t\t\t{     6, 10000, false,  true }, // drop.\n\t\t\t{     8, 10008, false, false },\n\t\t\t{    10, 10010, false, false },\n\t\t\t{     9, 10009, false, false },\n\t\t\t{    11, 10011, false, false },\n\t\t\t{     0, 10012,  true, false }, // sync.\n\t\t\t{     2, 10014, false, false },\n\t\t\t{     3, 10015, false, false },\n\t\t\t{     4, 10016, false, false },\n\t\t\t{     5, 10017, false, false },\n\t\t\t{     6, 10018, false, false },\n\t\t\t{     7, 10019, false, false },\n\t\t\t{     8, 10020, false, false },\n\t\t\t{     9, 10021, false, false },\n\t\t\t{    10, 10022, false, false },\n\t\t\t{     9, 10000, false,  true }, // drop.\n\t\t\t{    61, 10023,  true, false }, // sync.\n\t\t\t{    62, 10024, false, false },\n\t\t\t{    63, 10025, false, false },\n\t\t\t{    64, 10026, false, false },\n\t\t\t{    65, 10027, false, false },\n\t\t\t{    11, 10028,  true, false }, // sync.\n\t\t\t{    12, 10029, false, false },\n\t\t\t{    13, 10030, false, false },\n\t\t\t{    14, 10031, false, false },\n\t\t\t{    15, 10032, false, false },\n\t\t\t{     1, 10033,  true, false }, // sync.\n\t\t\t{     2, 10034, false, false },\n\t\t\t{     3, 10035, false, false },\n\t\t\t{     4, 10036, false, false },\n\t\t\t{     5, 10037, false, false },\n\t\t\t{ 65533, 10038,  true, false }, // sync.\n\t\t\t{ 65534, 10039, false, false },\n\t\t\t{ 65535, 10040, false, false },\n\t\t\t{     0, 10041,  true, false }, // sync.\n\t\t\t{     1, 10042, false, false },\n\t\t\t{     3, 10000, false,  true }, // drop.\n\t\t\t{     4, 10044, false, false },\n\t\t\t{     5, 10045, false, false },\n\t\t\t{     6, 10046, false, false },\n\t\t\t{     7, 10047, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 10000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive ordered numbers, sync, no drop, increase input (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{  0,  1, false, false },\n\t\t\t{  1,  2, false, false },\n\t\t\t{  2,  3, false, false },\n\t\t\t{ 80,  4,  true, false },\n\t\t\t{ 81,  5, false, false },\n\t\t\t{ 82,  6, false, false },\n\t\t\t{ 83,  7, false, false },\n\t\t\t{ 84,  8, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 1u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint16_t) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{   1, 1001, false, false },\n\t\t\t{   2, 1000, false,  true }, // drop.\n\t\t\t{   3, 1000, false,  true }, // drop.\n\t\t\t{   4, 1000, false,  true }, // drop.\n\t\t\t{   5, 1000, false,  true }, // drop.\n\t\t\t{   6, 1000, false,  true }, // drop.\n\t\t\t{   7, 1000, false,  true }, // drop.\n\t\t\t{   8, 1000, false,  true }, // drop.\n\t\t\t{   9, 1000, false,  true }, // drop.\n\t\t\t{ 120, 1112, false, false },\n\t\t\t{ 121, 1113, false, false },\n\t\t\t{ 122, 1114, false, false },\n\t\t\t{ 123, 1115, false, false },\n\t\t\t{ 124, 1116, false, false },\n\t\t\t{ 125, 1117, false, false },\n\t\t\t{ 126, 1118, false, false },\n\t\t\t{ 127, 1119, false, false },\n\t\t\t{ 128, 1120, false, false },\n\t\t\t{ 129, 1121, false, false },\n\t\t\t{ 130, 1122, false, false },\n\t\t\t{ 131, 1123, false, false },\n\t\t\t{ 132, 1124, false, false },\n\t\t\t{ 133, 1125, false, false },\n\t\t\t{ 134, 1126, false, false },\n\t\t\t{ 135, 1127, false, false },\n\t\t\t{ 136, 1128, false, false },\n\t\t\t{ 137, 1129, false, false },\n\t\t\t{ 138, 1130, false, false },\n\t\t\t{ 139, 1131, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\n\t\tresult = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint8_t) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint8_t>> inputs =\n\t\t{\n\t\t\t{   1, 201, false, false },\n\t\t\t{   2, 200, false,  true }, // drop.\n\t\t\t{   3, 200, false,  true }, // drop.\n\t\t\t{   4, 200, false,  true }, // drop.\n\t\t\t{   5, 200, false,  true }, // drop.\n\t\t\t{   6, 200, false,  true }, // drop.\n\t\t\t{   7, 200, false,  true }, // drop.\n\t\t\t{   8, 200, false,  true }, // drop.\n\t\t\t{   9, 200, false,  true }, // drop.\n\t\t\t{ 120,  56, false, false },\n\t\t\t{ 121,  57, false, false },\n\t\t\t{ 122,  58, false, false },\n\t\t\t{ 123,  59, false, false },\n\t\t\t{ 124,  60, false, false },\n\t\t\t{ 125,  61, false, false },\n\t\t\t{ 126,  62, false, false },\n\t\t\t{ 127,  63, false, false },\n\t\t\t{ 128,  64, false, false },\n\t\t\t{ 129,  65, false, false },\n\t\t\t{ 130,  66, false, false },\n\t\t\t{ 131,  67, false, false },\n\t\t\t{ 132,  68, false, false },\n\t\t\t{ 133,  69, false, false },\n\t\t\t{ 134,  70, false, false },\n\t\t\t{ 135,  71, false, false },\n\t\t\t{ 136,  72, false, false },\n\t\t\t{ 137,  73, false, false },\n\t\t\t{ 138,  74, false, false },\n\t\t\t{ 139,  75, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint8_t>{ /*initialOutput*/ 200u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive mixed numbers, sync, drop in range 15 (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     0, 100, false, false },\n\t\t\t{     1, 101, false, false },\n\t\t\t{     2, 102, false, false },\n\t\t\t{     3, 103, false, false },\n\t\t\t{     7, 107, false, false },\n\t\t\t{     6, 100, false,  true }, // drop.\n\t\t\t{     8, 108, false, false },\n\t\t\t{    10, 110, false, false },\n\t\t\t{     9, 109, false, false },\n\t\t\t{    11, 111, false, false },\n\t\t\t{     0, 112,  true, false }, // sync.\n\t\t\t{     2, 114, false, false },\n\t\t\t{     3, 115, false, false },\n\t\t\t{     4, 116, false, false },\n\t\t\t{     5, 117, false, false },\n\t\t\t{     6, 118, false, false },\n\t\t\t{     7, 119, false, false },\n\t\t\t{     8, 120, false, false },\n\t\t\t{     9, 121, false, false },\n\t\t\t{    10, 122, false, false },\n\t\t\t{     9, 100, false,  true }, // drop.\n\t\t\t{    61, 123,  true, false }, // sync.\n\t\t\t{    62, 124, false, false },\n\t\t\t{    63, 125, false, false },\n\t\t\t{    64, 126, false, false },\n\t\t\t{    65, 127, false, false },\n\t\t\t{    11, 128,  true, false }, // sync.\n\t\t\t{    12, 129, false, false },\n\t\t\t{    13, 130, false, false },\n\t\t\t{    14, 131, false, false },\n\t\t\t{    15, 132, false, false },\n\t\t\t{     1, 133,  true, false }, // sync.\n\t\t\t{     2, 134, false, false },\n\t\t\t{     3, 135, false, false },\n\t\t\t{     4, 136, false, false },\n\t\t\t{     5, 137, false, false },\n\t\t\t{ 32767, 138,  true, false }, // sync.\n\t\t\t{ 32768, 139, false, false },\n\t\t\t{ 32769, 140, false, false },\n\t\t\t{     0, 141,  true, false }, // sync.\n\t\t\t{     1, 142, false, false },\n\t\t\t{     3, 100, false,  true }, // drop.\n\t\t\t{     4, 144, false, false },\n\t\t\t{     5, 145, false, false },\n\t\t\t{     6, 146, false, false },\n\t\t\t{     7, 147, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"drop many inputs at the beginning (using uint16_t with high values) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     1,   201, false, false },\n\t\t\t{     2,   200, false,  true }, // drop.\n\t\t\t{     3,   200, false,  true }, // drop.\n\t\t\t{     4,   200, false,  true }, // drop.\n\t\t\t{     5,   200, false,  true }, // drop.\n\t\t\t{     6,   200, false,  true }, // drop.\n\t\t\t{     7,   200, false,  true }, // drop.\n\t\t\t{     8,   200, false,  true }, // drop.\n\t\t\t{     9,   200, false,  true }, // drop.\n\t\t\t{ 32768, 32960, false, false },\n\t\t\t{ 32769, 32961, false, false },\n\t\t\t{ 32770, 32962, false, false },\n\t\t\t{ 32771, 32963, false, false },\n\t\t\t{ 32772, 32964, false, false },\n\t\t\t{ 32773, 32965, false, false },\n\t\t\t{ 32774, 32966, false, false },\n\t\t\t{ 32775, 32967, false, false },\n\t\t\t{ 32776, 32968, false, false },\n\t\t\t{ 32777, 32969, false, false },\n\t\t\t{ 32778, 32970, false, false },\n\t\t\t{ 32779, 32971, false, false },\n\t\t\t{ 32780, 32972, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 200u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"sync and drop some input near max-value (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 65530, 201,  true, false },\n\t\t\t{ 65531, 202, false, false },\n\t\t\t{ 65532, 203, false, false },\n\t\t\t{ 65533, 200, false, true  },\n\t\t\t{ 65534, 200, false, true  },\n\t\t\t{ 65535, 204, false, false },\n\t\t\t{     0, 205, false, false },\n\t\t\t{     1, 206, false, false },\n\t\t\t{     2, 207, false, false },\n\t\t\t{     3, 208, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 200u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\n\t  \"drop many inputs at the beginning (using uint16_t range 15 with high values) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{     1,   101, false, false },\n\t\t\t{     2,   100, false,  true }, // drop.\n\t\t\t{     3,   100, false,  true }, // drop.\n\t\t\t{     4,   100, false,  true }, // drop.\n\t\t\t{     5,   100, false,  true }, // drop.\n\t\t\t{     6,   100, false,  true }, // drop.\n\t\t\t{     7,   100, false,  true }, // drop.\n\t\t\t{     8,   100, false,  true }, // drop.\n\t\t\t{     9,   100, false,  true }, // drop.\n\t\t\t{ 16384, 16476, false, false },\n\t\t\t{ 16385, 16477, false, false },\n\t\t\t{ 16386, 16478, false, false },\n\t\t\t{ 16387, 16479, false, false },\n\t\t\t{ 16388, 16480, false, false },\n\t\t\t{ 16389, 16481, false, false },\n\t\t\t{ 16390, 16482, false, false },\n\t\t\t{ 16391, 16483, false, false },\n\t\t\t{ 16392, 16484, false, false },\n\t\t\t{ 16393, 16485, false, false },\n\t\t\t{ 16394, 16486, false, false },\n\t\t\t{ 16395, 16487, false, false },\n\t\t\t{ 16396, 16488, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"sync and drop some input near max-value in a 15bit range (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 32762, 101,  true, false, 32762 },\n\t\t\t{ 32763, 102, false, false, 32763 },\n\t\t\t{ 32764, 103, false, false, 32764 },\n\t\t\t{ 32765, 100, false, true,  32765 },\n\t\t\t{ 32766, 100, false, true,  32766 },\n\t\t\t{ 32767, 104, false, false, 32767 },\n\t\t\t{     0, 105, false, false,     0 },\n\t\t\t{     1, 106, false, false,     1 },\n\t\t\t{     2, 107, false, false,     2 },\n\t\t\t{     3, 108, false, false,     3 }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should update all values during multiple roll overs (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 101, true, false, 0 },\n\t\t};\n\t\tfor (uint16_t j = 0; j < 3; ++j) {\n\t\t\tfor (uint16_t i = 1; i < std::numeric_limits<uint16_t>::max(); ++i) {\n\t\t\t\tconst uint16_t output = (i + 1 + 100) & std::numeric_limits<uint16_t>::max();\n\t\t\t\tinputs.emplace_back( i, output, false, false, i );\n\t\t\t}\n\t\t}\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should update all values during multiple roll overs (15 bits range) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 101, true, false, 0, },\n\t\t};\n\t\tfor (uint16_t j = 0; j < 3; ++j) {\n\t\t\tfor (uint16_t i = 1; i < MaxNumberFor15Bits; ++i) {\n\t\t\t\tconst uint16_t output = (i + 1 + 100) & MaxNumberFor15Bits;\n\t\t\t\tinputs.emplace_back( i, output, false, false, i );\n\t\t\t}\n\t\t}\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\n\t  \"should produce same output for same old input before drop (15 bits range) (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 10, 10001, true,  false }, // sync.\n\t\t\t{ 11, 10002, false, false },\n\t\t\t{ 12, 10003, false, false },\n\t\t\t{ 13, 10004, false, false },\n\t\t\t{ 14, 10000, false, true  }, // drop.\n\t\t\t{ 15, 10005, false, false },\n\t\t\t{ 12, 10003, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t, 15>{ /*initialOutput*/ 10000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"should properly clean previous cycle drops (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint8_t>> inputs =\n\t\t{\n\t\t\t{ 1, 3, false, false },\n\t\t\t{ 2, 2, false, true  }, // Drop.\n\t\t\t{ 3, 4, false, false },\n\t\t\t{ 4, 5, false, false },\n\t\t\t{ 5, 6, false, false },\n\t\t\t{ 6, 7, false, false },\n\t\t\t{ 7, 0, false, false },\n\t\t\t{ 0, 1, false, false },\n\t\t\t{ 1, 2, false, false },\n\t\t\t{ 2, 3, false, false },\n\t\t\t{ 3, 4, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint8_t, 3>{ /*initialOutput*/ 2u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed going out of range, 1. (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36964, 46964, false, false },\n\t\t\t{ 25923, 10000, false, true  }, // Drop.\n\t\t\t{ 25701, 35701, false, false },\n\t\t\t{ 17170, 10000, false, true  }, // Drop.\n\t\t\t{ 25923, 35923, false, false },\n\t\t\t{  4728, 10000, false, true  }, // Drop.\n\t\t\t{ 17170, 27170, false, false },\n\t\t\t{ 30738, 10000, false, true  }, // Drop.\n\t\t\t{  4728, 14728, false, false },\n\t\t\t{  4806, 10000, false, true  }, // Drop.\n\t\t\t{ 30738, 40738, false, false },\n\t\t\t{ 50886, 10000, false, true  }, // Drop.\n\t\t\t{  4806, 14805, false, false }, // Previously dropped.\n\t\t\t{ 50774, 10000, false, true  }, // Drop.\n\t\t\t{ 50886, 14805, false, false }, // Previously dropped.\n\t\t\t{ 22136, 10000, false, true  }, // Drop.\n\t\t\t{ 50774, 60773, false, false },\n\t\t\t{ 30910, 10000, false, true  }, // Drop.\n\t\t\t{ 22136, 60773, false, false }, // Previously dropped.\n\t\t\t{ 48862, 10000, false, true  }, // Drop.\n\t\t\t{ 30910, 40909, false, false },\n\t\t\t{ 56832, 10000, false, true  }, // Drop.\n\t\t\t{ 48862, 58861, false, false },\n\t\t\t{     2, 10000, false, true  }, // Drop.\n\t\t\t{ 56832, 58861, false, false }, // Previously dropped.\n\t\t\t{   530, 10000, false, true  }, // Drop.\n\t\t\t{     2, 58861, false, false }, // Previously dropped.\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 10000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed go out of range, 2. (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36960, 37060, false, false },\n\t\t\t{  3328,   100, false, true  }, // Drop.\n\t\t\t{ 24589, 24688, false, false },\n\t\t\t{   120,   100, false, true  }, // Drop.\n\t\t\t{  3328, 24688, false, false }, // Previously dropped.\n\t\t\t{ 30848,   100, false, true  }, // Drop.\n\t\t\t{   120,   220, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 100u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"dropped inputs to be removed go out of range, 3. (with initial output)\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 36964, 37964, false, false },\n\t\t\t{ 65396 , 1000, false, true  }, // Drop.\n\t\t\t{ 25855, 26854, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{ /*initialOutput*/ 1000u }, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\t// https://github.com/versatica/mediasoup/issues/1615\n\tSECTION(\"receive dropped inputs out of order\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0, 0, false, false },\n\t\t\t{ 2, 0, false, true  },\n\t\t\t{ 1, 0, false, true  },\n\t\t\t{ 3, 1, false, false  },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive dropped inputs out of order, 2\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 0,   0, false, false },\n\t\t\t{ 1,   1, false, false },\n\t\t\t{ 2,   2, false, false },\n\t\t\t{ 4,   4, false, false },\n\t\t\t{ 5,   5, false, false },\n\t\t\t{ 6,   0, false, true  },\n\t\t\t{ 7,   0, false, true  },\n\t\t\t{ 8,   0, false, true  },\n\t\t\t{ 3,   0, false, true  },\n\t\t\t{ 9,   0, false, true  },\n\t\t\t{ 10,  6, false, false }\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive dropped inputs out of order, 3\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 1, 1, false, false },\n\t\t\t{ 2, 2, false, false },\n\t\t\t{ 3, 3, false, false },\n\t\t\t{ 4, 4, false, false },\n\t\t\t{ 5, 0, false, true },\n\t\t\t{ 6, 0, false, true },\n\t\t\t{ 7, 0, false, true },\n\t\t\t{ 8, 0, false, true },\n\t\t\t{ 0, 0, false, true },\n\t\t\t{ 9, 5, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n\n\tSECTION(\"receive dropped inputs out of order, 4\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestSeqManagerInput<uint16_t>> inputs =\n\t\t{\n\t\t\t{ 1, 1, false, false },\n\t\t\t{ 2, 2, false, false },\n\t\t\t{ 3, 3, false, false },\n\t\t\t{ 4, 4, false, false },\n\t\t\t{ 5, 5, false, false },\n\t\t\t{ 6, 6, false, false },\n\t\t\t{ 7, 7, false, false },\n\t\t\t{ 8, 8, false, false },\n\t\t\t{ 9, 9, false, false },\n\t\t\t{ 10, 10, false, false },\n\t\t\t{ 11, 11, false, false },\n\t\t\t{ 12, 12, false, false },\n\t\t\t{ 13, 13, false, false },\n\t\t\t{ 14, 14, false, false },\n\t\t\t{ 15, 15, false, false },\n\t\t\t{ 16, 16, false, false },\n\t\t\t{ 17, 17, false, false },\n\t\t\t{ 18, 18, false, false },\n\t\t\t{ 19, 19, false, false },\n\t\t\t{ 21, 21, false, false },\n\t\t\t{ 22, 22, false, false },\n\t\t\t{ 23, 23, false, false },\n\t\t\t{ 24, 24, false, false },\n\t\t\t{ 25, 25, false, false },\n\t\t\t{ 26, 26, false, false },\n\t\t\t{ 27, 0, false, true },\n\t\t\t{ 28, 0, false, true },\n\t\t\t{ 29, 0, false, true },\n\t\t\t{ 20, 20, false, false },\n\t\t\t{ 30, 0, false, true },\n\t\t\t{ 31, 0, false, true },\n\t\t\t{ 32, 0, false, true },\n\t\t\t{ 33, 0, false, true },\n\t\t\t{ 34, 0, false, true },\n\t\t\t{ 35, 0, false, true },\n\t\t\t{ 36, 0, false, true },\n\t\t\t{ 38, 0, false, true },\n\t\t\t{ 39, 0, false, true },\n\t\t\t{ 40, 0, false, true },\n\t\t\t{ 41, 0, false, true },\n\t\t\t{ 42, 0, false, true },\n\t\t\t{ 44, 0, false, true },\n\t\t\t{ 45, 0, false, true },\n\t\t\t{ 46, 0, false, true },\n\t\t\t{ 47, 29, false, false },\n\t\t\t{ 48, 30, false, false },\n\t\t\t{ 49, 31, false, false },\n\t\t\t{ 51, 33, false, false },\n\t\t\t{ 52, 34, false, false },\n\t\t\t{ 53, 35, false, false },\n\t\t\t{ 54, 36, false, false },\n\t\t\t{ 55, 0, false, true },\n\t\t\t{ 56, 0, false, true },\n\t\t\t{ 57, 0, false, true },\n\t\t\t{ 58, 0, false, true },\n\t\t\t{ 59, 0, false, true },\n\t\t\t{ 37, 0, false, true },\n\t\t\t{ 60, 0, false, true },\n\t\t\t{ 61, 0, false, true },\n\t\t\t{ 62, 0, false, true },\n\t\t\t{ 63, 0, false, true },\n\t\t\t{ 64, 0, false, true },\n\t\t\t{ 43, 0, false, true },\n\t\t\t{ 50, 32, false, false },\n\t\t};\n\t\t// clang-format on\n\n\t\tauto result = validate(RTC::SeqManager<uint16_t>{}, inputs);\n\t\tREQUIRE(result.first == result.second);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestSimpleConsumer.cpp",
    "content": "#include \"flatbuffers/buffer.h\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"FBS/rtpParameters.h\"\n#include \"FBS/transport.h\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/RTP/RtpStream.hpp\"\n#include \"RTC/RTP/RtpStreamRecv.hpp\"\n#include \"RTC/RTP/SharedPacket.hpp\"\n#include \"RTC/RtpDictionaries.hpp\"\n#include \"RTC/SimpleConsumer.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nnamespace\n{\n\t// NOLINTBEGIN(readability-identifier-naming)\n\tconst uint8_t payloadType = 111;\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\t// NOLINTEND(readability-identifier-naming)\n\n\tclass RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener\n\t{\n\tpublic:\n\t\tvoid OnRtpStreamScore(\n\t\t  RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override\n\t\t{\n\t\t}\n\n\t\tvoid OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) override\n\t\t{\n\t\t}\n\n\t\tvoid OnRtpStreamNeedWorstRemoteFractionLost(\n\t\t  RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override\n\t\t{\n\t\t}\n\t};\n\n\tclass ConsumerListener : public RTC::Consumer::Listener\n\t{\n\t\tvoid OnConsumerSendRtpPacket(RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet) final\n\t\t{\n\t\t\tthis->sent.push_back(packet->GetSequenceNumber());\n\t\t};\n\t\tvoid OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) final\n\t\t{\n\t\t}\n\t\tvoid OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) final {};\n\t\tvoid OnConsumerNeedBitrateChange(RTC::Consumer* consumer) final {};\n\t\tvoid OnConsumerNeedZeroBitrate(RTC::Consumer* consumer) final {};\n\t\tvoid OnConsumerProducerClosed(RTC::Consumer* consumer) final {};\n\n\tpublic:\n\t\t// Verifies that the given number of packets have been sent,\n\t\t// and that the sequence numbers are consecutive.\n\t\tvoid Verify(size_t size)\n\t\t{\n\t\t\tREQUIRE(this->sent.size() == size);\n\n\t\t\tif (this->sent.size() <= 1)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto currentSeq = this->sent[0];\n\n\t\t\tfor (auto it = std::next(this->sent.begin()); it != this->sent.end(); ++it)\n\t\t\t{\n\t\t\t\tREQUIRE(*it == currentSeq + 1);\n\t\t\t\tcurrentSeq = *it;\n\t\t\t}\n\t\t}\n\n\tprivate:\n\t\tstd::vector<uint16_t> sent;\n\t};\n\n\tflatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters>>> createRtpEncodingParameters(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tstd::vector<flatbuffers::Offset<FBS::RtpParameters::RtpEncodingParameters>> encodings;\n\n\t\tauto encoding = RTC::RtpEncodingParameters();\n\n\t\tencoding.ssrc = 1234567890;\n\n\t\tencodings.emplace_back(encoding.FillBuffer(builder));\n\n\t\treturn builder.CreateVector(encodings);\n\t};\n\n\tflatbuffers::Offset<FBS::RtpParameters::RtpParameters> createRtpParameters(\n\t  flatbuffers::FlatBufferBuilder& builder)\n\t{\n\t\tauto rtpParameters = RTC::RtpParameters();\n\t\tauto codec         = RTC::RtpCodecParameters();\n\t\tauto encoding      = RTC::RtpEncodingParameters();\n\n\t\tcodec.mimeType.SetMimeType(\"audio/opus\");\n\t\tcodec.payloadType = payloadType;\n\n\t\tencoding.ssrc = 1234567890;\n\n\t\trtpParameters.mid = \"mid\";\n\t\trtpParameters.codecs.emplace_back(codec);\n\t\trtpParameters.encodings.emplace_back(encoding);\n\t\trtpParameters.headerExtensions = std::vector<RTC::RtpHeaderExtensionParameters>();\n\n\t\treturn rtpParameters.FillBuffer(builder);\n\t};\n\n\tstd::unique_ptr<RTC::SimpleConsumer> createConsumer(ConsumerListener* listener)\n\t{\n\t\tflatbuffers::FlatBufferBuilder bufferBuilder;\n\n\t\tauto consumerId          = bufferBuilder.CreateString(\"consumerId\");\n\t\tauto producerId          = bufferBuilder.CreateString(\"producerId\");\n\t\tauto rtpParameters       = createRtpParameters(bufferBuilder);\n\t\tauto consumableEncodings = createRtpEncodingParameters(bufferBuilder);\n\n\t\tauto consumeRequestBuilder = FBS::Transport::ConsumeRequestBuilder(bufferBuilder);\n\n\t\tconsumeRequestBuilder.add_consumerId(consumerId);\n\t\tconsumeRequestBuilder.add_producerId(producerId);\n\t\tconsumeRequestBuilder.add_kind(FBS::RtpParameters::MediaKind::AUDIO);\n\t\tconsumeRequestBuilder.add_rtpParameters(rtpParameters);\n\t\tconsumeRequestBuilder.add_type(FBS::RtpParameters::Type::SIMPLE);\n\t\tconsumeRequestBuilder.add_consumableRtpEncodings(consumableEncodings);\n\t\tconsumeRequestBuilder.add_paused(false);\n\t\tconsumeRequestBuilder.add_preferredLayers(0);\n\t\tconsumeRequestBuilder.add_ignoreDtx(false);\n\n\t\tauto offset = consumeRequestBuilder.Finish();\n\t\tbufferBuilder.Finish(offset);\n\n\t\tauto* buf = bufferBuilder.GetBufferPointer();\n\n\t\tconst auto* consumeRequest = flatbuffers::GetRoot<FBS::Transport::ConsumeRequest>(buf);\n\n\t\treturn std::make_unique<RTC::SimpleConsumer>(\n\t\t  std::addressof(shared),\n\t\t  consumeRequest->consumerId()->str(),\n\t\t  consumeRequest->producerId()->str(),\n\t\t  listener,\n\t\t  consumeRequest);\n\t}\n\n\tstd::unique_ptr<RTC::RTP::RtpStreamRecv> createRtpStreamRecv()\n\t{\n\t\tRtpStreamRecvListener streamRecvListener;\n\t\tRTC::RTP::RtpStream::Params params;\n\n\t\treturn std::make_unique<RTC::RTP::RtpStreamRecv>(\n\t\t  &streamRecvListener, std::addressof(shared), params, 0u, false);\n\t}\n\n\t/**\n\t * Centralize common setup and helper methods.\n\t */\n\tclass Fixture\n\t{\n\tpublic:\n\t\tFixture()\n\t\t  : listener(std::make_unique<ConsumerListener>()),\n\t\t    consumer(createConsumer(listener.get())),\n\t\t    rtpStream(createRtpStreamRecv())\n\t\t{\n\t\t\t// NOTE: This must be static because the Consumer stores the given vector\n\t\t\t// pointer which is supposed to exist in the associated Producer (but here\n\t\t\t// there is no associated Producer).\n\t\t\tconst std::vector<uint8_t> scores{ 10 };\n\n\t\t\tconsumer->ProducerRtpStreamScores(&scores);\n\n\t\t\t// NOTE: mappedSsrc here MUST be 1234567890 (otherwise Consumer will crash).\n\t\t\t// This is guaranteed by Producer class, however here we must do it manually.\n\t\t\tconsumer->ProducerNewRtpStream(rtpStream.get(), 1234567890);\n\t\t}\n\n\t\tstd::unique_ptr<ConsumerListener> listener;\n\t\tstd::unique_ptr<RTC::SimpleConsumer> consumer;\n\t\tstd::unique_ptr<RTC::RTP::RtpStreamRecv> rtpStream;\n\t};\n} // namespace\n\nSCENARIO(\"SimpleConsumer\", \"[rtp][consumer]\")\n{\n\t// TODO: We should NOT parse RTP packets for tests anymore. We should use\n\t// RTC::RTP::Packet::Factory() instead.\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x80, 0x01, 0x00, 0x08,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x49, 0x96, 0x02, 0xD2, // SSRC: 1234567890 (must be this exact value).\n\t\t// Payload (4 bytes).\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t// From here this is just buffer enough for the variable length payload so\n\t\t// when cloning the packet it doesn't read non allocated memory.\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t\t0xFF, 0xFF, 0xFF, 0xFF,\n\t};\n\t// clang-format on\n\n\t// This is the size of the original packet.\n\tconst size_t originalPacketLength{ 16 };\n\n\tSECTION(\"RTP packets are not forwarded when the consumer is not active\")\n\t{\n\t\tFixture fixture;\n\t\tauto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64);\n\t\tRTC::RTP::SharedPacket sharedPacket(packet);\n\n\t\tpacket->SetPayloadType(payloadType);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\n\t\tfixture.listener->Verify(0);\n\n\t\tdelete packet;\n\t}\n\n\tSECTION(\"RTP packets are not forwarded for unsupported payload types\")\n\t{\n\t\tFixture fixture;\n\n\t\t// Indicate that the transport is connected in order to activate the consumer.\n\t\tdynamic_cast<RTC::Consumer*>(fixture.consumer.get())->TransportConnected();\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64);\n\t\tRTC::RTP::SharedPacket sharedPacket(packet);\n\n\t\tpacket->SetPayloadType(payloadType + 1);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\t\tfixture.listener->Verify(0);\n\n\t\tdelete packet;\n\t}\n\n\tSECTION(\"RTP packets with empty payload are not forwarded\")\n\t{\n\t\tFixture fixture;\n\n\t\t// Indicate that the transport is connected in order to activate the consumer.\n\t\tdynamic_cast<RTC::Consumer*>(fixture.consumer.get())->TransportConnected();\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 0);\n\t\tRTC::RTP::SharedPacket sharedPacket(packet);\n\n\t\tpacket->SetPayloadType(payloadType + 1);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\t\tfixture.listener->Verify(0);\n\n\t\tdelete packet;\n\t}\n\n\tSECTION(\"outgoing RTP packets are forwarded with increased sequence number\")\n\t{\n\t\tFixture fixture;\n\n\t\t// Indicate that the transport is connected in order to activate the consumer.\n\t\tdynamic_cast<RTC::Consumer*>(fixture.consumer.get())->TransportConnected();\n\n\t\tauto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64);\n\t\tRTC::RTP::SharedPacket sharedPacket(packet);\n\n\t\tuint16_t seq{ 1 };\n\n\t\tpacket->SetSequenceNumber(seq++);\n\t\tpacket->SetPayloadType(payloadType);\n\t\tsharedPacket.Assign(packet);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\n\t\tpacket->SetSequenceNumber(seq++);\n\t\tsharedPacket.Assign(packet);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\n\t\tpacket->SetSequenceNumber(seq++);\n\t\tsharedPacket.Assign(packet);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\n\t\tpacket->SetSequenceNumber(seq++);\n\t\t// Remove the payload so it won't be sent.\n\t\tpacket->RemovePayload();\n\t\tsharedPacket.Assign(packet);\n\n\t\tfixture.consumer->SendRtpPacket(packet, sharedPacket);\n\n\t\tfixture.listener->Verify(3);\n\n\t\tdelete packet;\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestTransportCongestionControlServer.cpp",
    "content": "#include \"common.hpp\"\n#include \"mocks/include/MockShared.hpp\"\n#include \"RTC/Consts.hpp\"\n#include \"RTC/RTP/HeaderExtensionIds.hpp\"\n#include \"RTC/RTP/Packet.hpp\"\n#include \"RTC/TransportCongestionControlServer.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <deque>\n#include <vector>\n\nSCENARIO(\"TransportCongestionControlServer\", \"[rtp]\")\n{\n\tstruct TestTransportCongestionControlServerInput\n\t{\n\t\tuint16_t wideSeqNumber;\n\t\tuint64_t nowMs;\n\t};\n\n\tstruct TestTransportCongestionControlServerResult\n\t{\n\t\tuint16_t wideSeqNumber;\n\t\tbool received;\n\t\tuint64_t timestamp;\n\t};\n\n\tusing TestResults = std::deque<std::vector<TestTransportCongestionControlServerResult>>;\n\n\tclass TestTransportCongestionControlServerListener\n\t  : public RTC::TransportCongestionControlServer::Listener\n\t{\n\tpublic:\n\t\tvirtual void OnTransportCongestionControlServerSendRtcpPacket(\n\t\t  RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) override\n\t\t{\n\t\t\tauto* tccPacket = dynamic_cast<RTC::RTCP::FeedbackRtpTransportPacket*>(packet);\n\n\t\t\tif (!tccPacket)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tauto packetResults = tccPacket->GetPacketResults();\n\n\t\t\tREQUIRE(!this->results.empty());\n\n\t\t\tauto testResults = this->results.front();\n\t\t\tthis->results.pop_front();\n\n\t\t\tREQUIRE(testResults.size() == packetResults.size());\n\n\t\t\tauto packetResultIt = packetResults.begin();\n\t\t\tauto testResultIt   = testResults.begin();\n\n\t\t\tfor (; packetResultIt != packetResults.end() && testResultIt != testResults.end();\n\t\t\t     ++packetResultIt, ++testResultIt)\n\t\t\t{\n\t\t\t\tREQUIRE(packetResultIt->sequenceNumber == testResultIt->wideSeqNumber);\n\t\t\t\tREQUIRE(packetResultIt->received == testResultIt->received);\n\n\t\t\t\tif (packetResultIt->received)\n\t\t\t\t{\n\t\t\t\t\tREQUIRE(packetResultIt->receivedAtMs == static_cast<int64_t>(testResultIt->timestamp));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\tpublic:\n\t\tvoid SetResults(TestResults& results)\n\t\t{\n\t\t\tthis->results = results;\n\t\t}\n\n\t\tvoid Check()\n\t\t{\n\t\t\tREQUIRE(this->results.empty());\n\t\t}\n\n\tprivate:\n\t\tTestResults results;\n\t};\n\n\tmocks::MockShared shared(/*getTimeMs*/\n\t                         []()\n\t                         {\n\t\t                         return 1000;\n\t                         });\n\n\t// clang-format off\n\talignas(4) uint8_t buffer[] =\n\t{\n\t\t0x90, 0x01, 0x00, 0x01,\n\t\t0x00, 0x00, 0x00, 0x04,\n\t\t0x00, 0x00, 0x00, 0x05,\n\t\t0xbe, 0xde, 0x00, 0x01,\t// Header Extensions\n\t\t0x51, 0x60, 0xee, 0x00  // TCC Feedback\n\t};\n\t// clang-format on\n\n\tauto validate =\n\t  [&buffer,\n\t   &shared](std::vector<TestTransportCongestionControlServerInput>& inputs, TestResults& results)\n\t{\n\t\tTestTransportCongestionControlServerListener listener;\n\t\tauto tccServer = RTC::TransportCongestionControlServer(\n\t\t  std::addressof(listener),\n\t\t  std::addressof(shared),\n\t\t  RTC::BweType::TRANSPORT_CC,\n\t\t  RTC::Consts::MtuSize);\n\n\t\ttccServer.SetMaxIncomingBitrate(150000);\n\t\ttccServer.TransportConnected();\n\n\t\tstd::unique_ptr<RTC::RTP::Packet> packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) };\n\n\t\tRTC::RTP::HeaderExtensionIds headerExtensionIds{};\n\n\t\theaderExtensionIds.transportWideCc01 = 5;\n\n\t\tpacket->AssignExtensionIds(headerExtensionIds);\n\t\tpacket->SetSequenceNumber(1);\n\n\t\t// Save results.\n\t\tlistener.SetResults(results);\n\n\t\tuint64_t startTs = inputs[0].nowMs;\n\t\tuint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms.\n\n\t\tfor (auto input : inputs)\n\t\t{\n\t\t\t// Periodic sending TCC packets.\n\t\t\tuint64_t diffTs = input.nowMs - startTs;\n\n\t\t\tif (diffTs >= TransportCcFeedbackSendInterval)\n\t\t\t{\n\t\t\t\ttccServer.FillAndSendTransportCcFeedback();\n\t\t\t\tstartTs = input.nowMs;\n\t\t\t}\n\n\t\t\tpacket->UpdateTransportWideCc01(input.wideSeqNumber);\n\t\t\ttccServer.IncomingPacket(input.nowMs, packet.get());\n\t\t}\n\n\t\ttccServer.FillAndSendTransportCcFeedback();\n\t\tlistener.Check();\n\t};\n\n\tSECTION(\"normal time and sequence\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestTransportCongestionControlServerInput> inputs\n\t\t{\n\t\t\t{ 1u, 1000u },\n\t\t\t{ 2u, 1050u },\n\t\t\t{ 3u, 1100u },\n\t\t\t{ 4u, 1150u },\n\t\t\t{ 5u, 1200u },\n\t\t};\n\n\t\tTestResults results\n\t\t{\n\t\t\t{\n\t\t\t\t{ 1u, true, 1000u },\n\t\t\t\t{ 2u, true, 1050u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 3u, true, 1100u },\n\t\t\t\t{ 4u, true, 1150u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 5u, true, 1200u },\n\t\t\t},\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(inputs, results);\n\t}\n\n\tSECTION(\"lost packets\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestTransportCongestionControlServerInput> inputs\n\t\t{\n\t\t\t{  1u, 1000u },\n\t\t\t{  3u, 1050u },\n\t\t\t{  5u, 1100u },\n\t\t\t{  6u, 1150u },\n\t\t};\n\n\t\tTestResults results\n\t\t{\n\t\t\t{\n\t\t\t\t{ 1u,  true, 1000u },\n\t\t\t\t{ 2u, false,    0u },\n\t\t\t\t{ 3u,  true, 1050u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 4u, false,    0u },\n\t\t\t\t{ 5u,  true, 1100u },\n\t\t\t\t{ 6u,  true, 1150u },\n\t\t\t},\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(inputs, results);\n\t}\n\n\tSECTION(\"duplicate packets\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestTransportCongestionControlServerInput> inputs\n\t\t{\n\t\t\t{  1u, 1000u },\n\t\t\t{  1u, 1050u },\n\t\t\t{  2u, 1100u },\n\t\t\t{  3u, 1150u },\n\t\t\t{  3u, 1200u },\n\t\t\t{  4u, 1250u },\n\t\t};\n\n\t\tTestResults results\n\t\t{\n\t\t\t{\n\t\t\t\t{ 1u,  true, 1000u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 2u,  true, 1100u },\n\t\t\t\t{ 3u,  true, 1150u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 4u,  true, 1250u },\n\t\t\t},\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(inputs, results);\n\t}\n\n\tSECTION(\"packets arrive out of order\")\n\t{\n\t\t// clang-format off\n\t\tstd::vector<TestTransportCongestionControlServerInput> inputs\n\t\t{\n\t\t\t{ 1u, 1000u },\n\t\t\t{ 2u, 1050u },\n\t\t\t{ 4u, 1100u },\n\t\t\t{ 5u, 1150u },\n\t\t\t{ 3u, 1200u }, // Out of order\n\t\t\t{ 6u, 1250u },\n\t\t};\n\n\t\tTestResults results\n\t\t{\n\t\t\t{\n\t\t\t\t{ 1u, true, 1000u },\n\t\t\t\t{ 2u, true, 1050u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 3u, false,    0u },\n\t\t\t\t{ 4u,  true, 1100u },\n\t\t\t\t{ 5u,  true, 1150u },\n\t\t\t},\n\t\t\t{\n\t\t\t\t{ 3u, true, 1200u },\n\t\t\t\t{ 4u, true, 1100u },\n\t\t\t\t{ 5u, true, 1150u },\n\t\t\t\t{ 6u, true, 1250u },\n\t\t\t},\n\t\t};\n\t\t// clang-format on\n\n\t\tvalidate(inputs, results);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestTransportTuple.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/Transport.hpp\"\n#include \"RTC/TransportTuple.hpp\"\n#include \"RTC/UdpSocket.hpp\"\n#include <uv.h>\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"TransportTuple\", \"[transport-tuple]\")\n{\n\tclass UdpSocketListener : public RTC::UdpSocket::Listener\n\t{\n\tpublic:\n\t\tvoid OnUdpSocketPacketReceived(\n\t\t  RTC::UdpSocket* /*socket*/,\n\t\t  const uint8_t* /*data*/,\n\t\t  size_t /*len*/,\n\t\t  size_t /*bufferLen*/,\n\t\t  const struct sockaddr* /*remoteAddr*/) override\n\t\t{\n\t\t}\n\t};\n\n\tauto makeUdpSocket = [](const std::string& ip, uint16_t minPort, uint16_t maxPort)\n\t{\n\t\tUdpSocketListener listener;\n\t\tauto flags = RTC::Transport::SocketFlags{ .ipv6Only = false, .udpReusePort = false };\n\t\tuint64_t portRangeHash{ 0u };\n\t\tauto* udpSocket = new RTC::UdpSocket(\n\t\t  std::addressof(listener), const_cast<std::string&>(ip), minPort, maxPort, flags, portRangeHash);\n\n\t\treturn std::unique_ptr<RTC::UdpSocket>(udpSocket);\n\t};\n\n\tauto makeUdpSockAddr = [](int family, const std::string& ip, uint16_t port)\n\t{\n\t\tif (family == AF_INET)\n\t\t{\n\t\t\tauto addr = std::make_unique<sockaddr_in>();\n\n\t\t\taddr->sin_family = AF_INET;\n\t\t\taddr->sin_port   = htons(port);\n\n\t\t\tif (uv_inet_pton(AF_INET, ip.c_str(), std::addressof(addr->sin_addr)) != 0)\n\t\t\t{\n\t\t\t\tthrow std::runtime_error(\"invalid IPv4 address\");\n\t\t\t}\n\n\t\t\treturn std::unique_ptr<sockaddr>(reinterpret_cast<sockaddr*>(addr.release()));\n\t\t}\n\t\telse if (family == AF_INET6)\n\t\t{\n\t\t\tauto addr6         = std::make_unique<sockaddr_in6>();\n\t\t\taddr6->sin6_family = AF_INET6;\n\t\t\taddr6->sin6_port   = htons(port);\n\n\t\t\tif (uv_inet_pton(AF_INET6, ip.c_str(), std::addressof(addr6->sin6_addr)) != 0)\n\t\t\t{\n\t\t\t\tthrow std::runtime_error(\"invalid IPv6 address\");\n\t\t\t}\n\n\t\t\treturn std::unique_ptr<sockaddr>(reinterpret_cast<sockaddr*>(addr6.release()));\n\t\t}\n\t\telse\n\t\t{\n\t\t\tthrow std::runtime_error(\"invalid network family\");\n\t\t}\n\t};\n\n\tSECTION(\"2 tuples with same local and remote IP:port have the same hash\")\n\t{\n\t\tauto udpSocket      = makeUdpSocket(\"0.0.0.0\", 10000, 50000);\n\t\tauto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 1234);\n\t\tauto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 1234);\n\n\t\tRTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get());\n\t\tRTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get());\n\n\t\tREQUIRE(udpTuple1.hash == udpTuple2.hash);\n\t}\n\n\tSECTION(\n\t  \"2 tuples with same local IP:port, same remote IP and different remote port have different hashes\")\n\t{\n\t\tauto udpSocket      = makeUdpSocket(\"0.0.0.0\", 10000, 50000);\n\t\tauto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 10001);\n\t\tauto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 10002);\n\n\t\tRTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get());\n\t\tRTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get());\n\n\t\tREQUIRE(udpTuple1.hash != udpTuple2.hash);\n\n\t\tfor (uint16_t remotePort{ 1 }; remotePort < 65535; ++remotePort)\n\t\t{\n\t\t\t// SKip if same port as the one in udpRemoteAddr1.\n\t\t\tif (remotePort == 10001)\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tauto udpRemoteAddr3 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", remotePort);\n\n\t\t\tRTC::TransportTuple udpTuple3(udpSocket.get(), udpRemoteAddr3.get());\n\n\t\t\tREQUIRE(udpTuple1.hash != udpTuple3.hash);\n\t\t}\n\t}\n\n\tSECTION(\n\t  \"2 tuples with same local IP:port, different remote IP and same remote port have different hashes\")\n\t{\n\t\tauto udpSocket      = makeUdpSocket(\"0.0.0.0\", 10000, 50000);\n\t\tauto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 10001);\n\t\tauto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, \"1.2.3.5\", 10001);\n\n\t\tRTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get());\n\t\tRTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get());\n\n\t\tREQUIRE(udpTuple1.hash != udpTuple2.hash);\n\t}\n\n\tSECTION(\n\t  \"2 tuples with same remote IP:port, same local IP and different local port have different hashes\")\n\t{\n\t\tauto udpSocket1     = makeUdpSocket(\"0.0.0.0\", 10000, 20000);\n\t\tauto udpSocket2     = makeUdpSocket(\"0.0.0.0\", 30000, 40000);\n\t\tauto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, \"5.4.3.2\", 22222);\n\t\tauto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, \"5.4.3.2\", 22222);\n\n\t\tRTC::TransportTuple udpTuple1(udpSocket1.get(), udpRemoteAddr1.get());\n\t\tRTC::TransportTuple udpTuple2(udpSocket2.get(), udpRemoteAddr2.get());\n\n\t\tREQUIRE(udpTuple1.hash != udpTuple2.hash);\n\t}\n\n\tSECTION(\n\t  \"2 tuples with same local IP:port, same remote IP and different remote port have different hashes\")\n\t{\n\t\tauto udpSocket      = makeUdpSocket(\"0.0.0.0\", 10000, 50000);\n\t\tauto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 40001);\n\t\tauto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, \"1.2.3.4\", 40002);\n\n\t\tRTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get());\n\t\tRTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get());\n\n\t\tREQUIRE(udpTuple1.hash != udpTuple2.hash);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/RTC/TestTrendCalculator.cpp",
    "content": "#include \"common.hpp\"\n#include \"RTC/TrendCalculator.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"TrendCalculator\")\n{\n\tSECTION(\"trend values after same elapsed time match\")\n\t{\n\t\tRTC::TrendCalculator trendA;\n\t\tRTC::TrendCalculator trendB;\n\n\t\ttrendA.Update(1000u, 0u);\n\t\ttrendB.Update(1000u, 0u);\n\n\t\tREQUIRE(trendA.GetValue() == 1000u);\n\t\tREQUIRE(trendB.GetValue() == 1000u);\n\n\t\ttrendA.Update(200u, 500u);\n\t\ttrendA.Update(200u, 1000u);\n\t\ttrendB.Update(200u, 1000u);\n\n\t\tREQUIRE(trendA.GetValue() == trendB.GetValue());\n\n\t\ttrendA.Update(200u, 2000u);\n\t\ttrendA.Update(200u, 4000u);\n\t\ttrendB.Update(200u, 4000u);\n\n\t\tREQUIRE(trendA.GetValue() == trendB.GetValue());\n\n\t\ttrendA.Update(2000u, 5000u);\n\t\ttrendB.Update(2000u, 5000u);\n\n\t\tREQUIRE(trendA.GetValue() == 2000u);\n\t\tREQUIRE(trendB.GetValue() == 2000u);\n\n\t\ttrendA.ForceUpdate(0u, 5500u);\n\t\ttrendB.ForceUpdate(100u, 5000u);\n\n\t\tREQUIRE(trendA.GetValue() == 0u);\n\t\tREQUIRE(trendB.GetValue() == 100u);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestBits.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"Utils::Bits\", \"[utils][bits]\")\n{\n\tSECTION(\"CountSetBits()\")\n\t{\n\t\tuint16_t mask;\n\n\t\tmask = 0b0000000000000000;\n\t\tREQUIRE(Utils::Bits::CountSetBits(mask) == 0);\n\n\t\tmask = 0b0000000000000001;\n\t\tREQUIRE(Utils::Bits::CountSetBits(mask) == 1);\n\n\t\tmask = 0b1000000000000001;\n\t\tREQUIRE(Utils::Bits::CountSetBits(mask) == 2);\n\n\t\tmask = 0b1111111111111111;\n\t\tREQUIRE(Utils::Bits::CountSetBits(mask) == 16);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestByte.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"Utils::Byte\", \"[utils][byte]\")\n{\n\t// NOTE: The setup and teardown are implicit in how Catch2 works, meaning that\n\t// this buffer is initialized before each SECTION below.\n\t// Docs: https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#test-cases-and-sections\n\n\t// clang-format off\n\tuint8_t buffer[] =\n\t{\n\t\t0b00000000, 0b00000001, 0b00000010, 0b00000011,\n\t\t0b10000000, 0b01000000, 0b00100000, 0b00010000,\n\t\t0b01111111, 0b11111111, 0b11111111, 0b00000000,\n\t\t0b11111111, 0b11111111, 0b11111111, 0b00000000,\n\t\t0b10000000, 0b00000000, 0b00000000, 0b00000000\n\t};\n\t// clang-format on\n\n\tSECTION(\"Get3Bytes()\")\n\t{\n\t\t// Bytes 4,5 and 6 in the array are number 8405024.\n\t\tREQUIRE(Utils::Byte::Get3Bytes(buffer, 4) == 8405024);\n\t}\n\n\tSECTION(\"Set3Bytes()\")\n\t{\n\t\tUtils::Byte::Set3Bytes(buffer, 4, 5666777);\n\t\tREQUIRE(Utils::Byte::Get3Bytes(buffer, 4) == 5666777);\n\t}\n\n\tSECTION(\"Get3BytesSigned()\")\n\t{\n\t\t// Bytes 8, 9 and 10 in the array are number 8388607 since first bit is 0 and\n\t\t// all other bits are 1, so it must be maximum positive 24 bits signed integer,\n\t\t// which is Math.pow(2, 23) - 1 = 8388607.\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 8) == 8388607);\n\n\t\t// Bytes 12, 13 and 14 in the array are number -1.\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 12) == -1);\n\n\t\t// Bytes 16, 17 and 18 in the array are number -8388608 since first bit is 1\n\t\t// and all other bits are 0, so it must be minimum negative 24 bits signed\n\t\t// integer, which is -1 * Math.pow(2, 23) = -8388608.\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 16) == -8388608);\n\t}\n\n\tSECTION(\"Set3BytesSigned()\")\n\t{\n\t\tUtils::Byte::Set3BytesSigned(buffer, 0, 8388607);\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == 8388607);\n\n\t\tUtils::Byte::Set3BytesSigned(buffer, 0, -1);\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == -1);\n\n\t\tUtils::Byte::Set3BytesSigned(buffer, 0, -8388608);\n\t\tREQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == -8388608);\n\t}\n\n\tSECTION(\"IsPaddedTo4Bytes()\")\n\t{\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 4u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 252u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 255u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 4u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 252u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 65532u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 65535u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 252u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 65532u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4294967292u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4294967295u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 252u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 65532u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4294967292u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4294967295u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551608u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551612u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551615u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 252u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 65532u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967292u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967295u }) == false);\n\n// Check if size_t in current host is 64 bits. Otherwise the test would fail.\n#if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551608u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551612u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551615u }) == false);\n#endif\n\t}\n\n\tSECTION(\"IsPaddedTo8Bytes()\")\n\t{\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 4u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 252u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 255u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 4u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 252u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 65532u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 65535u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 252u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 65532u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967292u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967295u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 252u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 65532u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967292u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967295u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551608u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551612u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551615u }) == false);\n\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 0u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 1u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 2u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 3u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 5u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 8u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 9u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 252u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 255u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 256u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 65532u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 65535u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967288u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967292u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967295u }) == false);\n\n// Check if size_t in current host is 64 bits. Otherwise the test would fail.\n#if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551608u }) == true);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551612u }) == false);\n\t\tREQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551615u }) == false);\n#endif\n\t}\n\n\tSECTION(\"PadTo4Bytes()\")\n\t{\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 1u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 2u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 3u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 9u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 254u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 255u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 1u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 2u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 3u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 9u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 65535u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 1u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 2u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 3u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 9u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967292u }) == 4294967292u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967295u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 1u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 2u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 3u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 9u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967292u }) == 4294967292u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967295u }) == 4294967296u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551612u }) == 18446744073709551612u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551615u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 1u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 2u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 3u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 9u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4294967292u }) == 4294967292u);\n\n// Check if size_t in current host is 64 bits. Otherwise the test would fail.\n#if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551612u }) == 18446744073709551612u);\n\t\tREQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551615u }) == 0u);\n#endif\n\t}\n\n\tSECTION(\"PadDownTo4Bytes()\")\n\t{\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 1u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 2u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 3u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 5u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 9u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 15u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 254u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 255u }) == 252u);\n\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 1u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 2u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 3u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 5u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 9u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 15u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 254u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 255u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 65535u }) == 65532u);\n\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 1u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 2u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 3u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 5u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 9u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 15u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 254u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 255u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 65535u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967292u }) == 4294967292u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967295u }) == 4294967292u);\n\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 1u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 2u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 3u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 5u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 9u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 15u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 254u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 255u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 65535u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967292u }) == 4294967292u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967295u }) == 4294967292u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551612u }) == 18446744073709551612u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551615u }) == 18446744073709551612u);\n\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 1u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 2u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 3u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 5u }) == 4u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 9u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 15u }) == 12u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 252u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 254u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 255u }) == 252u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 65532u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 65535u }) == 65532u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4294967292u }) == 4294967292u);\n\n// Check if size_t in current host is 64 bits. Otherwise the test would fail.\n#if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551612u }) == 18446744073709551612u);\n\t\tREQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551615u }) == 18446744073709551612u);\n#endif\n\t}\n\n\tSECTION(\"PadTo8Bytes()\")\n\t{\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 1u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 2u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 3u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 4u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 6u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 7u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 9u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 16u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 17u }) == 24u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 252u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 254u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 255u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 1u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 2u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 3u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 4u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 6u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 7u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 9u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 16u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 17u }) == 24u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 252u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 65532u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 65535u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 1u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 2u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 3u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 6u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 7u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 9u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 16u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 252u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 65532u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967292u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967295u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 1u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 2u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 3u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 6u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 7u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 9u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 16u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 252u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 65532u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967288u }) == 4294967288u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967292u }) == 4294967296u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967295u }) == 4294967296u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551612u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551615u }) == 0u);\n\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 0u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 1u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 2u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 3u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 5u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 6u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 7u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 8u }) == 8u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 9u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 15u }) == 16u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 252u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 254u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 255u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 256u }) == 256u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 65532u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 65535u }) == 65536u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4294967288u }) == 4294967288u);\n\n// Check if size_t in current host is 64 bits. Otherwise the test would fail.\n#if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4294967292u }) == 4294967296u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551612u }) == 0u);\n\t\tREQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551615u }) == 0u);\n#endif\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestCrypto.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <limits> // std::numeric_limits()\n#include <set>\n\nSCENARIO(\"Utils::Crypto\", \"[utils][crypto]\")\n{\n\tSECTION(\"GetCRC32()\")\n\t{\n\t\tuint8_t dataEmpty[] = {};\n\t\tuint8_t dataZero[]  = { 0 };\n\t\t// clang-format off\n\t\tuint8_t dataRandom[] =\n\t\t{\n\t\t\t0xFF, 0x00, 0xAB, 0xCD, 0x12, 0x39, 0x54, 0xBB, 0xDD,\n\t\t\t0xEE, 0x01, 0x01, 0x01, 0x01, 0x88, 0x88, 0xAA\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(Utils::Crypto::GetCRC32(dataEmpty, sizeof(dataEmpty)) == 0U);\n\t\tREQUIRE(Utils::Crypto::GetCRC32(dataZero, sizeof(dataZero)) == 0xD202EF8D);\n\t\tREQUIRE(Utils::Crypto::GetCRC32(dataRandom, sizeof(dataRandom)) == 0xEEE31378);\n\t}\n\n\tSECTION(\"GetCRC32c()\")\n\t{\n\t\t// Tests copied from dcSCTP code in libwebrtc:\n\t\t// https://webrtc.googlesource.com/src//+/refs/heads/main/net/dcsctp/packet/crc32c_test.cc\n\n\t\tuint8_t dataEmpty[]     = {};\n\t\tuint8_t dataZero[]      = { 0 };\n\t\tuint8_t dataManyZeros[] = { 0, 0, 0, 0 };\n\t\tuint8_t dataShort[]     = { 1, 2, 3, 4 };\n\t\tuint8_t dataLong[]      = { 1, 2, 3, 4, 5, 6, 7, 8 };\n\t\t// clang-format off\n\t\tuint8_t data32Zeros[]   =\n\t\t{\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t\t\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n\t\t};\n\t\tuint8_t data32Ones[] =\n\t\t{\n\t\t\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t\t\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,\n\t\t\t0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF\n\t\t};\n\t\tuint8_t data32Incrementing[] =\n\t\t{\n\t\t\t0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,\n\t\t\t16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31\n\t\t};\n\t\tuint8_t data32Decrementing[] =\n\t\t{\n\t\t\t31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16,\n\t\t\t15, 14, 13, 12, 11, 10, 9,  8,  7,  6,  5,  4,  3,  2,  1,  0\n\t\t};\n\t\tuint8_t dataSCSICommandPDU[] = {\n\t\t\t0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00,\n\t\t\t0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n\t\t};\n\t\t// clang-format on\n\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataEmpty, sizeof(dataEmpty)) == 0);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataZero, sizeof(dataZero)) == 0x51537d52);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataManyZeros, sizeof(dataManyZeros)) == 0xC74B6748);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataShort, sizeof(dataShort)) == 0xF48C3029);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataLong, sizeof(dataLong)) == 0x811F8946);\n\t\t// https://tools.ietf.org/html/rfc3720#appendix-B.4\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(data32Zeros, sizeof(data32Zeros)) == 0xAA36918A);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(data32Ones, sizeof(data32Ones)) == 0x43ABA862);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(data32Incrementing, sizeof(data32Incrementing)) == 0x4E79DD46);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(data32Decrementing, sizeof(data32Decrementing)) == 0x5CDB3F11);\n\t\tREQUIRE(Utils::Crypto::GetCRC32c(dataSCSICommandPDU, sizeof(dataSCSICommandPDU)) == 0x563A96D9);\n\t}\n}\n\nSCENARIO(\"Utils::Crypto::GetRandomUInt()\", \"[utils][crypto]\")\n{\n\tstd::set<uint32_t> randomUint32Numbers;\n\tstd::set<uint32_t> randomUint64Numbers;\n\n\tfor (size_t i = 0; i < 200; ++i)\n\t{\n\t\tauto randomNumber =\n\t\t  Utils::Crypto::GetRandomUInt<uint32_t>(0, std::numeric_limits<uint32_t>::max());\n\n\t\tREQUIRE(randomUint32Numbers.find(randomNumber) == randomUint32Numbers.end());\n\n\t\trandomUint32Numbers.insert(randomNumber);\n\t}\n\n\tfor (size_t i = 0; i < 200; ++i)\n\t{\n\t\tauto randomNumber =\n\t\t  Utils::Crypto::GetRandomUInt<uint64_t>(0, std::numeric_limits<uint64_t>::max());\n\n\t\tREQUIRE(randomUint64Numbers.find(randomNumber) == randomUint64Numbers.end());\n\n\t\trandomUint64Numbers.insert(randomNumber);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestIP.cpp",
    "content": "#include \"common.hpp\"\n#include \"MediaSoupErrors.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memset()\n\nSCENARIO(\"Utils::IP\", \"[utils][ip]\")\n{\n\tSECTION(\"GetFamily()\")\n\t{\n\t\tstd::string ip;\n\n\t\tip = \"1.2.3.4\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET);\n\n\t\tip = \"127.0.0.1\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET);\n\n\t\tip = \"255.255.255.255\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET);\n\n\t\tip = \"1::1\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET6);\n\n\t\tip = \"a:b:c:D::0\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET6);\n\n\t\tip = \"0000:0000:0000:0000:0000:ffff:192.168.100.228\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_INET6);\n\n\t\tip = \"::0:\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"3::3:1:\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"chicken\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1.2.3.256\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1.2.3.1111\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1.2.3.01\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1::abcde\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1:::\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"1.2.3.4 \";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \" ::1\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\n\t\tip = \"0000:0000:0000:0000:0000:ffff:192.168.100.228.4567\";\n\t\tREQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC);\n\t}\n\n\tSECTION(\"NormalizeIp()\")\n\t{\n\t\tstd::string ip;\n\n\t\tip = \"1.2.3.4\";\n\t\tUtils::IP::NormalizeIp(ip);\n\t\tREQUIRE(ip == \"1.2.3.4\");\n\n\t\tip = \"255.255.255.255\";\n\t\tUtils::IP::NormalizeIp(ip);\n\t\tREQUIRE(ip == \"255.255.255.255\");\n\n\t\tip = \"aA::8\";\n\t\tUtils::IP::NormalizeIp(ip);\n\t\tREQUIRE(ip == \"aa::8\");\n\n\t\tip = \"aA::0:0008\";\n\t\tUtils::IP::NormalizeIp(ip);\n\t\tREQUIRE(ip == \"aa::8\");\n\n\t\tip = \"001.2.3.4\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"0255.255.255.255\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"1::2::3\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"::1 \";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"0.0.0.\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"::0:\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"3::3:1:\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\n\t\tip = \"\";\n\t\tREQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError);\n\t}\n\n\tSECTION(\"GetAddressInfo()\")\n\t{\n\t\tstruct sockaddr_in sin{};\n\n\t\tstd::memset(&sin, 0, sizeof(sin));\n\n\t\tsin.sin_family      = AF_INET;\n\t\tsin.sin_port        = htons(10251);\n\t\tsin.sin_addr.s_addr = inet_addr(\"82.99.219.114\");\n\n\t\tconst auto* addr = reinterpret_cast<const struct sockaddr*>(&sin);\n\t\tint family;\n\t\tstd::string ip;\n\t\tuint16_t port;\n\n\t\tUtils::IP::GetAddressInfo(addr, family, ip, port);\n\n\t\tREQUIRE(family == AF_INET);\n\t\tREQUIRE(ip == \"82.99.219.114\");\n\t\tREQUIRE(port == 10251);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestNumber.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <limits> // std::numeric_limits\n\nSCENARIO(\"Utils::Number\", \"[utils][number]\")\n{\n\tSECTION(\"IsEqualThan()\")\n\t{\n\t\t// 0 is not equal than 16.\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint16_t>(0, 16) == false);\n\n\t\t// Using N=4 bits, 0 is equal than 16.\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint8_t, 4>(0, 16) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint16_t, 4>(0, 16) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint32_t, 4>(0, 16) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint64_t, 4>(0, 16) == true);\n\n\t\t// Using N=7 bits, 0 is equal than 128.\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint8_t, 7>(0, 128) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint16_t, 7>(0, 128) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint32_t, 7>(0, 128) == true);\n\t\tREQUIRE(Utils::Number::IsEqualThan<uint64_t, 7>(0, 128) == true);\n\t}\n\n\tSECTION(\"IsHigherThan()\")\n\t{\n\t\t// 10 is higher than std::numeric_limits<uint8_t>::max().\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint8_t>(10, std::numeric_limits<uint8_t>::max()) == true);\n\n\t\t// 0 is greater than std::numeric_limits<uint64_t>::max().\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint64_t>(0, std::numeric_limits<uint64_t>::max()) == true);\n\n\t\t// std::numeric_limits<uint64_t>::max() / 2) - 1 is higher than\n\t\t// std::numeric_limits<uint64_t>::max().\n\t\tREQUIRE(\n\t\t  Utils::Number::IsHigherThan<uint64_t>(\n\t\t    (std::numeric_limits<uint64_t>::max() / 2) - 1, std::numeric_limits<uint64_t>::max()) == true);\n\n\t\t// std::numeric_limits<uint64_t>::max() is higher than\n\t\t// (std::numeric_limits<uint64_t>::max() / 2) + 1.\n\t\tREQUIRE(\n\t\t  Utils::Number::IsHigherThan<uint64_t>(\n\t\t    std::numeric_limits<uint64_t>::max(), (std::numeric_limits<uint64_t>::max() / 2) + 1) == true);\n\n\t\t// Using N=4 bits, 0 is higher than 14.\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint8_t, 4>(0, 14) == true);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint16_t, 4>(0, 14) == true);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint32_t, 4>(0, 14) == true);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint64_t, 4>(0, 14) == true);\n\n\t\t// Using N=6 bits, 0 is not higher than 64.\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint8_t, 6>(0, 64) == false);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint16_t, 6>(0, 64) == false);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint32_t, 6>(0, 64) == false);\n\t\tREQUIRE(Utils::Number::IsHigherThan<uint64_t, 6>(0, 64) == false);\n\t}\n\n\tSECTION(\"IsLowerThan()\")\n\t{\n\t\t// 1 is lower than 2.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint8_t>(1, 2) == true);\n\n\t\t// std::numeric_limits<uint8_t>::max() is lower than 0.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint8_t>(std::numeric_limits<uint8_t>::max(), 0) == true);\n\n\t\t// 1000000 is lower than 2000000.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint64_t>(1000000, 2000000) == true);\n\n\t\t// std::numeric_limits<uint64_t>::max() is lower than 0.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint64_t>(std::numeric_limits<uint64_t>::max(), 0) == true);\n\n\t\t// (std::numeric_limits<uint64_t>::max() / 2) + 1 is lower than\n\t\t// std::numeric_limits<uint64_t>::max().\n\t\tREQUIRE(\n\t\t  Utils::Number::IsLowerThan<uint64_t>(\n\t\t    (std::numeric_limits<uint64_t>::max() / 2) + 1, std::numeric_limits<uint64_t>::max()) == true);\n\n\t\t// std::numeric_limits<uint64_t>::max() is lower than\n\t\t// (std::numeric_limits<uint64_t>::max() / 2) - 1.\n\t\tREQUIRE(\n\t\t  Utils::Number::IsLowerThan<uint64_t>(\n\t\t    std::numeric_limits<uint64_t>::max(), (std::numeric_limits<uint64_t>::max() / 2) - 1) == true);\n\n\t\t// Using N=3 bits, 7 is lower than 2.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint8_t, 3>(15, 2) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint16_t, 3>(15, 2) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint32_t, 3>(15, 2) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint64_t, 3>(15, 2) == true);\n\n\t\t// Using N=2 bits, 3 is lower than 1.\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint8_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint16_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint32_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerThan<uint64_t, 2>(3, 1) == true);\n\t}\n\n\tSECTION(\"IsHigherOrEqualThan()\")\n\t{\n\t\t// 0 is greater or equal than std::numeric_limits<uint64_t>::max().\n\t\tREQUIRE(\n\t\t  Utils::Number::IsHigherOrEqualThan<uint64_t>(0, std::numeric_limits<uint64_t>::max()) == true);\n\n\t\t// Using N=5 bits, 0 is higher or equal than 32.\n\t\tREQUIRE(Utils::Number::IsHigherOrEqualThan<uint8_t, 5>(0, 32) == true);\n\t\tREQUIRE(Utils::Number::IsHigherOrEqualThan<uint16_t, 5>(0, 32) == true);\n\t\tREQUIRE(Utils::Number::IsHigherOrEqualThan<uint32_t, 5>(0, 32) == true);\n\t\tREQUIRE(Utils::Number::IsHigherOrEqualThan<uint64_t, 5>(0, 32) == true);\n\t}\n\n\tSECTION(\"IsLowerOrEqualThan()\")\n\t{\n\t\t// std::numeric_limits<uint64_t>::max() is lower or equal than 0.\n\t\tREQUIRE(\n\t\t  Utils::Number::IsLowerOrEqualThan<uint64_t>(std::numeric_limits<uint64_t>::max(), 0) == true);\n\n\t\t// Using N=2 bits, 0 is lower or equal than 4.\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint8_t, 2>(0, 4) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint16_t, 2>(0, 4) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint32_t, 2>(0, 4) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint64_t, 2>(0, 4) == true);\n\n\t\t// Using N=2 bits, 3 is lower or equal than 1.\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint8_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint16_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint32_t, 2>(3, 1) == true);\n\t\tREQUIRE(Utils::Number::IsLowerOrEqualThan<uint64_t, 2>(3, 1) == true);\n\t}\n\n\tSECTION(\"ForwardDiff()\")\n\t{\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff(4711u, 4711u)) == 0);\n\n\t\tuint8_t x{ 0 };\n\t\tuint8_t y{ 255 };\n\n\t\tfor (uint16_t i{ 0 }; i < 256; ++i)\n\t\t{\n\t\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff(x, y)) == 255);\n\n\t\t\t++x;\n\t\t\t++y;\n\t\t}\n\n\t\tuint32_t yi{ 255 };\n\n\t\tfor (uint16_t i{ 0 }; i < 512; ++i)\n\t\t{\n\t\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t>(x, yi)) == 255);\n\n\t\t\t++x;\n\t\t\t++yi;\n\t\t}\n\t}\n\n\tSECTION(\"ForwardDiff() with divisor\")\n\t{\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t, 123>(0, 122)) == 122);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t, 123>(122, 122)) == 0);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t, 123>(1, 0)) == 122);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t, 123>(0, 0)) == 0);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ForwardDiff<uint8_t, 123>(122, 0)) == 1);\n\t}\n\n\tSECTION(\"ReverseDiff()\")\n\t{\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff(4711u, 4711u)) == 0);\n\n\t\tuint8_t x{ 0 };\n\t\tuint8_t y{ 255 };\n\n\t\tfor (uint16_t i{ 0 }; i < 256; ++i)\n\t\t{\n\t\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff(x, y)) == 1);\n\n\t\t\t++x;\n\t\t\t++y;\n\t\t}\n\n\t\tuint32_t yi{ 255 };\n\n\t\tfor (uint16_t i{ 0 }; i < 512; ++i)\n\t\t{\n\t\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t>(x, yi)) == 1);\n\n\t\t\t++x;\n\t\t\t++yi;\n\t\t}\n\t}\n\n\tSECTION(\"ReverseDiff() with divisor\")\n\t{\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t, 123>(0, 122)) == 1);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t, 123>(122, 122)) == 0);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t, 123>(1, 0)) == 1);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t, 123>(0, 0)) == 0);\n\t\tREQUIRE(static_cast<uint64_t>(Utils::Number::ReverseDiff<uint8_t, 123>(122, 0)) == 122);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestString.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n#include <cstring> // std::memcmp()\n\nSCENARIO(\"Utils::String\", \"[utils][string]\")\n{\n\tSECTION(\"ToLowerCase()\")\n\t{\n\t\tstd::string str;\n\n\t\tstr = \"Foo\";\n\t\tUtils::String::ToLowerCase(str);\n\t\tREQUIRE(str == \"foo\");\n\n\t\tstr = \"Foo!œ\";\n\t\tUtils::String::ToLowerCase(str);\n\t\tREQUIRE(str == \"foo!œ\");\n\t}\n\n\tSECTION(\"Base64Encode() and Base64Decode()\")\n\t{\n\t\tstd::string data;\n\t\tstd::string encoded;\n\t\tsize_t outLen;\n\t\tuint8_t* decodedPtr;\n\t\tstd::string decoded;\n\n\t\t// NOTE: This is dangerous because we are using a string as binary.\n\t\tdata       = \"abcd\";\n\t\tencoded    = Utils::String::Base64Encode(data);\n\t\tdecodedPtr = Utils::String::Base64Decode(encoded, outLen);\n\t\tdecoded    = std::string(reinterpret_cast<char*>(decodedPtr), outLen);\n\t\tREQUIRE(encoded == \"YWJjZA==\");\n\t\tREQUIRE(decoded == data);\n\n\t\t// NOTE: This is dangerous because we are using a string as binary.\n\t\tdata       = \"Iñaki\";\n\t\tencoded    = Utils::String::Base64Encode(data);\n\t\tdecodedPtr = Utils::String::Base64Decode(encoded, outLen);\n\t\tdecoded    = std::string(reinterpret_cast<char*>(decodedPtr), outLen);\n\t\tREQUIRE(encoded == \"ScOxYWtp\");\n\t\tREQUIRE(decoded == data);\n\t\tREQUIRE(encoded == Utils::String::Base64Encode(decoded));\n\n\t\t// NOTE: This is dangerous because we are using a string as binary.\n\t\tdata =\n\t\t  \"kjsh 23 å∫∂ is89 ∫¶ §∂¶ i823y kjahsd 234u asd kasjhdii7682342 asdkjhaskjsahd   k jashd kajsdhaksjdh skadhkjhkjh       askdjhasdkjahs uyqiwey aså∫∂¢∞¬∫∂ ashksajdh kjasdhkajshda s kjahsdkjas 987897as897 97898623 9s kjsgå∫∂ 432å∫ƒ∂ å∫#¢ ouyqwiuyais kajsdhiuye  ajshkkSAH SDFYÑÑÑ å∫∂Ω 87253847b asdbuiasdi as kasuœæ€\\n321\";\n\t\tencoded    = Utils::String::Base64Encode(data);\n\t\tdecodedPtr = Utils::String::Base64Decode(encoded, outLen);\n\t\tdecoded    = std::string(reinterpret_cast<char*>(decodedPtr), outLen);\n\t\tREQUIRE(\n\t\t  encoded ==\n\t\t  \"a2pzaCAyMyDDpeKIq+KIgiBpczg5IOKIq8K2IMKn4oiCwrYgaTgyM3kga2phaHNkIDIzNHUgYXNkIGthc2poZGlpNzY4MjM0MiBhc2Rramhhc2tqc2FoZCAgIGsgamFzaGQga2Fqc2RoYWtzamRoIHNrYWRoa2poa2poICAgICAgIGFza2RqaGFzZGtqYWhzIHV5cWl3ZXkgYXPDpeKIq+KIgsKi4oiewqziiKviiIIgYXNoa3NhamRoIGtqYXNkaGthanNoZGEgcyBramFoc2RramFzIDk4Nzg5N2FzODk3IDk3ODk4NjIzIDlzIGtqc2fDpeKIq+KIgiA0MzLDpeKIq8aS4oiCIMOl4oirI8KiIG91eXF3aXV5YWlzIGthanNkaGl1eWUgIGFqc2hra1NBSCBTREZZw5HDkcORIMOl4oir4oiCzqkgODcyNTM4NDdiIGFzZGJ1aWFzZGkgYXMga2FzdcWTw6bigqwKMzIx\");\n\t\tREQUIRE(decoded == data);\n\t\tREQUIRE(encoded == Utils::String::Base64Encode(decoded));\n\n\t\tencoded    = \"1WfmbWJXSlhTbGhUYkdoVVlrZG9WVmxyWkc5Vw==\";\n\t\tdecodedPtr = Utils::String::Base64Decode(encoded, outLen);\n\t\tREQUIRE(outLen == 28);\n\t\tencoded = Utils::String::Base64Encode(decodedPtr, outLen);\n\t\tREQUIRE(encoded == \"1WfmbWJXSlhTbGhUYkdoVVlrZG9WVmxyWkc5Vw==\");\n\n\t\t// clang-format off\n\t\tuint8_t rtpPacket[] =\n\t\t{\n\t\t\t0xBE, 0xDE, 0, 3, // Header Extension\n\t\t\t0b00010000, 0xFF, 0b00100001, 0xFF,\n\t\t\t0xFF, 0, 0, 0b00110011,\n\t\t\t0xFF, 0xFF, 0xFF, 0xFF\n\t\t};\n\t\t// clang-format on\n\n\t\tencoded    = Utils::String::Base64Encode(rtpPacket, sizeof(rtpPacket));\n\t\tdecodedPtr = Utils::String::Base64Decode(encoded, outLen);\n\t\tREQUIRE(outLen == sizeof(rtpPacket));\n\t\tREQUIRE(std::memcmp(decodedPtr, rtpPacket, outLen) == 0);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestTime.cpp",
    "content": "#include \"common.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"Utils::Time\", \"[utils][time]\")\n{\n\tSECTION(\"Ntp2TimeMs()\")\n\t{\n\t\tconst auto nowMs  = DepLibUV::GetTimeMs();\n\t\tconst auto ntp    = Utils::Time::TimeMs2Ntp(nowMs);\n\t\tconst auto nowMs2 = Utils::Time::Ntp2TimeMs(ntp);\n\t\tconst auto ntp2   = Utils::Time::TimeMs2Ntp(nowMs2);\n\n\t\tREQUIRE(nowMs2 == nowMs);\n\t\tREQUIRE(ntp2.seconds == ntp.seconds);\n\t\tREQUIRE(ntp2.fractions == ntp.fractions);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/Utils/TestUnwrappedSequenceNumber.cpp",
    "content": "#include \"common.hpp\"\n#include \"Utils/UnwrappedSequenceNumber.hpp\"\n#include <catch2/catch_test_macros.hpp>\n\nSCENARIO(\"SCTP UnwrappedSequenceNumber\", \"[sctp]\")\n{\n\tusing TestSequence = Utils::UnwrappedSequenceNumber<uint16_t>;\n\n\tSECTION(\"simple unwrapping\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tTestSequence s0 = unwrapper.Unwrap(0);\n\t\tTestSequence s1 = unwrapper.Unwrap(1);\n\t\tTestSequence s2 = unwrapper.Unwrap(2);\n\t\tTestSequence s3 = unwrapper.Unwrap(3);\n\n\t\tREQUIRE(s0 < s1);\n\t\tREQUIRE(s0 < s2);\n\t\tREQUIRE(s0 < s3);\n\t\tREQUIRE(s1 < s2);\n\t\tREQUIRE(s1 < s3);\n\t\tREQUIRE(s2 < s3);\n\n\t\tREQUIRE(TestSequence::Difference(s1, s0) == 1);\n\t\tREQUIRE(TestSequence::Difference(s2, s0) == 2);\n\t\tREQUIRE(TestSequence::Difference(s3, s0) == 3);\n\n\t\tREQUIRE(s1 > s0);\n\t\tREQUIRE(s2 > s0);\n\t\tREQUIRE(s3 > s0);\n\t\tREQUIRE(s2 > s1);\n\t\tREQUIRE(s3 > s1);\n\t\tREQUIRE(s3 > s2);\n\n\t\ts0.Increment();\n\t\tREQUIRE(s0 == s1);\n\n\t\ts1.Increment();\n\t\tREQUIRE(s1 == s2);\n\n\t\ts2.Increment();\n\t\tREQUIRE(s2 == s3);\n\n\t\tREQUIRE(TestSequence::AddTo(s0, 2) == s3);\n\t}\n\n\tSECTION(\"mid value unwrapping\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tTestSequence s0 = unwrapper.Unwrap(0x7FFE);\n\t\tTestSequence s1 = unwrapper.Unwrap(0x7FFF);\n\t\tTestSequence s2 = unwrapper.Unwrap(0x8000);\n\t\tTestSequence s3 = unwrapper.Unwrap(0x8001);\n\n\t\tREQUIRE(s0 < s1);\n\t\tREQUIRE(s0 < s2);\n\t\tREQUIRE(s0 < s3);\n\t\tREQUIRE(s1 < s2);\n\t\tREQUIRE(s1 < s3);\n\t\tREQUIRE(s2 < s3);\n\n\t\tREQUIRE(TestSequence::Difference(s1, s0) == 1);\n\t\tREQUIRE(TestSequence::Difference(s2, s0) == 2);\n\t\tREQUIRE(TestSequence::Difference(s3, s0) == 3);\n\n\t\tREQUIRE(s1 > s0);\n\t\tREQUIRE(s2 > s0);\n\t\tREQUIRE(s3 > s0);\n\t\tREQUIRE(s2 > s1);\n\t\tREQUIRE(s3 > s1);\n\t\tREQUIRE(s3 > s2);\n\n\t\ts0.Increment();\n\t\tREQUIRE(s0 == s1);\n\n\t\ts1.Increment();\n\t\tREQUIRE(s1 == s2);\n\n\t\ts2.Increment();\n\t\tREQUIRE(s2 == s3);\n\n\t\tREQUIRE(TestSequence::AddTo(s0, 2) == s3);\n\t}\n\n\tSECTION(\"wrapped unwrapping\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tTestSequence s0 = unwrapper.Unwrap(0xFFFE);\n\t\tTestSequence s1 = unwrapper.Unwrap(0xFFFF);\n\t\tTestSequence s2 = unwrapper.Unwrap(0x0000);\n\t\tTestSequence s3 = unwrapper.Unwrap(0x0001);\n\n\t\tREQUIRE(s0 < s1);\n\t\tREQUIRE(s0 < s2);\n\t\tREQUIRE(s0 < s3);\n\t\tREQUIRE(s1 < s2);\n\t\tREQUIRE(s1 < s3);\n\t\tREQUIRE(s2 < s3);\n\n\t\tREQUIRE(TestSequence::Difference(s1, s0) == 1);\n\t\tREQUIRE(TestSequence::Difference(s2, s0) == 2);\n\t\tREQUIRE(TestSequence::Difference(s3, s0) == 3);\n\n\t\tREQUIRE(s1 > s0);\n\t\tREQUIRE(s2 > s0);\n\t\tREQUIRE(s3 > s0);\n\t\tREQUIRE(s2 > s1);\n\t\tREQUIRE(s3 > s1);\n\t\tREQUIRE(s3 > s2);\n\n\t\ts0.Increment();\n\t\tREQUIRE(s0 == s1);\n\n\t\ts1.Increment();\n\t\tREQUIRE(s1 == s2);\n\n\t\ts2.Increment();\n\t\tREQUIRE(s2 == s3);\n\n\t\tREQUIRE(TestSequence::AddTo(s0, 2) == s3);\n\t}\n\n\tSECTION(\"wrap around a few times\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tconst TestSequence s0 = unwrapper.Unwrap(0);\n\t\tTestSequence prev     = s0;\n\n\t\tfor (uint32_t i{ 1 }; i < 65536 * 3; ++i)\n\t\t{\n\t\t\tconst auto wrapped    = static_cast<uint16_t>(i);\n\t\t\tconst TestSequence si = unwrapper.Unwrap(wrapped);\n\n\t\t\tREQUIRE(s0 < si);\n\t\t\tREQUIRE(prev < si);\n\n\t\t\tprev = si;\n\t\t}\n\t}\n\n\tSECTION(\"increment is same as wrapped\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tTestSequence s0   = unwrapper.Unwrap(0);\n\t\tTestSequence prev = s0;\n\n\t\tfor (uint32_t i{ 1 }; i < 65536 * 2; ++i)\n\t\t{\n\t\t\tconst auto wrapped    = static_cast<uint16_t>(i);\n\t\t\tconst TestSequence si = unwrapper.Unwrap(wrapped);\n\n\t\t\ts0.Increment();\n\t\t\tREQUIRE(s0 == si);\n\n\t\t\tprev = si;\n\t\t}\n\t}\n\n\tSECTION(\"unwrapping larger number is always larger\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tfor (uint32_t i{ 1 }; i < 65536 * 2; ++i)\n\t\t{\n\t\t\tconst auto wrapped    = static_cast<uint16_t>(i);\n\t\t\tconst TestSequence si = unwrapper.Unwrap(wrapped);\n\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped + 1) > si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped + 5) > si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped + 10) > si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped + 100) > si);\n\t\t}\n\t}\n\n\tSECTION(\"unwrapping smaller number is always smaller\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\n\t\tfor (uint32_t i{ 1 }; i < 65536 * 2; ++i)\n\t\t{\n\t\t\tconst auto wrapped    = static_cast<uint16_t>(i);\n\t\t\tconst TestSequence si = unwrapper.Unwrap(wrapped);\n\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped - 1) < si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped - 5) < si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped - 10) < si);\n\t\t\tREQUIRE(unwrapper.Unwrap(wrapped - 100) < si);\n\t\t}\n\t}\n\n\tSECTION(\"difference is absolute\")\n\t{\n\t\tTestSequence::Unwrapper unwrapper;\n\t\tconst TestSequence thisValue  = unwrapper.Unwrap(10);\n\t\tconst TestSequence otherValue = TestSequence::AddTo(thisValue, 100);\n\n\t\tREQUIRE(TestSequence::Difference(thisValue, otherValue) == 100);\n\t\tREQUIRE(TestSequence::Difference(otherValue, thisValue) == 100);\n\n\t\tconst TestSequence minusValue = TestSequence::AddTo(thisValue, -100);\n\n\t\tREQUIRE(TestSequence::Difference(thisValue, minusValue) == 100);\n\t\tREQUIRE(TestSequence::Difference(minusValue, thisValue) == 100);\n\t}\n}\n"
  },
  {
    "path": "worker/test/src/testHelpers.cpp",
    "content": "#define MS_CLASS \"TEST::HELPERS\"\n\n#include \"test/include/testHelpers.hpp\"\n#include \"Logger.hpp\"\n#include <cstring> // std::memcmp()\n#include <fstream>\n#include <string>\n\nnamespace helpers\n{\n\tbool readBinaryFile(const char* file, uint8_t* buffer, size_t* len)\n\t{\n\t\tMS_TRACE();\n\n\t\t// NOLINTNEXTLINE(misc-const-correctness)\n\t\tstd::string filePath = \"test/\" + std::string(file);\n\n#ifdef _WIN32\n\t\tstd::replace(filePath.begin(), filePath.end(), '/', '\\\\');\n#endif\n\n\t\tstd::ifstream in(filePath, std::ios::ate | std::ios::binary);\n\n\t\tif (!in)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t*len = static_cast<size_t>(in.tellg()) - 1;\n\n\t\tin.seekg(0, std::ios::beg);\n\t\tin.read(reinterpret_cast<char*>(buffer), *len);\n\t\tin.close();\n\n\t\treturn true;\n\t}\n\n\tbool areBuffersEqual(const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2)\n\t{\n\t\tMS_TRACE();\n\n\t\tif (size1 != size2)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treturn std::memcmp(data1, data2, size1) == 0;\n\t}\n} // namespace helpers\n"
  },
  {
    "path": "worker/test/src/tests.cpp",
    "content": "#include \"common.hpp\"\n#include \"DepLibSRTP.hpp\"\n#include \"DepLibUV.hpp\"\n#include \"DepLibWebRTC.hpp\"\n#include \"DepOpenSSL.hpp\"\n// TODO: Remove once we only use built-in SCTP stack.\n#include \"DepUsrSCTP.hpp\"\n#include \"Settings.hpp\"\n#include \"Utils.hpp\"\n#include <catch2/catch_session.hpp>\n#include <cstdlib> // std::getenv()\n#include <sstream> // std::istringstream()\n#include <string>\n#include <vector>\n\nint main(int argc, char* argv[])\n{\n\tstd::string logLevel{ \"none\" };\n\tstd::vector<std::string> logTags = { \"info\" };\n\n\tconst auto* logLevelPtr = std::getenv(\"MS_TEST_LOG_LEVEL\");\n\tconst auto* logTagsPtr  = std::getenv(\"MS_TEST_LOG_TAGS\");\n\n\t// Get logLevel from ENV variable.\n\tif (logLevelPtr)\n\t{\n\t\tlogLevel = std::string(logLevelPtr);\n\t}\n\n\t// Get logTags from ENV variable.\n\tif (logTagsPtr)\n\t{\n\t\tauto logTagsStr = std::string(logTagsPtr);\n\t\tstd::istringstream iss(logTagsStr);\n\t\tstd::string logTag;\n\n\t\twhile (iss >> logTag)\n\t\t{\n\t\t\tlogTags.push_back(logTag);\n\t\t}\n\t}\n\n\tSettings::SetLogLevel(logLevel);\n\tSettings::SetLogTags(logTags);\n\tSettings::PrintConfiguration();\n\n\t// Initialize static stuff.\n\tDepLibUV::ClassInit();\n\tDepOpenSSL::ClassInit();\n\tDepLibSRTP::ClassInit();\n\t// TODO: Remove once we only use built-in SCTP stack.\n\tif (!Settings::configuration.useBuiltInSctpStack)\n\t{\n\t\tDepUsrSCTP::ClassInit();\n\t}\n\tDepLibWebRTC::ClassInit();\n\tUtils::Crypto::ClassInit();\n\n\tCatch::Session session;\n\n\tconst int status = session.run(argc, argv);\n\n\t// Free static stuff.\n\tDepLibSRTP::ClassDestroy();\n\tUtils::Crypto::ClassDestroy();\n\tDepLibWebRTC::ClassDestroy();\n\t// TODO: Remove once we only use built-in SCTP stack.\n\tif (!Settings::configuration.useBuiltInSctpStack)\n\t{\n\t\tDepUsrSCTP::ClassDestroy();\n\t}\n\tDepLibUV::ClassDestroy();\n\n\treturn status;\n}\n"
  },
  {
    "path": "worker/ubsan_suppressions.txt",
    "content": "function:err_string_data_hash\nfunction:err_string_data_cmp\nfunction:value_free_hash\nfunction:value_free_stack_doall\nfunction:pd_free\nfunction:SHA1_Update\nfunction:alg_cleanup\nfunction:getrn\nfunction:doall_util_fn\nfunction:sa_doall\nfunction:OPENSSL_sk_pop_free\nfunction:EVP_DigestUpdate\n"
  }
]